رفتن به محتوا
LoopX

معرفی Docker Compose

توی این درس یاد می‌گیری وقتی برنامه‌ات چند کانتینر دارد (وب‌سرور، اپ، دیتابیس)، نوشتن چندین 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 نگه داری و به همکارت بدهی.

با docker run هر کانتینر یک دستور بلند جدا دارد؛ با Compose همه در یک فایل توصیف و با یک دستور مدیریت می‌شوند.
p1/compose.yaml
services:
web:
image: nginx:alpine
ports:
- "8380:80"

کوتاه‌ترین Compose ممکن: یک سرویس به اسم web از image nginx:alpine با نگاشت پورت. ساختار: زیر services: هر سرویس یک اسم دارد و زیرش تنظیماتش می‌آید (image، ports، …). تورفتگی (indentation) در YAML معنی دارد؛ با دو space (نه tab).

Terminal window
cd p1
docker compose up -d 2>&1 | grep -E "Container|Network" | awk '!s[$0]++' | head -4
sleep 2
docker 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 Created
NAME SERVICE STATUS PORTS
p1-web-1 web Up 2 seconds 0.0.0.0:8380->80/tcp
HTTP 200

docker compose up -d فایل را خواند، شبکه‌ی پروژه (p1_default) و کانتینر p1-web-1 را ساخت. -d یعنی در پس‌زمینه (همان -d در docker run). docker compose ps وضعیت سرویس‌های این پروژه را نشان می‌دهد.

مثال ۲: دو سرویس که با اسم هم را می‌بینند

Section titled “مثال ۲: دو سرویس که با اسم هم را می‌بینند”
p2/compose.yaml
services:
web:
image: nginx:alpine
client:
image: alpine
command: sh -c "sleep 3; wget -qO- http://web | grep -o '<title>.*</title>'; sleep 60"
Terminal window
cd p2
docker compose -p lxc2 up -d 2>&1 | grep -c "Started"
sleep 8
docker 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 وصل می‌شود؟

مثال ۳: نام‌گذاری خودکار

Section titled “مثال ۳: نام‌گذاری خودکار”
Terminal window
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 FILES
p1 running(1) ./p1/compose.yaml
p1-web-1
p1_default

نام کانتینر پروژه-سرویس-شماره است (p1-web-1) و شبکه پروژه_default. اسم پروژه از اسم پوشه می‌آید (یا -p یا name: در فایل). Compose همه‌چیز را با label علامت می‌زند (مثل com.docker.compose.project) تا بعداً بداند چه چیزی مال کدام پروژه است.

مثال ۴: دیدن پیکربندی نهایی (docker compose config)

Section titled “مثال ۴: دیدن پیکربندی نهایی (docker compose config)”
Terminal window
cd p1
docker compose config
خروجی
name: p1
services:
web:
image: nginx:alpine
networks:
default: null
ports:
- mode: ingress
target: 80
published: "8380"
protocol: tcp
networks:
default:
name: p1_default

دستور docker compose config فایل را می‌خواند، متغیرها را جای‌گذاری می‌کند و نتیجه‌ی نرمال‌شده را چاپ می‌کند؛ هم برای دیدن اینکه Compose چه فهمیده و هم برای پیدا کردن خطای فایل (اگر YAML خراب باشد همین‌جا خطا می‌دهد). دقت کن که name: و فرم کامل پورت‌ها اضافه شده.

مثال ۵: docker compose در برابر docker-compose

Section titled “مثال ۵: docker compose در برابر docker-compose”
Terminal window
docker compose version
docker-compose version 2>&1 | head -1
خروجی
Docker Compose version v5.5.1
Docker Compose version v5.5.1

دو نوشتار وجود دارد: docker compose (با فاصله) افزونه‌ی خود داکر است (نسخه‌ی ۲ به بعد) و راه توصیه‌شده. docker-compose (با خط تیره) نام قدیمی است: نسخه‌ی ۱ (نوشته‌شده با Python) منسوخ و بدون پشتیبانی است. روی سیستم من، Docker Desktop خودش نسخه‌ی جدید را با این نام قدیمی هم نصب کرده (خروجی بالا نسخه‌ی یکسان است)، ولی ممکن است روی سیستم تو یا روی یک سرور قدیمی، docker-compose همان نسخه‌ی ۱ باشد با رفتار متفاوت. برای همین در این دوره و در اسکریپت‌هایت فقط docker compose (با فاصله) را بنویس. همین‌طور خط version: "3.8" اول فایل‌های قدیمی دیگر لازم نیست:

p3/compose.yaml
version: "3.8"
services:
web:
image: nginx:alpine
Terminal window
cd p3
docker compose config 2>&1 | head -3
خروجی
level=warning msg="./p3/compose.yaml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion"
name: p3
services:

Compose جدید می‌گوید کلید version منسوخ است و نادیده گرفته می‌شود. راه‌حل: حذفش کن.

مثال ۶: پاک کردن (docker compose down)

Section titled “مثال ۶: پاک کردن (docker compose down)”
Terminal window
cd p1
docker compose down 2>&1 | grep -E "Removed|Removing" | head -3
docker 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 Removing
0

docker compose down کانتینرها و شبکه‌ی پروژه را حذف می‌کند. (volume های نام‌دار به‌صورت پیش‌فرض می‌مانند؛ برای حذف آن‌ها down -v.) تفاوت با docker compose stop: stop فقط متوقف می‌کند و کانتینرها می‌مانند.

Compose یک ابزار سمت کلاینت است: فایل YAML را می‌خواند، آن را به یک مدل داخلی تبدیل می‌کند و سپس همان API ای را صدا می‌زند که docker CLI می‌زند (ساخت شبکه، ساخت کانتینر، start). هیچ daemon جداگانه‌ای ندارد. مهم‌ترین رفتارها:

  • idempotent: دوباره up زدن چیزی را که تغییر نکرده دست نمی‌زند؛ فقط سرویس‌هایی که پیکربندی‌شان عوض شده دوباره ساخته می‌شوند.
  • label ها: هر چیزی که می‌سازد com.docker.compose.project و com.docker.compose.service دارد؛ همین است که ps و down را ممکن می‌کند.
  • اسم پروژه از اسم پوشه می‌آید و روی نام کانتینر، شبکه و volume اثر دارد؛ دو پوشه‌ی هم‌نام در دو مسیر ممکن است تداخل کنند (در آن صورت -p بده).
دستور کار
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 (درس بعد)
Terminal window
mkdir -p bad1 && printf 'services:\n web:\n image: nginx:alpine\n' > bad1/compose.yaml
cd bad1 && docker compose config 2>&1 | head -2 | cut -c1-120
خروجی
services.image must be a mapping

image هم‌سطح web نوشته شده (باید زیر آن باشد). راه‌حل: دو space برای هر سطح؛ هرگز tab.

Terminal window
mkdir -p empty-dir && cd empty-dir && docker compose up -d 2>&1 | head -2 | cut -c1-120
خروجی
no configuration file provided: not found

در این پوشه compose.yaml (یا docker-compose.yml) نیست. راه‌حل: در پوشه‌ی درست باش یا با -f مسیر فایل را بده.

Terminal window
cd p1 && docker compose up -d >/dev/null 2>&1
mkdir -p ../dup && printf 'services:\n web:\n image: nginx:alpine\n ports:\n - "8380:80"\n' > ../dup/compose.yaml
cd ../dup && docker compose -p lxdup up -d 2>&1 | grep -iE "port is already allocated|Bind" | head -1 | cut -c1-150
docker compose -p lxdup down >/dev/null 2>&1; cd ../p1 && docker compose down >/dev/null 2>&1
خروجی
Error 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: web جلوی ساخت چند نسخه (--scale) یا چند پروژه را می‌گیرد، چون اسم کانتینر یکتاست. راه‌حل: بگذار Compose نام‌گذاری کند.

✎ تمرینآسان

یک compose.yaml با سرویس web (nginx:alpine) روی پورت 8381 بنویس، با up -d بالا بیاور و با down پاکش کن.

دیدن جواب
Terminal window
mkdir -p e1 && printf 'services:\n web:\n image: nginx:alpine\n ports:\n - "8381:80"\n' > e1/compose.yaml
cd e1
docker compose -p lxe1 up -d 2>&1 | grep -c Started
sleep 2; curl -s -o /dev/null -w "%{http_code}\n" localhost:8381
docker compose -p lxe1 down 2>&1 | grep -c Removed
خروجی
1
200
2
✎ تمرینمتوسط

تمرین اصلی: nginx و یک اپ را با Compose بالا بیاور. سرویس web (nginx) و سرویس app (alpine) که هر ثانیه آدرس http://web را wget کند. با docker compose logs app ثابت کن به web رسیده.

دیدن جواب
Terminal window
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"
LXEOF
cd e2
docker compose -p lxe2 up -d >/dev/null 2>&1; sleep 6
docker 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: داخل فایل مشخص کن و اسم کانتینر ساخته‌شده را بخوان.

دیدن جواب
Terminal window
mkdir -p e3 && printf 'name: lxproj\nservices:\n web:\n image: nginx:alpine\n ports:\n - "8382:80"\n' > e3/compose.yaml
cd e3
docker compose config | grep -A4 "ports:"
docker compose up -d >/dev/null 2>&1
docker 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: tcp
lxproj-web-1
؟ آزمونک
  1. Compose چه مشکلی را حل می‌کند؟

  2. تفاوت docker compose با docker-compose؟

  3. در Compose سرویس‌ها چطور هم‌دیگر را پیدا می‌کنند؟

  4. docker compose down چه چیزی را حذف می‌کند؟

  5. تورفتگی در YAML Compose با چیست؟

  • 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