اوبنتوداکردواپس

آموزش نصب و استفاده از Docker Compose روی اوبونتو

مقدمه

Docker فرایند مدیریت پروسه‌های اپلیکیشن را در کانتینرها ساده می‌کند. اگرچه کانتینرها از برخی جهات شبیه ماشین‌های مجازی هستند، اما سبک‌تر بوده و منابع کمتری مصرف می‌کنند. همین موضوع به توسعه‌دهندگان اجازه می‌دهد محیط یک اپلیکیشن را به چند سرویس ایزوله تقسیم کنند.

برای اپلیکیشن‌هایی که به چند سرویس وابسته‌اند، هماهنگ‌کردن همه کانتینرها برای راه‌اندازی، برقراری ارتباط و خاموش‌شدن گروهی، خیلی زود می‌تواند غیرقابل‌مدیریت شود. Docker Compose ابزاری است که به شما امکان می‌دهد محیط‌های اپلیکیشنِ چندکانتینری را بر اساس تعریف‌های قرارگرفته در یک فایل YAML اجرا کنید. این ابزار با استفاده از تعریف سرویس‌ها، محیط‌هایی کاملاً قابل‌سفارشی‌سازی با چندین کانتینر می‌سازد که می‌توانند شبکه‌ها و والیوم‌های داده را با هم به اشتراک بگذارند.

این راهنما شما را در نصب Docker Compose روی یک سرور اوبونتو و اجرای یک کانتینر ساده همراهی می‌کند. از آنجا به بعد، یاد می‌گیرید با استفاده از یک اپلیکیشن وردپرس و یک پایگاه داده MySQL، یک محیط چندسرویسی بسازید. همچنین موضوعات پیشرفته‌تری را پوشش می‌دهیم؛ از جمله مقیاس‌دهی سرویس‌ها، تعریف شبکه‌های سفارشی و استفاده از دستورات ماژولارِ include. در پایان، این مقاله یک راهنمای مهاجرت از docker-compose قدیمی (v1) به docker compose مدرن (v2) و یک بخش مفصل درباره عیب‌یابی مشکلات رایج مانند تداخل پورت و خطاهای دسترسی ارائه می‌کند.

نکات کلیدی

  • Docker Compose ابزاری برای تعریف و اجرای اپلیکیشن‌های چندکانتینری است که از یک فایل YAML واحد برای پیکربندی سرویس‌ها، شبکه‌ها و والیوم‌ها استفاده می‌کند.
  • Docker Compose روی اوبونتو با افزودن مخزن Docker و نصب پکیج docker-compose-plugin از طریق apt نصب می‌شود.
  • دستور مدرن، docker compose است (با فاصله). این دستور جایگزین ابزار docker-compose (با خط تیره) نسخه v1 شده که منسوخ شده است.
  • برای شروع یک اپلیکیشن در حالت پس‌زمینه (Detached) از docker compose up -d و برای توقف و حذف همه کانتینرها و شبکه‌های آن از docker compose down استفاده کنید.
  • دستور depends_on به کنترل ترتیب راه‌اندازی سرویس‌ها کمک می‌کند؛ مثلاً اطمینان از راه‌اندازی کانتینر پایگاه داده قبل از کانتینر اپلیکیشن وب.
  • سه خطای رایج عبارت‌اند از: خطای «Permission Denied» هنگام اجرای Compose (راه‌حل: افزودن کاربر به گروه docker)، تداخل پورت هنگام راه‌اندازی (راه‌حل: تغییر نگاشت پورت میزبان در docker-compose.yml) و شکست راه‌اندازی کانتینرها در اپ‌های چندسرویسی (راه‌حل: استفاده از healthcheck همراه با depends_on: condition: service_healthy به‌جای اتکای صرف به depends_on).
  • می‌توانید با دستور docker compose up --scale <service_name>=<number> چندین نمونه از یک سرویس را اجرا کنید.
  • دستور include به شما اجازه می‌دهد یک فایل بزرگ docker-compose.yml را به فایل‌های پیکربندی کوچک‌تر و قابل‌مدیریت‌تر تقسیم کنید.

پیش‌نیازها

برای دنبال کردن این مقاله به موارد زیر نیاز دارید:

  • دسترسی به یک ماشین محلی اوبونتو یا سرور توسعه، به‌عنوان کاربری غیر root با دسترسی sudo. اگر از سرور راه‌دور استفاده می‌کنید، توصیه می‌شود یک فایروال فعال روی آن نصب داشته باشید. برای راه‌اندازی این موارد، راهنمای راه‌اندازی اولیه سرور اوبونتو را مطالعه کنید.
  • Docker نصب‌شده روی سرور یا ماشین محلی شما، طبق مراحل ۱ و ۲ مقاله «آموزش نصب Docker روی اوبونتو – راهنمای گام‌به‌گام».

توجه: از Docker Compose نسخه v2 به بعد، Docker به استفاده از دستور پلاگین compose در CLI روی آورده و از docker-compose اصلی فاصله گرفته است؛ همان‌طور که در مقاله «آموزش نصب Docker Compose روی اوبونتو (راهنمای گام‌به‌گام)» مستند شده است. اگرچه روش نصب متفاوت است، اما در عمل استفاده از آن فقط به حذف خط تیره از دستورات docker-compose و تبدیل آن به docker compose ختم می‌شود. برای جزئیات کامل سازگاری، مستندات رسمی Docker درباره سازگاری دستورات بین compose جدید و docker-compose قدیمی را بررسی کنید.

مرحله ۱ — نصب Docker Compose

دو روش برای نصب Docker Compose روی اوبونتو وجود دارد:

  • نصب با استفاده از مخزن
  • نصب دستی

در این بخش هر دو روش را بررسی می‌کنیم.

نصب Docker Compose با استفاده از مخزن apt

ابتدا مخزن apt مربوط به Docker را راه‌اندازی می‌کنیم:

# افزودن کلید GPG رسمی Docker:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://docker-ce.parmincloud.ir/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# افزودن مخزن به منابع Apt:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://docker-ce.parmincloud.ir/ \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

حالا می‌توانید Docker Compose را با دستور زیر نصب کنید:

sudo apt install docker-compose-plugin

Docker Compose اکنون با موفقیت روی سیستم شما نصب شده است. برای تأیید موفقیت‌آمیز بودن نصب، می‌توانید این دستور را اجرا کنید:

docker compose version

خروجی مشابه زیر را خواهید دید:

Docker Compose version v2.x.x

توجه: Docker Compose نسخه v5، نسخه اصلی بعدی پس از v2 است. تیم Docker نسخه‌های v3 و v4 را نادیده گرفت تا از ایجاد ابهام با شماره نسخه‌های قدیمی فرمت فایل Compose (نسخه‌های 2.x و 3.x مربوط به دوران v1) جلوگیری شود. Compose v5 از نظر عملکردی با v2 سازگار است؛ فایل‌ها و دستورات docker-compose.yml موجود شما بدون هیچ تغییری کار می‌کنند. افزوده اصلی v5 یک SDK رسمی Go است که به توسعه‌دهندگان اجازه می‌دهد قابلیت‌های Compose را مستقیماً در ابزارها و اتوماسیون خودشان یکپارچه کنند. پکیج docker-compose-plugin که در این راهنما نصب می‌شود، از طریق فرایند استاندارد apt upgrade به v5 ارتقا خواهد یافت. می‌توانید نسخه‌های منتشرشده را در صفحه رسمی GitHub Releases پروژه Compose دنبال کنید.

نصب دستی Docker Compose

برای اطمینان از دریافت جدیدترین نسخه پایدار Docker Compose، این نرم‌افزار را از مخزن رسمی GitHub آن دانلود کنید.

ابتدا آخرین نسخه موجود در صفحه Releases آن را بررسی کنید. در زمان نگارش این مقاله، جدیدترین نسخه پایدار v2.40.2 است.

برای دانلود از دستور زیر استفاده کنید:

mkdir -p ~/.docker/cli-plugins/
curl -SL https://github.com/docker/compose/releases/download/v2.40.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose

سپس دسترسی‌های درست را تنظیم کنید تا دستور docker compose قابل اجرا باشد:

chmod +x ~/.docker/cli-plugins/docker-compose

در بخش بعدی می‌بینید که چطور یک فایل docker-compose.yml راه‌اندازی کرده و با این ابزار یک محیط کانتینری را بالا بیاورید.

مرحله ۲ — راه‌اندازی فایل docker-compose.yml

برای نمایش نحوه راه‌اندازی فایل docker-compose.yml و کار با Docker Compose، یک محیط وب‌سرور با استفاده از ایمیج رسمی Nginx در Docker Hub (رجیستری عمومی Docker) می‌سازید. این محیط کانتینری یک فایل HTML استاتیک ساده را سرو می‌کند.

ابتدا در پوشه خانه خود یک دایرکتوری جدید بسازید و وارد آن شوید:

mkdir ~/compose-demo
cd ~/compose-demo

در این دایرکتوری، یک پوشه اپلیکیشن بسازید که به‌عنوان ریشه مستندات (Document Root) محیط Nginx شما عمل کند:

mkdir app

با ویرایشگر متن دلخواه خود، یک فایل index.html جدید داخل پوشه app بسازید:

nano app/index.html

محتوای زیر را در این فایل قرار دهید:

~/compose-demo/app/index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Docker Compose Demo</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/dark.min.css">
</head>
<body>

    <h1>This is a Docker Compose Demo Page.</h1>
    <p>This content is being served by an Nginx container.</p>

</body>
</html>

پس از اتمام کار، فایل را ذخیره کرده و ببندید. اگر از nano استفاده می‌کنید، با فشردن CTRL+X و سپس Y و ENTER این کار را انجام دهید.

سپس فایل docker-compose.yml را بسازید:

nano docker-compose.yml

محتوای زیر را در فایل docker-compose.yml خود قرار دهید:

docker-compose.yml

services:
  web:
    image: hub.cr.parmincloud.ir/library/nginx:alpine
    ports:
      - "8000:80"
    volumes:
      - ./app:/usr/share/nginx/html

در Docker Compose مدرن، فیلد version اختیاری است و معمولاً حذف می‌شود؛ چرا که Compose می‌تواند نسخه پیکربندی را به‌صورت خودکار تشخیص دهد. مثال بالا فیلد version را شامل نمی‌شود که رویکرد توصیه‌شده برای بیشتر پروژه‌های جدید است. تنها در صورت نیاز به سازگاری با ابزارهای قدیمی لازم است version را مشخص کنید.

سپس بلوک services را دارید که سرویس‌های تشکیل‌دهنده این محیط را در آن تعریف می‌کنید. در این مورد، یک سرویس واحد با نام web دارید. این سرویس از ایمیج nginx:alpine استفاده کرده و با دستور ports یک نگاشت پورت تنظیم می‌کند. تمام درخواست‌های رسیده به پورت 8000 ماشین میزبان (سیستمی که Docker Compose را از روی آن اجرا می‌کنید)، به کانتینر web روی پورت 80 هدایت می‌شوند؛ جایی که Nginx در حال اجراست.

دستور volumes یک والیوم اشتراکی بین ماشین میزبان و کانتینر ایجاد می‌کند. این کار پوشه محلی app را با کانتینر به اشتراک می‌گذارد و والیوم داخل کانتینر در مسیر /usr/share/nginx/html قرار می‌گیرد که این مسیر، ریشه مستندات پیش‌فرض Nginx را بازنویسی (Override) می‌کند.

فایل را ذخیره کرده و ببندید.

شما یک صفحه دمو و یک فایل docker-compose.yml برای ساخت یک محیط وب‌سرور کانتینری که آن را سرو می‌کند، راه‌اندازی کرده‌اید. در مرحله بعد، این محیط را با Docker Compose بالا می‌آورید.

مرحله ۳ — اجرای Docker Compose

حالا که فایل docker-compose.yml در جای خود قرار دارد، می‌توانید Docker Compose را برای بالا آوردن محیط خود اجرا کنید. دستور زیر ایمیج‌های لازم Docker را دانلود کرده، برای سرویس web یک کانتینر می‌سازد و محیط کانتینری را در حالت پس‌زمینه اجرا می‌کند:

docker compose up -d

Docker Compose ابتدا ایمیج تعریف‌شده را روی سیستم محلی شما جست‌وجو می‌کند و در صورت پیدا نکردن آن، ایمیج را از Docker Hub دانلود می‌کند. خروجی مشابه زیر را می‌بینید:

خروجی:

Creating network "compose-demo_default" with the default driver
Pulling web (nginx:alpine)...
alpine: Pulling from library/nginx
cbdbe7a5bc2a: Pull complete
10c113fb0c77: Pull complete
9ba64393807b: Pull complete
c829a9c40ab2: Pull complete
61d685417b2f: Pull complete
Digest: sha256:57254039c6313fe8c53f1acbf15657ec9616a813397b74b063e32443427c5502
Status: Downloaded newer image for nginx:alpine
Creating compose-demo_web_1 ... done

توجه: اگر هنگام اجرای docker compose up با خطای «permission denied» روبرو شدید، این معمولاً یعنی کاربر غیر root شما دسترسی لازم برای دسترسی به سوکت دیمن Docker را ندارد.

به‌صورت پیش‌فرض، دیمن Docker به یک سوکت Unix با آدرس (/var/run/docker.sock) متصل است که مالک آن کاربر root است. برای رفع این مشکل، باید کاربر غیر root خود را به گروه docker اضافه کنید؛ گروهی که هنگام نصب Docker ایجاد شده است.

دستور زیر را برای افزودن کاربر خود به گروه docker اجرا کنید:

sudo usermod -aG docker ${USER}

پس از اجرای این دستور، باید از سیستم خارج شده و دوباره وارد شوید تا تغییرات گروه اعمال شوند. همچنین می‌توانید تغییرات را برای نشست فعلی ترمینال با تایپ دستور زیر فعال کنید:

newgrp docker

این دستور باید هرگونه خطای دسترسی مربوط به سوکت Docker را رفع کند. برای راهنمای کامل، به مرحله ۲ مقاله «آموزش نصب Docker روی اوبونتو – راهنمای گام‌به‌گام» مراجعه کنید.

محیط شما اکنون در پس‌زمینه در حال اجراست. برای تأیید فعال بودن کانتینر می‌توانید اجرا کنید:

docker compose ps

این دستور اطلاعات مربوط به کانتینرهای در حال اجرا و وضعیت آن‌ها و همچنین نگاشت‌های پورت فعال را نشان می‌دهد:

خروجی:

       Name                     Command               State          Ports        
----------------------------------------------------------------------------------
compose-demo_web_1   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:8000->80/tcp

حالا می‌توانید با مرورگر خود به آدرس localhost:8000 (اگر این دمو را روی ماشین محلی خود اجرا می‌کنید) یا your_server_domain_or_IP:8000 (اگر روی سرور راه‌دور اجرا می‌کنید) به اپلیکیشن دمو دسترسی پیدا کنید.

صفحه‌ای شبیه این خواهید دید:

[تصویر: صفحه دمو Docker Compose]

والیوم اشتراکی که در فایل docker-compose.yml تنظیم کرده‌اید، فایل‌های پوشه app شما را با ریشه مستندات کانتینر همگام نگه می‌دارد. اگر تغییری در فایل index.html ایجاد کنید، به‌صورت خودکار توسط کانتینر تشخیص داده شده و در نتیجه هنگام رفرش صفحه در مرورگر شما نمایش داده می‌شود.

در مرحله بعد، مدیریت محیط کانتینری خود با دستورات Docker Compose را می‌بینید.

مرحله ۴ — آشنایی با دستورات Docker Compose

دیدید که چطور یک فایل docker-compose.yml راه‌اندازی کرده و محیط خود را با docker compose up بالا بیاورید. حالا می‌بینید که چطور با دستورات Docker Compose، محیط کانتینری خود را مدیریت و با آن تعامل داشته باشید.

برای بررسی لاگ‌های تولیدشده توسط کانتینر Nginx خود، می‌توانید از دستور logs استفاده کنید:

docker compose logs

خروجی مشابه زیر خواهید دید:

خروجی:

Attaching to compose-demo_web_1
web_1  | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
web_1  | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
web_1  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
web_1  | 10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
web_1  | 10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
web_1  | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
web_1  | /docker-entrypoint.sh: Configuration complete; ready for start up
web_1  | 172.22.0.1 - - [02/Jun/2020:10:47:13 +0000] "GET / HTTP/1.1" 200 353 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" "-"

اگر می‌خواهید اجرای محیط را بدون تغییر وضعیت فعلی کانتینرها متوقف کنید، می‌توانید از دستور زیر استفاده کنید:

docker compose pause

خروجی:

Pausing compose-demo_web_1 ... done

برای از سرگیری اجرا پس از صدور دستور pause:

docker compose unpause

خروجی:

Unpausing compose-demo_web_1 ... done

دستور stop اجرای کانتینر را خاتمه می‌دهد، اما هیچ داده‌ای مرتبط با کانتینرهای شما را تخریب نمی‌کند:

docker compose stop

خروجی:

Stopping compose-demo_web_1 ... done

اگر می‌خواهید کانتینرها، شبکه‌ها و والیوم‌های مرتبط با این محیط کانتینری را حذف کنید، از دستور down استفاده کنید:

docker compose down

خروجی:

Removing compose-demo_web_1 ... done
Removing network compose-demo_default

توجه کنید که این کار ایمیج پایه‌ای که Docker Compose برای راه‌اندازی محیط شما استفاده کرده (در این مورد، nginx:alpine) را حذف نمی‌کند. به این ترتیب، هر بار که محیط خود را با docker compose up دوباره بالا بیاورید، فرایند بسیار سریع‌تر خواهد بود؛ چون ایمیج از قبل روی سیستم شما موجود است.

در صورتی که بخواهید ایمیج پایه را هم از سیستم خود حذف کنید، می‌توانید از دستور زیر استفاده کنید:

docker image rm hub.cr.parmincloud.ir/library/nginx:alpine

خروجی:

Untagged: hub.cr.parmincloud.ir/library/nginx:alpine
Untagged: nginx@sha256:b89a6ccbda39576ad23fd079978c967cecc6b170db6e7ff8a769bf2259a71912
Deleted: sha256:7d0cdcc60a96a5124763fddf5d534d058ad7d0d8d4c3b8be2aefedf4267d0270
Deleted: sha256:05a0eaca15d731e0029a7604ef54f0dda3b736d4e987e6ac87b91ac7aac03ab1
Deleted: sha256:c6bbc4bdac396583641cb44cd35126b2c195be8fe1ac5e6c577c14752bbe9157
Deleted: sha256:35789b1e1a362b0da8392ca7d5759ef08b9a6b7141cc1521570f984dc7905eb6
Deleted: sha256:a3efaa65ec344c882fe5d543a392a54c4ceacd1efd91662d06964211b1be4c08
Deleted: sha256:3e207b409db364b595ba862cdc12be96dcdad8e36c59a03b7b3b61c946a5741a

توجه: برای مرجع کامل‌تر دستورات Docker، به راهنمای «آموزش نصب Docker روی اوبونتو – راهنمای گام‌به‌گام» مراجعه کنید.

مرحله ۵ — ساخت اپلیکیشن چندسرویسی

قدرت واقعی Docker Compose در مدیریت چندین سرویس است که با هم کار می‌کنند. مثال Nginx یک سرویس تک بود. بیایید یک اپلیکیشن چندسرویسی عملی‌تر بسازیم: یک وب‌سایت وردپرس متصل به یک پایگاه داده MySQL.

این راه‌اندازی شامل دو سرویس است: wordpress (اجرای اپلیکیشن) و db (اجرای پایگاه داده). همچنین از والیوم‌های Docker استفاده می‌کنیم تا مطمئن شویم داده‌های پایگاه داده حتی در صورت حذف کانتینر باقی می‌مانند.

بیایید یک دایرکتوری جدید برای این اپلیکیشن بسازیم:

mkdir -p ~/compose-demo/wordpressapp
cd ~/compose-demo/wordpressapp

برای این مثال، به هیچ فایل محلی نیاز نداریم؛ چرا که ایمیج‌ها همه نرم‌افزارهای لازم را در بر دارند.

یک فایل docker-compose.yml جدید بسازید:

nano docker-compose.yml

پیکربندی زیر را جای‌گذاری کنید. این فایل پیچیده‌تر است، پس هر بخش را جداگانه بررسی می‌کنیم.

docker-compose.yml

services:
  db:
    image: hub.cr.parmincloud.ir/library/mysql:8.0
    container_name: mysql_db
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: <your_root_password_here>
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress_user
      MYSQL_PASSWORD: <your_wordpress_password_here>
    restart: unless-stopped

  wordpress:
    image: hub.cr.parmincloud.ir/library/wordpress:latest
    container_name: wordpress_app
    ports:
      - "8001:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress_user
      WORDPRESS_DB_PASSWORD: <your_wordpress_password_here>
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - ./wp_content:/var/www/html/wp-content
    depends_on:
      - db
    restart: unless-stopped

volumes:
  db_data:

توجه: مقادیر رمز عبور در اینجا صرفاً جهت نمایش به‌صورت ثابت نوشته شده‌اند. هنگام استفاده در محیط واقعی، حتماً از متغیرهای محیطی در فایل .env استفاده کنید تا اطلاعات حساس شما فاش نشود.

فایل را ذخیره کرده و ببندید. به یاد داشته باشید که جایگزین‌های <...> را با رمزهای عبور قوی و امن عوض کنید.

بیایید دستورات (Directive) جدید این فایل را بررسی کنیم:

  • services: این بلوک همچنان کانتینرهای ما را تعریف می‌کند. حالا دو کانتینر داریم: db و wordpress.
  • image: برای پایگاه داده از mysql:8.0 و برای اپلیکیشن از ایمیج wordpress:latest استفاده می‌کنیم.
  • container_name: یک نام مشخص و قابل‌خواندن برای کانتینر تنظیم می‌کند که ارجاع دادن به آن از نام‌های تولیدشده خودکار راحت‌تر است.
  • environment: این یک فهرست از متغیرهای محیطی است که به کانتینر پاس داده می‌شوند. ایمیج mysql از این متغیرها برای تنظیم رمز عبور root و ساخت پایگاه داده و کاربر اولیه استفاده می‌کند. ایمیج wordpress هم از آن‌ها برای دانستن نحوه اتصال به پایگاه داده خود استفاده می‌کند.
  • volumes (در سطح سرویس):
    • برای سرویس db، مقدار db_data:/var/lib/mysql یک والیوم مدیریت‌شده توسط Docker با نام db_data را به دایرکتوری داده MySQL داخل کانتینر نگاشت می‌کند. این کار داده‌های شما را امن نگه می‌دارد.
    • برای سرویس wordpress، مقدار ./wp_content:/var/www/html/wp-content یک دایرکتوری محلی (wp_content) را به دایرکتوری محتوای وردپرس نگاشت می‌کند. این موضوع به شما اجازه می‌دهد قالب‌ها و افزونه‌ها را مستقیماً از ماشین میزبان ویرایش کنید.
  • depends_on: به Compose می‌گوید سرویس db را قبل از سرویس wordpress راه‌اندازی کند. این موضوع مهم است، چرا که وردپرس در صورت پیدا نکردن پایگاه داده خود هنگام راه‌اندازی، شکست می‌خورد.
  • restart: unless-stopped: به Docker می‌گوید در صورت خروج غیرمنتظره کانتینر (مثلاً بعد از کرش یا ریبوت سرور) آن را به‌صورت خودکار ری‌استارت کند، اما نه در صورتی که خودتان آن را با docker compose stop یا docker compose down متوقف کرده باشید.
  • volumes (در سطح بالا): این بلوک والیوم‌های نام‌دار را تعریف می‌کند. مقدار db_data: یک والیوم مدیریت‌شده توسط Docker می‌سازد که روش پیشنهادی برای مدیریت داده‌های ماندگار (Persistent) است.

حالا این اپلیکیشن چندسرویسی را بالا بیاورید:

docker compose up -d

Compose هر دو ایمیج mysql و wordpress را دریافت (Pull) کرده و سپس کانتینرها را می‌سازد و ابتدا سرویس db را راه‌اندازی می‌کند.

خروجی:

Creating network "compose-demo_default" with the default driver
Creating volume "compose-demo_db_data" with default driver
Pulling db (mysql:8.0)...
...
Pulling wordpress (wordpress:latest)...
...
Creating mysql_db        ... done
Creating wordpress_app ... done

حالا می‌توانید با مراجعه به localhost:8001 یا your_server_domain_or_IP:8001 در مرورگر، به سایت وردپرس جدید خود دسترسی پیدا کنید. باید صفحه نصب وردپرس را ببینید. برای مثال مفصل‌تر، مقاله «آموزش نصب وردپرس با Docker Compose» را ببینید.

مرحله ۶ — استفاده از Docker Compose برای توسعه AI/ML

فراتر از اپلیکیشن‌های وب، Docker Compose ابزاری فوق‌العاده کاربردی برای ساخت محیط‌های علم داده و یادگیری ماشین (AI/ML) قابل‌تکرار است. پروژه‌های AI/ML به دلیل وابستگی‌های پیچیده‌شان معروفند؛ از جمله نسخه‌های خاص Python، کتابخانه‌هایی مانند TensorFlow یا PyTorch و درایورهای سطح سیستمی مانند NVIDIA CUDA Toolkit. Docker Compose تمام این محیط را در فایل‌های پیکربندی ثبت می‌کند و مشکل «روی سیستم من کار می‌کنه» را حل می‌کند؛ مسئله‌ای که برای پژوهش‌های قابل‌تکرار حیاتی است.

در این مثال، یک محیط AI/ML چندسرویسی شامل موارد زیر می‌سازید:

  • JupyterLab: کانتینری برای توسعه تعاملی که از یک Dockerfile سفارشی ساخته می‌شود.
  • PostgreSQL: کانتینر پایگاه داده برای ذخیره نتایج آزمایش‌ها و متادیتا.
  • دسترسی GPU: کانتینر JupyterLab طوری پیکربندی می‌شود که به GPU انویدیا ماشین میزبان دسترسی داشته باشد؛ نیازی که برای آموزش مدل‌ها رایج است.

پیش‌نیاز: این مثال به یک GPU انویدیا روی ماشین میزبان و نصب NVIDIA Container Toolkit نیاز دارد. بدون آن، کانتینر هنگام درخواست منابع GPU با خطا مواجه می‌شود.

ساختار فایل‌ها

ابتدا یک دایرکتوری برای پروژه خود بسازید. داخل آن، یک فایل docker-compose.yml، یک دایرکتوری jupyter، یک Dockerfile برای Jupyter و یک فایل requirements.txt می‌سازید. ساختار باید چیزی شبیه این باشد:

ai-project/
├── docker-compose.yml
└── jupyter/
    ├── Dockerfile
    └── requirements.txt

ساخت فایل‌های اجزا

۱. فایل requirements.txt

این فایل پکیج‌های Python محیط علم داده شما را فهرست می‌کند.

nano jupyter/requirements.txt

پکیج‌های موردنیاز خود را اضافه کنید. برای این مثال، کتابخانه‌هایی برای پردازش داده، اتصال به پایگاه داده و یک فریم‌ورک یادگیری عمیق را اضافه می‌کنیم.

jupyter/requirements.txt

pandas
scikit-learn
tensorflow
jupyterlab
psycopg2-binary

فایل را ذخیره کرده و ببندید.

۲. فایل Dockerfile

این فایل سرویس سفارشی JupyterLab شما را تعریف می‌کند. این فایل از ایمیج رسمی Jupyter با نام scipy-notebook (میزبان‌شده در Quay.io) به‌عنوان پایه استفاده کرده و پکیج‌های requirements.txt را نصب می‌کند.

nano jupyter/Dockerfile

محتوای زیر را جای‌گذاری کنید:

jupyter/Dockerfile

# شروع از یک ایمیج پایه که Jupyter و کتابخانه‌های علمی را در بر دارد
FROM quay.cr.parmincloud.ir/jupyter/scipy-notebook:latest

# کپی فایل requirements محلی به داخل کانتینر
COPY requirements.txt /tmp/requirements.txt

# نصب پکیج‌های Python
RUN pip install --no-cache-dir -i https://pypi.parmincloud.ir/simple/ -r /tmp/requirements.txt

این فایل به Docker دستور می‌دهد که از ایمیج scipy-notebook به‌عنوان نقطه شروع استفاده کند، فایل requirements.txt شما را داخل کانتینر کپی کند و سپس پکیج‌های Python را با pip نصب کند.

تعریف محیط با docker-compose.yml

حالا فایل اصلی docker-compose.yml را بسازید. این فایل هم سرویس db و هم سرویس سفارشی jupyter شما را ارکستراسیون می‌کند.

nano docker-compose.yml

پیکربندی زیر را جای‌گذاری کرده و جایگزین‌های <...> را با اطلاعات امن خودتان عوض کنید.

docker-compose.yml

services:
  db:
    image: hub.cr.parmincloud.ir/library/postgres:15-alpine
    container_name: ai_postgres_db
    environment:
      POSTGRES_USER: <your_db_user>
      POSTGRES_PASSWORD: <your_db_pass>
      POSTGRES_DB: experiments
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U <your_db_user> -d experiments"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped
    networks:
      - ai_net

  jupyter:
    build: ./jupyter
    container_name: ai_jupyter_lab
    ports:
      - "8888:8888"
    volumes:
      - ./notebooks:/home/jovyan/work
    environment:
      - POSTGRES_HOST=db
      - POSTGRES_USER=<your_db_user>
      - POSTGRES_PASSWORD=<your_db_pass>
      - POSTGRES_DB=experiments
    restart: unless-stopped
    networks:
      - ai_net
    depends_on:
      db:
        condition: service_healthy
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

volumes:
  postgres_data:
    name: ai_project_data

networks:
  ai_net:
    driver: bridge

این فایل پیکربندی چندین مفهوم مهم را معرفی می‌کند:

  • build: ./jupyter: به Docker Compose می‌گوید برای سرویس jupyter یک ایمیج سفارشی بسازد. این دستور به‌دنبال یک Dockerfile داخل دایرکتوری مشخص‌شده یعنی ./jupyter می‌گردد.
  • volumes: – ./notebooks:/home/jovyan/work: یک دایرکتوری محلی با نام notebooks را داخل کانتینر بایند ماونت (Bind Mount) می‌کند. این کار تضمین می‌کند همه نوت‌بوک‌های Jupyter که می‌سازید، مستقیماً روی ماشین میزبان ذخیره شوند و حتی بعد از حذف کانتینر باقی بمانند.
  • healthcheck: همان‌طور که در بخش عیب‌یابی خواهید دید، این بررسی تأیید می‌کند که سرویس db فقط در حال اجرا نیست، بلکه پیش از شروع سرویس‌های دیگر برای پذیرش اتصال‌ها آماده است.
  • depends_on: … condition: service_healthy: به سرویس jupyter می‌گوید تا زمان موفقیت healthcheck سرویس db صبر کرده و سپس شروع شود.
  • deploy.resources.reservations.devices: این بلوک است که دسترسی GPU را درخواست می‌کند.
    • driver: nvidia: درایور میزبانِ مورد استفاده را مشخص می‌کند.
    • count: 1: یک GPU درخواست می‌کند.
    • capabilities: [gpu]: تضمین می‌کند کانتینر قابلیت‌های لازم برای استفاده از GPU را داشته باشد.

اجرای محیط AI/ML

حالا که فایل‌های خود را در جای خود دارید، آماده ساخت و اجرای سرویس‌ها هستید.

از دایرکتوری ai-project خود، دستور docker compose up را اجرا کنید. بار اول باید فلگ –build را اضافه کنید تا به Compose بگویید ایمیج سفارشی jupyter شما را بسازد.

docker compose up -d --build

Compose ابتدا ایمیج jupyter را می‌سازد (که ممکن است چند دقیقه طول بکشد؛ چون TensorFlow را دانلود می‌کند)، سپس ایمیج postgres را دریافت کرده و در نهایت هر دو کانتینر را راه‌اندازی می‌کند.

خروجی:

[+] Building 5.8s (9/9) FINISHED
 => [internal] load build definition from Dockerfile
...
[+] Running 3/3
 ✔ Network ai-project_ai_net        Created
 ✔ Container ai_postgres_db         Started
 ✔ Container ai_jupyter_lab         Started

حالا می‌توانید با مراجعه به http://localhost:8888 (یا your_server_ip:8888) در مرورگر، به رابط JupyterLab دسترسی پیدا کنید. از شما یک توکن خواسته می‌شود که می‌توانید آن را از لاگ‌های کانتینر بگیرید:

docker compose logs jupyter

به‌دنبال خطی مشابه http://127.0.0.1:8888/lab?token=a1b2c3d4e5f6… بگردید.

داخل یک نوت‌بوک Jupyter، حالا می‌توانید با استفاده از نام هاست db و اطلاعاتی که ارائه کرده‌اید، به پایگاه داده خود متصل شوید. محیط شما همچنین برای آموزش مدل‌ها به GPU میزبان دسترسی دارد.

مقیاس‌دهی سرویس‌ها و استفاده از شبکه‌های سفارشی

Docker Compose شامل قابلیت‌هایی برای مقیاس‌دهی سرویس‌ها و مدیریت شبکه‌هایی است که سرویس‌ها روی آن‌ها با هم ارتباط برقرار می‌کنند.

مقیاس‌دهی سرویس با رپلیکا

تصور کنید وب‌سرور Nginx شما از مرحله ۲ ترافیک زیادی دریافت می‌کند. می‌توانید سرویس web را مقیاس دهید تا چندین نمونه کانتینر اجرا شود. Docker Compose می‌تواند این کار را به‌صورت خودکار مدیریت کند.

دو روش اصلی برای مقیاس‌دهی یک سرویس وجود دارد.

۱. استفاده از کلید replicas (روش ترجیحی در v2)

می‌توانید تعداد دلخواه نمونه‌ها را مستقیماً در فایل docker-compose.yml خود با کلیدهای deploy و replicas تعریف کنید. این قابلیت در اصل بخشی از Docker Swarm بود، اما اکنون برای استقرارهای استاندارد Compose هم در دسترس است.

فایل docker-compose.yml مربوط به Nginx خود را از مرحله ۲ به این شکل تغییر دهید:

docker-compose.yml

services:
  web:
    image: hub.cr.parmincloud.ir/library/nginx:alpine
    ports:
      - "8000:80"
    volumes:
      - ./app:/usr/share/nginx/html
    deploy:
      replicas: 3

وقتی docker compose up -d را اجرا کنید، Compose سه کانتینر web می‌سازد. اما یک مشکل خواهید داشت: هر سه کانتینر سعی می‌کنند به پورت 8000 میزبان متصل (Bind) شوند. این موضوع باعث بروز خطای «port is already allocated» برای کانتینرهای دوم و سوم می‌شود. برای رفع این مشکل در یک راه‌اندازی production، باید نگاشت ports را از سرویس web حذف کنید. به این ترتیب، کانتینرهای web فقط داخل شبکه Docker در دسترس خواهند بود. یک سرویس لود بالانسر جداگانه، تنها سرویسی خواهد بود که پورت عمومی دارد و سپس ترافیک را بین سه رپلیکای web توزیع می‌کند.

یک نمونه پیکربندی به این شکل خواهد بود (این یک مثال پیشرفته است):

docker-compose.yml — حالت درست برای replicas

services:
  web:
    image: hub.cr.parmincloud.ir/library/nginx:alpine
    # اینجا نگاشت 'ports' وجود ندارد.
    # سرویس فقط داخل شبکه 'web-net' در دسترس است.
    volumes:
      - ./app:/usr/share/nginx/html
    deploy:
      replicas: 3
    networks:
      - web-net

  load_balancer:
    image: hub.cr.parmincloud.ir/library/nginx:latest
    ports:
      - "80:80" # لود بالانسر تنها سرویسی است که پورت عمومی دارد.
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf # یک فایل پیکربندی که بار را بین سرویس‌های 'web' توزیع می‌کند.
    networks:
      - web-net
    depends_on:
      - web

networks:
  web-net:

در این راه‌اندازی، load_balancer روی پورت 80 گوش می‌دهد و درخواست‌ها را به‌صورت داخلی به web_1، web_2 و web_3 هدایت می‌کند.

کلید replicas بیشترین کاربرد را زمانی دارد که با یک پروکسی معکوس (Reverse Proxy) مانند Traefik یا یک نمونه دیگر از Nginx استفاده شود که می‌تواند درخواست‌ها را بین رپلیکاها داخل شبکه Docker متوازن کند؛ بدون آنکه هر رپلیکا نیاز به در اختیار گذاشتن پورتی روی میزبان داشته باشد.

۲. استفاده از فلگ –scale

یک روش رایج دیگر برای مقیاس‌دهی، فلگ –scale است؛ میراثی از Compose v1. این فلگ در زمان اجرا اعمال شده و هر کلید replicas را بازنویسی (Override) می‌کند.

با این حال، باید با تعریف پورت‌ها دقت کنید. اگر سرویس شما یک نگاشت پورت ثابت میزبان تعریف کرده باشد (مثلاً “8000:80”)، اجرای docker compose up –scale برای کانتینرهای دوم و سوم خطا ایجاد می‌کند؛ چون همه سعی می‌کنند به همان پورت میزبان متصل شوند.

برای استفاده از –scale برای سرویس‌هایی که پورت دارند، نباید از نگاشت پورت ثابت میزبان استفاده کنید.

گزینه ۱: نگاشت به پورت‌های تصادفی میزبان (مناسب برای توسعه)

می‌توانید فایل docker-compose.yml خود را طوری تغییر دهید که فقط پورت کانتینر را مشخص کند. این کار به Docker می‌گوید پورت 80 هر کانتینر را به یک پورت تصادفی و آزاد روی ماشین میزبان شما نگاشت کند.

docker-compose.yml — حالت درست برای –scale

services:
  web:
    image: hub.cr.parmincloud.ir/library/nginx:alpine
    ports:
      - "80" # بدون پورت ثابت میزبان
    volumes:
      - ./app:/usr/share/nginx/html

حالا وقتی دستور مقیاس‌دهی را اجرا کنید:

docker compose up -d --scale web=3

برای دیدن نتیجه، docker compose ps را اجرا کنید. هر کانتینر روی یک پورت میزبانِ متفاوت و تصادفیِ اختصاص‌یافته در حال اجرا خواهد بود:

خروجی:

      Name                     Command               State          Ports        
----------------------------------------------------------------------------------
compose-demo_web_1   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:49154->80/tcp
compose-demo-web_2   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:49155->80/tcp
compose-demo-web_3   /docker-entrypoint.sh ngin ...   Up      0.0.0.0:49156->80/tcp

گزینه ۲: استفاده از پروکسی معکوس (مناسب برای production)

راه‌حل دیگر، همان‌طور که در بخش replicas اشاره شد، حذف کامل دستور ports از سرویس web است. سپس از یک کانتینر لود بالانسر جداگانه (که تنها پورت عمومی را دارد) برای مدیریت و توزیع ترافیک بین رپلیکاهای مقیاس‌شده داخل شبکه Docker استفاده می‌کنید.

برای توقف و حذف هر سه کانتینر، دستور همان دستور قبلی باقی می‌ماند:

docker compose down

پیکربندی شبکه سفارشی

به‌صورت پیش‌فرض، Docker Compose برای اپلیکیشن شما یک شبکه bridge واحد می‌سازد. هر سرویس موجود در فایل به آن متصل است؛ و دقیقاً به همین دلیل بود که کانتینر wordpress توانست کانتینر db را فقط با استفاده از نام سرویس آن (db) پیدا کند.

با این حال، می‌توانید برای ایزولاسیون و کنترل بهتر، شبکه‌های سفارشی خودتان را تعریف کنید.

بیایید مثال وردپرس را طوری تغییر دهیم که از یک شبکه bridge سفارشی استفاده کند.

docker-compose.yml

services:
  db:
    image: hub.cr.parmincloud.ir/library/mysql:8.0
    ...
    networks:
      - app_net

  wordpress:
    image: hub.cr.parmincloud.ir/library/wordpress:latest
    ...
    ports:
      - "8001:80"
    depends_on:
      - db
    networks:
      - app_net

volumes:
  db_data:

networks:
  app_net:
    driver: bridge

آنچه اضافه کردیم:

  • networks (در سطح بالا): شبکه جدیدی با نام app_net تعریف می‌کند و مشخص می‌کند که از درایور استاندارد bridge استفاده شود.
  • networks (در سطح سرویس): زیر هر دو سرویس db و wordpress، این کلید آن‌ها را به شبکه app_net ما متصل می‌کند.

اگر سرویس سومی داشتید (مثلاً یک ابزار تحلیل) که آن را به app_net متصل نمی‌کردید، آن سرویس کاملاً ایزوله بود و قادر به ارتباط با کانتینرهای db یا wordpress نبود.

برای کلاسترینگ چندمیزبانی با Docker Swarm، درایور را از bridge به overlay تغییر می‌دهید. درایور bridge برای ارتباط بین کانتینرها روی یک میزبان واحد است که حالت استاندارد بیشتر موارد استفاده از Docker Compose به شمار می‌رود.

استفاده از فایل‌های پیکربندی ماژولار

با رشد اپلیکیشن‌های شما، فایل docker-compose.yml می‌تواند بزرگ و دشوار برای مدیریت شود. Docker Compose از دستور include پشتیبانی می‌کند که به شما اجازه می‌دهد پیکربندی خود را بین چندین فایل تقسیم کنید.

تصور کنید می‌خواهید تعریف‌های وردپرس و پایگاه داده خود را از هم جدا کنید و شاید هم یک docker-compose.override.yml مشترک برای تنظیمات مخصوص توسعه داشته باشید (مثل بایند کردن یک پورت روی پایگاه داده).

ساختار دایرکتوری شما ممکن است به این شکل باشد:

compose-demo/
├── docker-compose.yml
├── docker-compose.db.yml
├── docker-compose.web.yml

این فایل فقط سرویس db را تعریف می‌کند.

docker-compose.db.yml

services:
  db:
    image: hub.cr.parmincloud.ir/library/mysql:8.0
    container_name: mysql_db
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: <your_root_password_here>
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress_user
      MYSQL_PASSWORD: <your_wordpress_password_here>
    restart: unless-stopped

volumes:
  db_data:

این فایل فقط سرویس wordpress را تعریف می‌کند.

docker-compose.web.yml

services:
  wordpress:
    image: hub.cr.parmincloud.ir/library/wordpress:latest
    container_name: wordpress_app
    ports:
      - "8001:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress_user
      WORDPRESS_DB_PASSWORD: <your_wordpress_password_here>
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - ./wp_content:/var/www/html/wp-content
    depends_on:
      - db
    restart: unless-stopped

حالا فایل اصلی docker-compose.yml شما بسیار ساده می‌شود. این فایل فقط از include برای فراخوانی فایل‌های دیگر استفاده می‌کند.

docker-compose.yml

include:
  - docker-compose.db.yml
  - docker-compose.web.yml

وقتی docker compose up -d را اجرا کنید، Compose هر سه فایل را می‌خواند، پیکربندی‌ها را ادغام (Merge) کرده و سرویس‌های db و wordpress را دقیقاً مثل زمانی که در یک فایل واحد تعریف شده بودند، راه‌اندازی می‌کند. این رویکرد، پیکربندی شما را بسیار ماژولارتر و قابل‌استفاده مجدد می‌کند.

توجه: دستور include: به Docker Compose نسخه 2.20 یا جدیدتر نیاز دارد. اگر سیستم شما از نسخه قدیمی‌تری استفاده می‌کند، می‌توانید فایل‌ها را با دستور docker compose -f docker-compose.db.yml -f docker-compose.web.yml up ترکیب کنید.

مهاجرت از docker-compose (v1) به docker compose (v2)

دستور docker compose که در مرحله ۱ نصب کردید، «Compose v2» است؛ یک پلاگین مبتنی بر Go که مستقیماً در Docker CLI ادغام شده است.

نسخه اصلی یعنی «Compose v1»، یک ابزار مجزای Python بود که با docker-compose (با خط تیره) فراخوانی می‌شد. از جولای 2023، Compose v1 دیگر پشتیبانی نمی‌شود و منسوخ شده است.

تغییرات کلیدی و سازگاری به عقب

  • دستور: مشخص‌ترین تغییر، خود دستور است. خط تیره (docker-compose) حالا به یک فاصله (docker compose) تبدیل شده است.
  • نصب: v1 با pip نصب می‌شد. v2 همراه Docker Desktop عرضه می‌شود یا مثل کاری که در مرحله ۱ انجام دادید، به‌عنوان پلاگین CLI نصب می‌شود.
  • فایل‌های YAML: فایل‌های docker-compose.yml شما تقریباً به‌طور ۱۰۰٪ با نسخه قبل سازگارند. Compose v2 به‌طور کامل از نسخه‌های فایل 3.x پشتیبانی می‌کند. برای مهاجرت نیازی به تغییر فایل‌های YAML خود ندارید؛ فقط دستورات را تغییر دهید.
  • فلگ –project-name: فلگ -p همچنان کار می‌کند، اما فلگ کامل آن حالا –project-name است، نه –project_name.

راهنمای مهاجرت دستورات

بیشتر دستورات یکسان‌اند؛ فقط خط تیره حذف شده است. این جدول دستورات رایج v1 را با معادل‌های v2 آن‌ها مقایسه می‌کند:

دستور docker-compose (v1)دستور docker compose (v2)نکات
docker-compose up -ddocker compose up -dبدون تغییر در سینتکس
docker-compose downdocker compose downبدون تغییر در سینتکس
docker-compose psdocker compose psبدون تغییر در سینتکس
docker-compose logsdocker compose logsبدون تغییر در سینتکس
docker-compose stopdocker compose stopبدون تغییر در سینتکس
docker-compose builddocker compose buildبدون تغییر در سینتکس
docker-compose exec web bashdocker compose exec web bashبدون تغییر در سینتکس
docker-compose run web bashdocker compose run web bashدستور run یک کانتینر جدید می‌سازد؛ exec داخل یک کانتینر موجود اجرا می‌شود
docker-compose up –scale web=3docker compose up –scale web=3فلگ –scale همچنان در v2 پشتیبانی می‌شود

همان‌طور که می‌بینید، برای بیشتر کارهای روزمره، تنها تغییر docker-compose → docker compose است. اگر از اسکریپت استفاده می‌کنید، می‌توانید آن‌ها را با حذف ساده خط تیره به‌روزرسانی کنید.

اگر هنوز docker-compose قدیمی v1 را نصب دارید، می‌توانید برای جلوگیری از سردرگمی آن را حذف کنید:

sudo pip3 uninstall docker-compose

یا اگر با apt نصب شده بود:

sudo apt remove docker-compose

عیب‌یابی مشکلات رایج Docker Compose

هنگام کار با Docker Compose ممکن است با مشکلاتی مربوط به سینتکس فایل، دسترسی‌ها یا تداخل‌های زمان اجرای کانتینر روبرو شوید. بیشتر مشکلات را می‌توان با بررسی متدوالانه پیکربندی، دسترسی‌ها و لاگ‌های کانتینر تشخیص و رفع کرد.

بیایید به برخی از رایج‌ترین خطاها و راه‌حل‌های آن‌ها نگاه کنیم.

Permission Denied (سوکت Docker)

این یکی از رایج‌ترین خطاها برای کاربران جدید Docker است. دستور docker compose up را اجرا می‌کنید و پیام خطایی درباره سوکت دیمن Docker می‌بینید.

خروجی:

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock

مشکل: کاربر غیر root شما دسترسی لازم برای ارتباط با دیمن Docker (که با دسترسی root اجرا می‌شود) را ندارد.

راه‌حل: باید کاربر خود را به گروه docker اضافه کنید؛ گروهی که هنگام نصب Docker ایجاد شده است.

کاربر خود را به گروه docker اضافه کنید:

sudo usermod -aG docker ${USER}

برای اعمال عضویت جدید در گروه، باید از سیستم خارج شده و دوباره وارد شوید.

همچنین می‌توانید تغییرات گروه را برای نشست فعلی ترمینال با این دستور فعال کنید:

newgrp docker

این کار باید هرگونه خطای دسترسی مربوط به سوکت Docker را رفع کند.

خطاهای سینتکس YAML و تورفتگی

Docker Compose اجرا نمی‌شود و گزارش می‌دهد که فایل docker-compose.yml شما نامعتبر است.

خروجی:

ERROR: The Compose file './docker-compose.yml' is invalid because:
services.web.ports contains an invalid type, it should be a list

یا:

mapping values are not allowed in this context at line 5

مشکل: فایل docker-compose.yml به سینتکس سخت‌گیرانه YAML متکی است. رایج‌ترین اشتباه، تورفتگی نادرست است. YAML برای تعریف ساختار از فاصله (Space) استفاده می‌کند، نه Tab.

راه‌حل: تورفتگی فایل docker-compose.yml خود را با دقت بررسی کنید.

  • استاندارد، دو فاصله برای هر سطح تورفتگی است.
  • مطمئن شوید همه کلیدهای هم‌سطح (مثل version، services و volumes) در یک ستون شروع می‌شوند.
  • مطمئن شوید همه زیرکلیدها (مثل image، ports و volumes زیر یک سرویس) دو فاصله بیشتر از سرویس والد خود (مثلاً web) تورفتگی دارند.
  • از یک ویرایشگر کد با YAML Linter برای هایلایت خودکار خطاهای سینتکس استفاده کنید.

تداخل پورت‌ها

سعی می‌کنید محیط خود را راه‌اندازی کنید، اما دستور با پیام خطای «آدرس در حال استفاده است» شکست می‌خورد.

خروجی:

Error starting userland proxy: listen tcp 0.0.0.0:8000: bind: address already in use

مشکل: یک فرایند دیگر روی ماشین میزبان شما از قبل روی پورتی که سعی دارید نگاشت کنید (در این مورد پورت 8000) در حال گوش دادن است. این فرایند اغلب یک کانتینر Docker دیگر یا یک سرور توسعه محلی است.

راه‌حل: دو گزینه دارید:

فرایند دیگر را متوقف کنید. می‌توانید فرایندِ در حال استفاده از پورت را با این دستور پیدا کنید:

sudo lsof -i :8000

اگر یک کانتینر Docker دیگر است، آن را با docker stop متوقف کنید.

پورت میزبان را در فایل docker-compose.yml خود تغییر دهید. این اغلب ساده‌ترین راه‌حل است. نگاشت ports را از “8000:80” به یک پورت متفاوت، مثلاً “8001:80” تغییر دهید.

docker-compose.yml

...
ports:
  - "8001:80" # تغییر از 8000
...

شکست سرویس با خطای Connection Refused (depends_on)

در یک اپلیکیشن چندسرویسی (مثل مثال وردپرس و MySQL)، کانتینر wordpress راه‌اندازی می‌شود، اما لاگ‌های آن هنگام تلاش برای اتصال به سرویس db خطای Connection refused یا MySQL server has gone away نشان می‌دهند.

مشکل: شما از depends_on استفاده کرده‌اید، اما این دستور فقط برای راه‌اندازی کانتینر db صبر می‌کند؛ نه برای مقداردهی اولیه کامل اپلیکیشن MySQL داخل کانتینر و آماده شدن آن برای پذیرش اتصال‌ها.

راه‌حل: اپلیکیشن (در این مورد وردپرس) باید برای تلاش مجدد اتصال پیکربندی شود. بیشتر ایمیج‌های مدرن این منطق تلاش مجدد را به‌صورت داخلی دارند. برای ایمیج‌هایی که ندارند، باید یک healthcheck پیاده‌سازی کنید.

می‌توانید به سرویس db خود یک healthcheck اضافه کنید. سپس depends_on سرویس wordpress را می‌توان طوری پیکربندی کرد که برای «سالم بودن» (Healthy) پایگاه داده صبر کند، نه فقط «راه‌اندازی شده بودن» آن.

نمونه healthcheck برای MySQL:

docker-compose.yml

services:
  db:
    image: hub.cr.parmincloud.ir/library/mysql:8.0
    ...
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p<your_root_password_here>"]
      interval: 10s
      timeout: 5s
      retries: 5

  wordpress:
    image: hub.cr.parmincloud.ir/library/wordpress:latest
    ...
    depends_on:
      db:
        condition: service_healthy # حالا برای موفقیت healthcheck صبر می‌کند
    ...

خطاهای والیوم‌ها و بایند ماونت‌ها

ممکن است در لاگ‌های کانتینر خود با خطاهای دسترسی روبرو شوید یا متوجه شوید دایرکتوری داده کانتینر خالی است.

دسترسی فایل/دایرکتوری

مشکل: کانتینر با یک کاربر مشخص اجرا می‌شود (مثلاً www-data با شناسه کاربری 33)، اما دایرکتوری میزبانی که ماونت کرده‌اید متعلق به کاربر شماست (مثلاً ubuntu با شناسه 1000). کاربر کانتینر دسترسی نوشتن در دایرکتوری میزبان را ندارد.

راه‌حل: مالکیت دایرکتوری میزبان را طوری تغییر دهید که با شناسه کاربری داخل کانتینر مطابقت داشته باشد. می‌توانید شناسه کاربر کانتینر را با اجرای docker compose exec id پیدا کنید. مثلاً اگر شناسه 33 باشد (رایج برای www-data):

sudo chown -R 33:33 ./wp_content

مشکلات مسیر نسبی

مشکل: فایل docker-compose.yml شما از یک مسیر نسبی مثل ./app استفاده می‌کند، اما فایل index.html شما سرو نمی‌شود.

راه‌حل: Docker Compose مسیرهای نسبی را نسبت به دایرکتوری‌ای که دستور docker compose up را در آن اجرا می‌کنید، تفسیر می‌کند. همیشه دستورات Compose را از همان دایرکتوری‌ای اجرا کنید که حاوی فایل docker-compose.yml است.

شبکه و کشف سرویس

لاگ‌های کانتینن اپلیکیشن شما خطای Host not found یا Could not resolve host: db را نشان می‌دهند.

مشکل: کانتینر wordpress شما نمی‌تواند کانتینر db را روی شبکه پیدا کند.

راه‌حل: به‌صورت پیش‌فرض، Docker Compose یک شبکه می‌سازد و همه سرویس‌ها را به آن متصل می‌کند. سرویس‌ها می‌توانند با استفاده از نام سرویس خود، یکدیگر را پیدا کنند.

  • بررسی نام سرویس‌ها: مطمئن شوید نام هاست در پیکربندی اپلیکیشن شما (مثلاً متغیر محیطی WORDPRESS_DB_HOST) دقیقاً همان نام سرویس در فایل docker-compose.yml شماست (مثلاً db).
  • بررسی شبکه: با docker compose ps نام پروژه خود را پیدا کنید (مثلاً compose-demo). سپس شبکه پیش‌فرض را بررسی کنید:
docker network inspect compose-demo_default

خروجی JSON همه کانتینرهای متصل به این شبکه را فهرست می‌کند. اگر یکی از سرویس‌های شما در آن غایب است، فایل docker-compose.yml را از نظر هرگونه پیکربندی شبکه سفارشی که ممکن است آن را ایزوله کرده باشد، بررسی کنید.

عدم تطابق دستور (v1 در برابر v2)

docker-compose up (با خط تیره) را تایپ می‌کنید و خطای «command not found» می‌بینید.

خروجی:

docker-compose: command not found

مشکل: در حال استفاده از دستور مربوط به Compose v1 هستید؛ ابزار Python منسوخ‌شده. این راهنما Compose v2 را نصب می‌کند که پلاگینی است مستقیماً در Docker CLI ادغام شده است.

راه‌حل: از دستور درست با جایگزینی خط تیره (-) با فاصله استفاده کنید.

docker-compose (v1)docker compose (v2)
docker-compose updocker compose up
docker-compose downdocker compose down
docker-compose psdocker compose ps
docker-compose logsdocker compose logs

هنگام دنبال کردن این راهنما، همیشه از docker compose (با فاصله) استفاده کنید.

سوالات متداول

۱. Docker Compose برای چه استفاده می‌شود؟

Docker Compose ابزاری برای تعریف و اجرای اپلیکیشن‌های چندکانتینری Docker است.

این ابزار از یک فایل YAML واحد (به‌صورت پیش‌فرض docker-compose.yml) برای پیکربندی همه اجزای اپلیکیشن شما استفاده می‌کند که به آن‌ها سرویس گفته می‌شود. این فایل همچنین شبکه‌هایی را که به سرویس‌ها اجازه ارتباط با یکدیگر می‌دهند و والیوم‌های مورد استفاده برای داده‌های ماندگار را تعریف می‌کند.

با این فایل واحد، می‌توانید کل استک اپلیکیشن خود را با دستورات ساده مدیریت کنید:

  • docker compose up کل اپلیکیشن شما را راه‌اندازی و اجرا می‌کند؛ شامل همه کانتینرها، شبکه‌ها و والیوم‌های مشخص‌شده.
  • docker compose down همه کانتینرها، شبکه‌ها و والیوم‌هایی را که اپلیکیشن شما ایجاد کرده است، متوقف و حذف می‌کند.

این ابزار بیشترین کاربرد را برای مدیریت اپلیکیشن‌هایی دارد که به چند جزء نیاز دارند؛ مانند وب‌سایتی که به وب‌سرور (مثل Nginx)، بک‌اند اپلیکیشن (مثل وردپرس) و پایگاه داده (مثل MySQL) نیاز دارد.

۲. چطور Docker Compose را روی اوبونتو نصب کنم؟

روش توصیه‌شده، نصب Docker Compose به‌عنوان پلاگینی برای Docker CLI است. این کار با نصب پکیج docker-compose-plugin از مخزن apt مربوط به Docker انجام می‌شود.

ابتدا مطمئن شوید مخزن apt داکر را طبق مرحله ۱ این مقاله روی سیستم اوبونتوی خود راه‌اندازی کرده‌اید.

فهرست پکیج‌های خود را به‌روز کنید:

sudo apt update

پلاگین Docker Compose را نصب کنید:

sudo apt install docker-compose-plugin

نصب را با بررسی نسخه تأیید کنید. این دستور از فاصله استفاده می‌کند، نه خط تیره:

docker compose version

۳. چطور Docker Compose را به آخرین نسخه به‌روزرسانی کنم؟

از آنجا که Docker Compose (v2) به‌عنوان یک پکیج سیستمی با apt نصب می‌شود، می‌توانید آن را با فرایند استاندارد به‌روزرسانی نرم‌افزار اوبونتو ارتقا دهید.

ایندکس پکیج‌های محلی خود را رفرش کنید:

sudo apt update

یک ارتقای سراسری سیستم اجرا کنید که پلاگین Compose را هم در بر می‌گیرد:

sudo apt upgrade

اگر فقط می‌خواهید خود پلاگین را به‌روز کنید، می‌توانید این دستور را اجرا کنید:

sudo apt install --only-upgrade docker-compose-plugin

۴. آیا Docker Compose را می‌توان در محیط‌های production استفاده کرد؟

بله، Docker Compose به‌طور مکرر در production استفاده می‌شود؛ به‌خصوص برای اپلیکیشن‌هایی که روی یک میزبان واحد اجرا می‌شوند.

این ابزار راهی سرراست برای تعریف، استقرار و مدیریت چرخه حیات سرویس‌ها، شبکه‌ها و والیوم‌های اپلیکیشن شما فراهم می‌کند. برای استقرار تک‌سروره، راه‌حلی بسیار مؤثر و ساده برای مدیریت به شمار می‌رود.

برای سناریوهای پیچیده‌تری که نیاز به هماهنگی کانتینرها بین چندین میزبان (کلاستر) دارند، ابزارهای دیگری رایج‌ترند. این ابزارها شامل Docker Swarm (که از سینتکس فایل Compose مشابهی استفاده می‌کند) و Kubernetes (استاندارد صنعت برای ارکستراسیون کانتینر در مقیاس بزرگ) می‌شوند.

۵. تفاوت Docker و Docker Compose چیست؟

این جدول تفاوت‌های اصلی بین Docker CLI و Docker Compose CLI را نشان می‌دهد:

ویژگیdocker (Docker Engine)docker compose (پلاگین Compose)
دامنهمدیریت تک‌تک اشیای Dockerمدیریت یک اپلیکیشن چندکانتینری کامل به‌عنوان یک واحد واحد
کاربرد اصلیساخت ایمیج‌ها (docker build)، اجرای کانتینرهای منفرد (docker run)، مدیریت کانتینرها (docker ps)، ایمیج‌ها، والیوم‌ها و شبکه‌هاارکستراسیون چندین سرویس تعریف‌شده در فایل docker-compose.yml
دستوراتدستورات سطح پایین متمرکز بر یک شیء. مثال: docker run -d -p 80:80 -v ./:/app nginxدستورات سطح بالا برای کل اپلیکیشن. مثال: docker compose up
تمثیلیک بلوک ساختمانی (یک کانتینر منفرد)نقشه ساختمان و مدیر ساختِ کل ساختمان (اپلیکیشن)

خلاصه اینکه از docker برای تعامل با یک کانتینر منفرد استفاده می‌کنید و از docker compose برای مدیریت کل استک اپلیکیشن خود (مثلاً web، app و db) به‌صورت هم‌زمان.

۶. چطور با Docker Compose چندین کانتینر اجرا کنم؟

اجرای چندین کانتینر با تعریف آن‌ها به‌عنوان سرویس در فایل docker-compose.yml انجام می‌شود.

فایلی با نام docker-compose.yml بسازید.

داخل این فایل، از کلید services: برای تعریف هر کانتینری که می‌خواهید اجرا شود استفاده کنید.

اینجا یک مثال عملی آورده شده که دو کانتینر را تعریف و اجرا می‌کند: یک سایت وردپرس و یک پایگاه داده MySQL.

docker-compose.yml

services:
  db:
    # این اولین سرویس (کانتینر) است
    image: hub.cr.parmincloud.ir/library/mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: <your_password_here>
      MYSQL_DATABASE: wordpress
    volumes:
      - db_data:/var/lib/mysql

  wordpress:
    # این دومین سرویس (کانتینر) است
    image: hub.cr.parmincloud.ir/library/wordpress:latest
    ports:
      - "8001:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_PASSWORD: <your_password_here>
    depends_on:
      - db

volumes:
  db_data:

فایل را ذخیره کرده و در همان دایرکتوری یک ترمینال باز کنید.

با یک دستور، هر دو کانتینر را راه‌اندازی کنید:

docker compose up -d

Docker Compose فایل را می‌خواند، یک شبکه اشتراکی برای سرویس‌ها می‌سازد، هر دو ایمیج mysql و wordpress را دریافت کرده و برای هر سرویس یک کانتینر راه‌اندازی می‌کند.

۷. چرا از Docker Compose برای توسعه استفاده کنیم؟

Docker Compose ابزاری استاندارد برای محیط‌های توسعه است؛ چرا که چندین مشکل رایج را حل می‌کند.

  • محیط‌های یکسان: تضمین می‌کند هر توسعه‌دهنده در یک تیم، دقیقاً همان سرویس‌ها (پایگاه داده، کش، وب‌سرور) را با همان نسخه‌ها و پیکربندی‌ها اجرا کند. این موضوع در فایل docker-compose.yml تعریف می‌شود که در کنترل نسخه (Version Control) ثبت می‌شود. مشکل «روی سیستم من کار می‌کنه» را از بین می‌برد.
  • سادگی: جایگزین اسکریپت‌های پیچیده راه‌اندازی و دستورات طولانی docker run می‌شود. یک توسعه‌دهنده فقط باید docker compose up را برای شروع کل استک اپلیکیشن و docker compose down را برای توقف آن اجرا کند.
  • ایزولاسیون سرویس: توسعه‌دهندگان می‌توانند روی یک ماشین، روی چندین پروژه بدون تداخل وابستگی‌ها کار کنند. پروژه A می‌تواند از PostgreSQL 9.6 و پروژه B از PostgreSQL 14 استفاده کند؛ چون هر پایگاه داده در یک کانتینر ایزوله و مدیریت‌شده توسط فایل Compose خودش اجرا می‌شود.
  • تست یکپارچگی آسان: از آنجا که Compose همه وابستگی‌های یک اپلیکیشن را با هم راه‌اندازی می‌کند، یک محیط محلی عالی برای اجرای تست‌های یکپارچگی (Integration Test) که نحوه تعامل سرویس‌ها با یکدیگر را بررسی می‌کنند، ایجاد می‌کند.

۸. آیا لازم است فیلد version: را در فایل docker-compose.yml خود قرار دهم؟

خیر. فیلد version در سطح بالا (مثلاً version: “3.8”) دیگر لازم نیست و Docker Compose v2 آن را نادیده می‌گیرد. بیشتر پروژه‌های مدرن این فیلد را حذف می‌کنند و همان‌طور که در مرحله ۲ دیدید، Compose به‌صورت خودکار نسخه پیکربندی را تشخیص می‌دهد. تنها زمانی که به سازگاری با ابزارهای قدیمی نیاز دارید، لازم است مقدار آن را مشخص کنید.

جمع‌بندی

در این مقاله، Docker Compose را روی اوبونتو نصب کردید، یک محیط کانتینری ساده با Nginx راه‌اندازی کردید و سپس یک اپلیکیشن چندسرویسی شامل وردپرس و MySQL ساختید. همچنین یک محیط توسعه AI/ML با JupyterLab، PostgreSQL و دسترسی GPU پیاده‌سازی کردید و با مفاهیم پیشرفته‌ای مانند مقیاس‌دهی سرویس‌ها، شبکه‌های سفارشی، فایل‌های پیکربندی ماژولار با دستور include و مهاجرت از نسخه v1 به v2 آشنا شدید. در پایان نیز راه‌حل مشکلات رایجی مانند خطاهای دسترسی، تداخل پورت‌ها و شکست راه‌اندازی سرویس‌ها در اپلیکیشن‌های چندسرویسی را یاد گرفتید. با Docker Compose می‌توانید کل استک اپلیکیشن خود را با یک فایل YAML و چند دستور ساده، تعریف، اجرا و مدیریت کنید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا