چرا Docker Compose؟

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

پیش‌نیازهای Docker Compose

  • نصب Docker و Docker Compose (کامند docker compose version)

  • آشنایی مقدماتی با Dockerfile

  • آشنایی با مفاهیم شبکه، ولوم، متغیرهای محیطی

معماری نمونه: Nginx (Reverse Proxy) + API (Node.js) + PostgreSQL + Redis

در این معماری، Docker Compose وظیفه تعریف و مدیریت ارتباط میان Nginx، API، PostgreSQL و Redis را بر عهده دارد.

در این نمونه:

  • nginx ورودی‌ها را دریافت و به api هدایت می‌کند.

  • api (Node.js/Express) به postgres وصل می‌شود و از redis برای کش استفاده می‌کند.

  • healthcheck برای اطمینان از آماده‌بودن سرویس‌ها.

  • استفاده از .env، ولوم پایدار دیتابیس، شبکه اختصاصی داخلی.

ساختار پوشه‌ها

project-root/
├─ nginx/
│  ├─ default.conf
├─ api/
│  ├─ Dockerfile
│  ├─ package.json
│  └─ src/
│     └─ index.js
├─ .env
└─ docker-compose.yml

فایل docker-compose.yml (Compose v2)

version: "3.9"

name: multi-service-stack

services:
  nginx:
    image: nginx:1.27-alpine
    container_name: web
    depends_on:
      api:
        condition: service_healthy
    ports:
      - "80:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    networks:
      - app_net

  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    container_name: api
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      DB_HOST: postgres
      DB_USER: ${POSTGRES_USER}
      DB_PASSWORD: ${POSTGRES_PASSWORD}
      DB_NAME: ${POSTGRES_DB}
      REDIS_HOST: redis
      REDIS_PORT: 6379
      PORT: 3000
    expose:
      - "3000"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost:3000/health || exit 1"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 10s
    networks:
      - app_net

  postgres:
    image: postgres:16-alpine
    container_name: db
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-appuser}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-apppass}
      POSTGRES_DB: ${POSTGRES_DB:-appdb}
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-appuser} -d ${POSTGRES_DB:-appdb}"]
      interval: 10s
      timeout: 3s
      retries: 5
    networks:
      - app_net

  redis:
    image: redis:7-alpine
    container_name: cache
    command: ["redis-server", "--save", "60", "1000", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "PING"]
      interval: 10s
      timeout: 3s
      retries: 5
    networks:
      - app_net

volumes:
  db_data:
  redis_data:

networks:
  app_net:
    driver: bridge

nginx/default.conf (Reverse Proxy)

server {
  listen 80;
  server_name _;

  location / {
    proxy_pass http://api:3000/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }

  location /healthz {
    return 200 "ok";
    add_header Content-Type text/plain;
  }
}

api/Dockerfile (Node.js 20 + production-ready)

FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build || echo "no build step"

FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app ./
EXPOSE 3000
CMD ["node", "src/index.js"]

api/src/index.js (نمونه API با مسیر سلامت)

import http from "http";
import { Client } from "pg";
import { createClient } from "redis";

const PORT = process.env.PORT || 3000;

const db = new Client({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
});
const redis = createClient({ url: `redis://${process.env.REDIS_HOST}:6379` });

async function start() {
  await db.connect();
  await redis.connect();

  const server = http.createServer(async (req, res) => {
    if (req.url === "/health") {
      try {
        await db.query("SELECT 1");
        await redis.ping();
        res.writeHead(200, { "Content-Type": "application/json" });
        return res.end(JSON.stringify({ status: "ok" }));
      } catch (e) {
        res.writeHead(500);
        return res.end("unhealthy");
      }
    }

    if (req.url === "/") {
      res.writeHead(200, { "Content-Type": "text/plain" });
      return res.end("Hello from API!");
    }

    res.writeHead(404);
    res.end("Not Found");
  });

  server.listen(PORT, () => console.log(`API on :${PORT}`));
}

start().catch((err) => {
  console.error(err);
  process.exit(1);
});

فایل .env (نمونهٔ امنِ محلی)

NODE_ENV=development
POSTGRES_USER=appuser
POSTGRES_PASSWORD=apppass
POSTGRES_DB=appdb

اجرای استک با Docker Compose

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

بالا آوردن:

docker compose up -d --build

مشاهده لاگ‌ها:

docker compose logs -f api

توقف/پاک‌سازی:

docker compose down
docker compose down -v   # با حذف ولوم‌ها

بهترین‌تمرین‌ها (Best Practices)

  1. healthcheck و depends_on با condition: مطمئن شوید سرویس‌ها فقط پس از آماده‌شدن وابستگی‌ها بالا بیایند.

  2. .env و secrets: متغیرهای حساس را در .env و برای تولید از secrets و مخازن امن (Vault) استفاده کنید.

  3. ولوم پایدار برای دیتابیس: هرگز دیتابیس را بدون ولوم پایدار بالا نیاورید.

  4. شبکه اختصاصی: شبکه‌ی bridge اختصاصی برای جداسازی سرویس‌ها ایجاد کنید.

  5. تصویرهای کوچک و امن: از تصاویر پایه‌ی کم‌حجم (alpine) استفاده کنید، کاربر non-root، حذف ابزارهای اضافی.

  6. Lint و اسکن آسیب‌پذیری: Trivy/Grype را در CI اضافه کنید.

  7. Observability: لاگ ساختاریافته، metrics و tracing (Prometheus/Grafana/OTel) را بیفزایید.

  8. Profiles: با profiles محیط dev/stage/prod را از هم جدا کنید.

  9. Rate limits در Nginx: برای محافظت از API در ورودی.

  10. Readiness در اپ: مسیر /health یا /ready را سبک و سریع نگه دارید.

نمونه Profiles

استفاده از Profiles در Docker Compose کمک می‌کند سرویس‌های موردنیاز هر محیط را به‌صورت جداگانه فعال کنید.
services:
  api:
    profiles: ["dev"]
  nginx:
    profiles: ["dev","prod"]

اجرا برای prod:

docker compose --profile prod up -d

خطاهای رایج و رفع آن‌ها

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

  • پورت اشغال است: خطای bind: address already in use → پورت را عوض کنید یا پروسه‌ی اشغال‌کننده را ببندید.

  • اتصال DB/Redis برقرار نمی‌شود: نام سرویس را به‌عنوان hostname استفاده کنید (postgres، redis).

  • لوپ رستارت: healthcheck سخت‌گیرانه یا وابستگی دیرآماده. بازه‌ها و retries را تنظیم کنید.

  • Permission در ولوم‌ها: در کانتینر user بسازید یا مالکیت مسیر را تنظیم کنید.

  • کندی در Mac/Windows: bind mount سنگین است؛ از ولوم یا cached/delegated استفاده کنید.

نکات تولید (Production)

استفاده از Docker Compose در محیط Production به بررسی دقیق‌تر امنیت، مانیتورینگ، پشتیبان‌گیری و مدیریت Secrets نیاز دارد.

  • Compose vs Swarm/Kubernetes: Compose برای توسعه و تیم‌های کوچک عالی است؛ برای مقیاس و HA به Swarm/K8s فکر کنید.

  • Rolling updates: در Swarm از بخش deploy استفاده کنید؛ در Kubernetes از Deployment/StatefulSet.

  • Backup & Restore: ولوم‌های دیتابیس را با اسکریپت‌های زمان‌بندی‌شده پشتیبان‌گیری کنید.

  • Zero-downtime: چند replica + لودبالانسر (nginx/haproxy/traefik).

  • TLS اجباری: در production حتماً HTTPS (با Certbot/Traefik) فعال باشد.

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

1) چرا سرویس‌ها همدیگر را با localhost نمی‌بینند؟
داخل شبکه‌ی Compose از نام سرویس به‌عنوان hostname استفاده کنید، نه localhost.

2) آیا می‌توانم چند فایل Compose داشته باشم؟
بله:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

3) چگونه مهاجرت دیتابیس را اتوماتیک کنم؟
ورودی command یا اسکریپت entrypoint برای اجرای migrations در startup.

چک‌لیست سریع راه‌اندازی

  • Docker/Compose نصب شد

  • فایل‌های Dockerfile و compose آماده‌اند

  • .env تکمیل شد

  • healthcheck تنظیم شد

  • ولوم پایدار دیتابیس تعریف شد

  • reverse proxy و rewriteها تست شدند

  • لاگ‌ها و متریک‌ها رصد می‌شوند

سخن آخر شارن

Docker Compose یکی از ابزارهای کاربردی برای اجرای پروژه‌های چندسرویسی و مدیریت کانتینرها در یک ساختار منظم و تکرارپذیر است. با رعایت اصول امنیتی، استفاده از ولوم‌های پایدار، تنظیم Healthcheck، مانیتورینگ و مدیریت صحیح متغیرهای محیطی، می‌توانید استک قابل‌اعتمادتری برای توسعه و استقرار پروژه ایجاد کنید.

اگر برای طراحی معماری کانتینری، راه‌اندازی Docker، پیاده‌سازی CI/CD، مانیتورینگ سرورها یا بهینه‌سازی زیرساخت پروژه خود به خدمات تخصصی نیاز دارید، تیم شارن آماده ارائه مشاوره، اجرا و پشتیبانی زیرساخت‌های سازمانی است. برای آشنایی بیشتر با خدمات شارن به وب‌سایت sharen.co مراجعه کنید.

5/5 - (5 امتیاز)