یک فایل docker-compose.yml که «بالا میآید» لزوماً درست نیست. بیشتر مشکلها وقتی خودشان را نشان میدهند که سرویس در تولید است: دیتابیسی که روی اینترنت باز مانده، ایمیجی که بیخبر عوض شده، یا سرویسی که قبل از آمادهشدن دیتابیس بالا آمده و میمیرد. این راهنما ۱۲ اشتباه را با نمونهی بد و خوب نشان میدهد.
۱ تا ۳: نسخه، پورت و رمز
۱. استفاده از تگ 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: را بنویسم؟
depends_on تضمین میکند دیتابیس آماده است؟
condition: service_healthy استفاده کنید.فایل .env را در Git بگذارم؟
.gitignore بگذارید و یک .env.example بدون مقدار حساس را در مخزن نگه دارید.