توی این درس یاد میگیری وقتی برنامهات چند کانتینر دارد (وبسرور، اپ، دیتابیس)، نوشتن چندین docker run با --network و -p و -v و -e خستهکننده و پر از خطاست. Docker Compose همه را در یک فایل YAML (compose.yaml) توصیف میکند و با یک دستور (docker compose up -d) همه را بالا میآورد و با docker compose down پاک میکند. ساختار فایل، دستورات up، down و ps، نامگذاری خودکار، و فرق docker compose (نسخهی جدید) با docker-compose (قدیمی) را میبینی.
تشبیه: برنامهی ارکستر
Section titled “تشبیه: برنامهی ارکستر”اگر هر نوازنده بدون نت و رهبر بنوازد، نتیجهاش آشفتگی است. docker run مثل این است که به هر نوازنده جداگانه بگویی چه بزند. Compose مثل نت ارکستر است: یک فایل که میگوید چه کسی چه چیزی را کجا و کی مینوازد. همهی نوازندهها (سرویسها) با یک دستور شروع و با یک دستور تمام میشوند، و نت را میتوانی در git نگه داری و به همکارت بدهی.
از چند دستور به یک فایل
Section titled “از چند دستور به یک فایل”مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: اولین compose.yaml
Section titled “مثال ۱: اولین compose.yaml”services: web: image: nginx:alpine ports: - "8380:80"کوتاهترین Compose ممکن: یک سرویس به اسم web از image nginx:alpine با نگاشت پورت. ساختار: زیر services: هر سرویس یک اسم دارد و زیرش تنظیماتش میآید (image، ports، …). تورفتگی (indentation) در YAML معنی دارد؛ با دو space (نه tab).
cd p1docker compose up -d 2>&1 | grep -E "Container|Network" | awk '!s[$0]++' | head -4sleep 2docker compose ps --format 'table {{.Name}}\t{{.Service}}\t{{.Status}}\t{{.Ports}}' | sed 's/, \[::\][^ ]*//'curl -s -o /dev/null -w "HTTP %{http_code}\n" localhost:8380 Network p1_default Creating Network p1_default Created Container p1-web-1 Creating Container p1-web-1 CreatedNAME SERVICE STATUS PORTSp1-web-1 web Up 2 seconds 0.0.0.0:8380->80/tcpHTTP 200docker compose up -d فایل را خواند، شبکهی پروژه (p1_default) و کانتینر p1-web-1 را ساخت. -d یعنی در پسزمینه (همان -d در docker run). docker compose ps وضعیت سرویسهای این پروژه را نشان میدهد.
مثال ۲: دو سرویس که با اسم هم را میبینند
Section titled “مثال ۲: دو سرویس که با اسم هم را میبینند”services: web: image: nginx:alpine client: image: alpine command: sh -c "sleep 3; wget -qO- http://web | grep -o '<title>.*</title>'; sleep 60"cd p2docker compose -p lxc2 up -d 2>&1 | grep -c "Started"sleep 8docker compose -p lxc2 logs client 2>&1 | sed -E 's/^[^|]+\| //'docker compose -p lxc2 down 2>&1 | grep -cE "Removed"2<title>Welcome to nginx!</title>3سرویس client با آدرس http://web به سرویس web وصل شد؛ اسم سرویس همان اسم DNS است (درس قبل). نیازی نبود شبکهای بسازیم یا IP بدانیم: Compose خودش یک شبکه ساخت و هر دو سرویس را به آن وصل کرد. -p lxc2 اسم پروژه را مشخص میکند (پیشفرض اسم پوشه است).
در Compose سرویس client با چه آدرسی به سرویس web وصل میشود؟
Compose یک شبکهی سفارشی با DNS داخلی میسازد؛ اسم سرویس همان hostname است.
مثال ۳: نامگذاری خودکار
Section titled “مثال ۳: نامگذاری خودکار”docker compose ls --filter name=p1 | sed 's#/tmp/lx-host/[^/]*/#./#'docker ps --filter label=com.docker.compose.project=p1 --format '{{.Names}}'docker network ls --filter name=p1_ --format '{{.Name}}'NAME STATUS CONFIG FILESp1 running(1) ./p1/compose.yamlp1-web-1p1_defaultنام کانتینر پروژه-سرویس-شماره است (p1-web-1) و شبکه پروژه_default. اسم پروژه از اسم پوشه میآید (یا -p یا name: در فایل). Compose همهچیز را با label علامت میزند (مثل com.docker.compose.project) تا بعداً بداند چه چیزی مال کدام پروژه است.
مثال ۴: دیدن پیکربندی نهایی (docker compose config)
Section titled “مثال ۴: دیدن پیکربندی نهایی (docker compose config)”cd p1docker compose configname: p1services: web: image: nginx:alpine networks: default: null ports: - mode: ingress target: 80 published: "8380" protocol: tcpnetworks: default: name: p1_defaultدستور docker compose config فایل را میخواند، متغیرها را جایگذاری میکند و نتیجهی نرمالشده را چاپ میکند؛ هم برای دیدن اینکه Compose چه فهمیده و هم برای پیدا کردن خطای فایل (اگر YAML خراب باشد همینجا خطا میدهد). دقت کن که name: و فرم کامل پورتها اضافه شده.
مثال ۵: docker compose در برابر docker-compose
Section titled “مثال ۵: docker compose در برابر docker-compose”docker compose versiondocker-compose version 2>&1 | head -1Docker Compose version v5.5.1Docker Compose version v5.5.1دو نوشتار وجود دارد: docker compose (با فاصله) افزونهی خود داکر است (نسخهی ۲ به بعد) و راه توصیهشده. docker-compose (با خط تیره) نام قدیمی است: نسخهی ۱ (نوشتهشده با Python) منسوخ و بدون پشتیبانی است. روی سیستم من، Docker Desktop خودش نسخهی جدید را با این نام قدیمی هم نصب کرده (خروجی بالا نسخهی یکسان است)، ولی ممکن است روی سیستم تو یا روی یک سرور قدیمی، docker-compose همان نسخهی ۱ باشد با رفتار متفاوت. برای همین در این دوره و در اسکریپتهایت فقط docker compose (با فاصله) را بنویس. همینطور خط version: "3.8" اول فایلهای قدیمی دیگر لازم نیست:
version: "3.8"services: web: image: nginx:alpinecd p3docker compose config 2>&1 | head -3level=warning msg="./p3/compose.yaml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion"name: p3services:Compose جدید میگوید کلید version منسوخ است و نادیده گرفته میشود. راهحل: حذفش کن.
مثال ۶: پاک کردن (docker compose down)
Section titled “مثال ۶: پاک کردن (docker compose down)”cd p1docker compose down 2>&1 | grep -E "Removed|Removing" | head -3docker ps -a --filter label=com.docker.compose.project=p1 -q | wc -l | tr -d ' ' Container p1-web-1 Removing Container p1-web-1 Removed Network p1_default Removing0docker compose down کانتینرها و شبکهی پروژه را حذف میکند. (volume های نامدار بهصورت پیشفرض میمانند؛ برای حذف آنها down -v.) تفاوت با docker compose stop: stop فقط متوقف میکند و کانتینرها میمانند.
پشت پرده
Section titled “پشت پرده”Compose یک ابزار سمت کلاینت است: فایل YAML را میخواند، آن را به یک مدل داخلی تبدیل میکند و سپس همان API ای را صدا میزند که docker CLI میزند (ساخت شبکه، ساخت کانتینر، start). هیچ daemon جداگانهای ندارد. مهمترین رفتارها:
- idempotent: دوباره
upزدن چیزی را که تغییر نکرده دست نمیزند؛ فقط سرویسهایی که پیکربندیشان عوض شده دوباره ساخته میشوند. - label ها: هر چیزی که میسازد
com.docker.compose.projectوcom.docker.compose.serviceدارد؛ همین است کهpsوdownرا ممکن میکند. - اسم پروژه از اسم پوشه میآید و روی نام کانتینر، شبکه و volume اثر دارد؛ دو پوشهی همنام در دو مسیر ممکن است تداخل کنند (در آن صورت
-pبده).
جدولهای مرجع
Section titled “جدولهای مرجع”| دستور | کار |
|---|---|
docker compose up -d |
ساخت و اجرای همهی سرویسها در پسزمینه |
docker compose down |
حذف کانتینرها و شبکهی پروژه |
docker compose ps |
وضعیت سرویسها |
docker compose logs |
لاگ سرویسها |
docker compose config |
پیکربندی نهایی و اعتبارسنجی |
docker compose ls |
همهی پروژههای Compose |
docker compose -p NAME ... |
اسم پروژه |
| کلید اولیه | معنی |
|---|---|
services: |
تعریف سرویسها |
image: |
image آماده |
ports: |
نگاشت پورت (مثل -p) |
command: |
override کردن CMD |
networks: / volumes: |
تعریف شبکه و volume (درس بعد) |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) تورفتگی اشتباه یا tab
Section titled “۱) تورفتگی اشتباه یا tab”mkdir -p bad1 && printf 'services:\n web:\n image: nginx:alpine\n' > bad1/compose.yamlcd bad1 && docker compose config 2>&1 | head -2 | cut -c1-120services.image must be a mappingimage همسطح web نوشته شده (باید زیر آن باشد). راهحل: دو space برای هر سطح؛ هرگز tab.
۲) فایل Compose پیدا نشد
Section titled “۲) فایل Compose پیدا نشد”mkdir -p empty-dir && cd empty-dir && docker compose up -d 2>&1 | head -2 | cut -c1-120no configuration file provided: not foundدر این پوشه compose.yaml (یا docker-compose.yml) نیست. راهحل: در پوشهی درست باش یا با -f مسیر فایل را بده.
۳) تداخل پورت
Section titled “۳) تداخل پورت”cd p1 && docker compose up -d >/dev/null 2>&1mkdir -p ../dup && printf 'services:\n web:\n image: nginx:alpine\n ports:\n - "8380:80"\n' > ../dup/compose.yamlcd ../dup && docker compose -p lxdup up -d 2>&1 | grep -iE "port is already allocated|Bind" | head -1 | cut -c1-150docker compose -p lxdup down >/dev/null 2>&1; cd ../p1 && docker compose down >/dev/null 2>&1Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint lxdup-web-1 (dd9ebc0789دو پروژه میخواهند هر دو پورت ۸۳۸۰ را بگیرند. راهحل: پورت میزبان متفاوت (مثل درس پورتها).
۴) استفاده از docker-compose قدیمی
Section titled “۴) استفاده از docker-compose قدیمی”دستور docker-compose (خط تیره) دیگر نگهداری نمیشود و ممکن است رفتار متفاوتی داشته باشد. راهحل: docker compose.
۵) گذاشتن container_name ثابت
Section titled “۵) گذاشتن container_name ثابت”container_name: web جلوی ساخت چند نسخه (--scale) یا چند پروژه را میگیرد، چون اسم کانتینر یکتاست. راهحل: بگذار Compose نامگذاری کند.
یک compose.yaml با سرویس web (nginx:alpine) روی پورت 8381 بنویس، با up -d بالا بیاور و با down پاکش کن.
دیدن جواب
mkdir -p e1 && printf 'services:\n web:\n image: nginx:alpine\n ports:\n - "8381:80"\n' > e1/compose.yamlcd e1docker compose -p lxe1 up -d 2>&1 | grep -c Startedsleep 2; curl -s -o /dev/null -w "%{http_code}\n" localhost:8381docker compose -p lxe1 down 2>&1 | grep -c Removed12002تمرین اصلی: nginx و یک اپ را با Compose بالا بیاور. سرویس web (nginx) و سرویس app (alpine) که هر ثانیه آدرس http://web را wget کند. با docker compose logs app ثابت کن به web رسیده.
دیدن جواب
mkdir -p e2 && cat > e2/compose.yaml <<'LXEOF'services: web: image: nginx:alpine app: image: alpine command: sh -c "sleep 2; wget -qO- http://web | head -c 15; echo; sleep 30"LXEOFcd e2docker compose -p lxe2 up -d >/dev/null 2>&1; sleep 6docker compose -p lxe2 logs app 2>&1 | sed -E 's/^[^|]+\| //'docker compose -p lxe2 down >/dev/null 2>&1<!DOCTYPE html>با docker compose config ثابت کن Compose پورت "8382:80" را در فرم کامل چطور میخواند، سپس اسم پروژه را با name: داخل فایل مشخص کن و اسم کانتینر ساختهشده را بخوان.
دیدن جواب
mkdir -p e3 && printf 'name: lxproj\nservices:\n web:\n image: nginx:alpine\n ports:\n - "8382:80"\n' > e3/compose.yamlcd e3docker compose config | grep -A4 "ports:"docker compose up -d >/dev/null 2>&1docker ps --filter label=com.docker.compose.project=lxproj --format '{{.Names}}'docker compose down >/dev/null 2>&1 ports: - mode: ingress target: 80 published: "8382" protocol: tcplxproj-web-1آزمونک
Section titled “آزمونک”Compose چه مشکلی را حل میکند؟
بهجای چندین docker run بلند.
تفاوت docker compose با docker-compose؟
از docker compose (با فاصله) استفاده کن.
در Compose سرویسها چطور همدیگر را پیدا میکنند؟
Compose یک شبکهی سفارشی میسازد.
docker compose down چه چیزی را حذف میکند؟
برای حذف volume ها down -v.
تورفتگی در YAML Compose با چیست؟
tab در YAML مجاز نیست.
جمعبندی
Section titled “جمعبندی”- Compose چند کانتینر را در یک فایل
compose.yamlتوصیف میکند؛docker compose up -dهمه را بالا میآورد وdownپاک میکند. - ساختار:
services:← اسم سرویس ←image،ports،commandو …؛ تورفتگی با space. - سرویسها با اسم سرویس به هم وصل میشوند؛ Compose شبکهی پروژه را خودش میسازد.
- نامگذاری:
پروژه-سرویس-N؛ اسم پروژه از پوشه یا-pیاname:. docker compose configپیکربندی را اعتبارسنجی میکند؛docker compose(v2) نهdocker-compose.
| دستور | کاری که میکند |
|---|---|
docker compose up -d | ساخت و اجرا در پسزمینه |
docker compose down | حذف کانتینرها و شبکه |
docker compose ps | وضعیت سرویسها |
docker compose logs -f | لاگ زنده |
docker compose config | پیکربندی نهایی/اعتبارسنجی |
docker compose ls | پروژههای Compose |
docker compose -p NAME up -d | اسم پروژه |
docker compose -f FILE up -d | فایل دیگر |
docker compose version | نسخهی Compose |