DOCKER

۱۲ اشتباه رایج در docker-compose.yml و راه درست نوشتن آن

پورت‌های باز روی اینترنت، تگ latest، نبود healthcheck، رمز داخل فایل و چند اشتباه دیگر در Compose تولیدی — با نمونه‌ی بد و خوب و یک فایل نهایی کامل.

یک فایل docker-compose.yml که «بالا می‌آید» لزوماً درست نیست. بیشتر مشکل‌ها وقتی خودشان را نشان می‌دهند که سرویس در تولید است: دیتابیسی که روی اینترنت باز مانده، ایمیجی که بی‌خبر عوض شده، یا سرویسی که قبل از آماده‌شدن دیتابیس بالا آمده و می‌میرد. این راهنما ۱۲ اشتباه را با نمونه‌ی بد و خوب نشان می‌دهد.

YMLDocker Compose Validatorفایل Compose خودتان را بچسبانید و ایرادهای ساختاری را به تفکیک خطا، هشدار و پیشنهاد ببینید.

۱ تا ۳: نسخه، پورت و رمز

۱. استفاده از تگ latest (یا نداشتن تگ)

با image: postgres هر docker pull می‌تواند نسخه‌ی اصلی جدیدی بیاورد و دیتابیس شما با فرمت داده‌ی ناسازگار بالا نیاید. نسخه را ثابت کنید؛ هرچه دقیق‌تر، بازتولیدپذیرتر.

# بد
image: postgres

# خوب
image: postgres:16.4-alpine

۲. منتشر کردن پورت دیتابیس روی همه‌ی اینترفیس‌ها

"5432:5432" پورت را روی 0.0.0.0 باز می‌کند، یعنی از بیرون سرور هم قابل‌دسترس است (و Docker معمولاً قوانین فایروال ufw را هم دور می‌زند). سرویس‌هایی که فقط کانتینرهای دیگر لازم دارند اصلاً نیازی به ports ندارند؛ آن‌ها در شبکه‌ی Compose با نام سرویس به هم می‌رسند.

# بد: دیتابیس برای کل اینترنت
ports:
  - "5432:5432"

# خوب (۱): بدون انتشار، فقط از طریق شبکه‌ی داخلی
# خوب (۲): انتشار فقط روی لوپ‌بک برای دسترسی محلی
ports:
  - "127.0.0.1:5432:5432"

۳. رمز و توکن داخل فایل

مقدارهایی مثل POSTGRES_PASSWORD: admin123 یا کلید API داخل YAML به Git می‌روند، در لاگ CI چاپ می‌شوند و در docker inspect دیده می‌شوند. دو گام بهتر: env_file که در Git نیست، و برای مقدارهای حساس‌تر Docker secrets.

services:
  db:
    image: postgres:16.4-alpine
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt   # این فایل در .gitignore است

۴ تا ۷: سلامت، ری‌استارت و منابع

۴. نداشتن healthcheck و اتکا به depends_on ساده

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

services:
  db:
    image: postgres:16.4-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 10

  app:
    image: registry.example.com/app:1.8.2
    depends_on:
      db:
        condition: service_healthy

۵. نداشتن سیاست ری‌استارت

بدون restart، بعد از یک کرش یا ری‌بوت سرور، سرویس بالا نمی‌آید. unless-stopped برای اغلب سرویس‌ها مناسب است: بعد از ری‌بوت راه می‌افتد، ولی اگر خودتان عمداً متوقفش کرده باشید دوباره روشن نمی‌شود.

restart: unless-stopped

مقدارهای مجاز no، always، unless-stopped و on-failure[:N] هستند؛ هر چیز دیگر یک خطای نوشتاری است.

۶. نداشتن محدودیت منابع

یک نشتی حافظه در یک سرویس می‌تواند کل سرور را به OOM بکشاند و دیتابیس را هم با خود ببرد. برای هر سرویس سقف حافظه و CPU بگذارید.

services:
  app:
    image: registry.example.com/app:1.8.2
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M

۷. لاگ‌های بی‌پایان

درایور پیش‌فرض json-file هیچ سقفی ندارد؛ لاگ یک سرویس پرگو می‌تواند دیسک را پر کند. چرخش لاگ را فعال کنید:

logging:
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

۸ تا ۱۲: امنیت و جزئیات اجرا

۸. اجرا با کاربر root

بیشتر ایمیج‌ها به‌طور پیش‌فرض با root اجرا می‌شوند. اگر برنامه نیاز به root ندارد (و معمولاً ندارد)، کاربر غیرprivilege تعیین کنید و در صورت امکان فایل‌سیستم را فقط‌خواندنی کنید.

user: "10001:10001"
read_only: true
tmpfs:
  - /tmp
cap_drop:
  - ALL
security_opt:
  - no-new-privileges:true

۹. نوشتن پورت‌ها بدون کوتیشن

YAML 1.1 مقدارهایی مثل 22:22 را ممکن است به‌صورت عدد در مبنای ۶۰ تفسیر کند. مستندات Compose هم توصیه می‌کند پورت را همیشه با کوتیشن (رشته) بنویسید.

ports:
  - "8080:80"

۱۰. container_name ثابت

اسم ثابت جلوی اجرای بیش از یک نسخه را می‌گیرد (docker compose up --scale app=3 خطا می‌دهد) و چند پروژه‌ی هم‌نام را روی یک میزبان با هم دچار تداخل می‌کند. بگذارید Compose خودش نام بسازد و در شبکه از نام سرویس استفاده کنید.

۱۱. استفاده‌ی بی‌مورد از bind mount

مسیرهایی مثل ./data:/var/lib/postgresql/data مشکل دسترسی (UID متفاوت)، رفتار متفاوت بین سیستم‌عامل‌ها و پشتیبان‌گیری سخت ایجاد می‌کنند. برای داده‌ی ماندگار از named volume استفاده کنید و bind mount را برای کد و کانفیگ در توسعه نگه دارید.

services:
  db:
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

۱۲. نگه‌داشتن خط version: قدیمی

خط version: "3.8" دیگر در Compose Specification لازم نیست و نسخه‌های جدید Docker Compose آن را نادیده می‌گیرند و فقط هشدار می‌دهند؛ حذفش فایل را تمیزتر می‌کند. اگر نگهش می‌دارید، آن را داخل کوتیشن بنویسید تا مثلاً 3.10 به عدد اعشاری 3.1 تبدیل نشود.

همه‌چیز کنار هم

name: shop

services:
  app:
    image: registry.example.com/shop:1.8.2
    restart: unless-stopped
    user: "10001:10001"
    read_only: true
    ports:
      - "127.0.0.1:8080:8080"
    env_file: [.env]
    depends_on:
      db:
        condition: service_healthy
    deploy:
      resources:
        limits: { cpus: "1.0", memory: 512M }
    logging:
      driver: json-file
      options: { max-size: "10m", max-file: "3" }

  db:
    image: postgres:16.4-alpine
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets: [db_password]
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 10

secrets:
  db_password:
    file: ./secrets/db_password.txt

volumes:
  pgdata:

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

IMGDocker Image Size Calculatorلایه‌های ایمیج را بشکافید و Dockerfile را برای مشکلات رایج بررسی کنید.YAMLYAML Validatorاگر خطای نحوی در YAML می‌گیرید، محل دقیق آن را اینجا پیدا کنید.

جمع‌بندی و مطالعه‌ی بعدی

فایل Compose قابل‌اتکا یعنی نسخه‌ی ثابت، healthcheck، سیاست restart، سقف منابع و مدیریت درست رمزها. برای ادامه، نوشتن Dockerfile بهینه ایمیج‌هایی را که در Compose استفاده می‌کنید سبک‌تر و امن‌تر می‌کند، شبکه و Volume در Docker بخش‌های network و volumes این فایل را به‌طور کامل توضیح می‌دهد و امن‌سازی Docker اقدام‌های لازم برای میزبان را فهرست می‌کند. اگر Docker روی سرور ایران نصب نمی‌شود، راهنمای نصب از مخزن داخلی را ببینید.

پرسش‌های متداول

آیا هنوز باید خط version: را بنویسم؟
نه. Compose Specification جدید به آن نیاز ندارد و نسخه‌های اخیر Docker Compose فقط یک هشدار نشان می‌دهند. حذفش امن است.
depends_on تضمین می‌کند دیتابیس آماده است؟
به‌طور پیش‌فرض نه؛ فقط ترتیب شروع کانتینرها را مشخص می‌کند. برای انتظار تا آماده‌شدن واقعی باید healthcheck تعریف کنید و از condition: service_healthy استفاده کنید.
فایل .env را در Git بگذارم؟
خیر. فایل واقعی را در .gitignore بگذارید و یک .env.example بدون مقدار حساس را در مخزن نگه دارید.
Compose برای تولید مناسب است؟
برای یک سرور یا چند سرویس کوچک، بله، به شرطی که به همین نکته‌ها (نسخه‌ی ثابت، healthcheck، محدودیت منابع، رمزها) توجه شود. برای چندین سرور و استقرار بدون قطعی معمولاً به Kubernetes یا Swarm نیاز دارید.