cover

每次開新專案都在從零寫 docker-compose.yml?然後忘了加 healthcheck、忘了限制 log 大小、volume 名稱亂取到後來自己都搞不清楚哪個是哪個?這篇直接給你四套可以拿去用的模板。

先講結論

Docker Compose 的標準化不是限制彈性,是把「正確的預設」變成起點。本篇提供四套模板:Web App Stack、監控 Stack、日誌 Stack、開發環境。每套都附最佳實踐。直接 copy,改環境變數,docker compose up -d 就能跑。


模板 1:Web App Stack

最常見的組合:Nginx 反向代理 + App + PostgreSQL + Redis。大部分 Web 服務用這套就夠了。

# docker-compose.app.yml
version: "3.8"
 
services:
  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - static_files:/var/www/static:ro
    depends_on:
      app:
        condition: service_healthy
    networks:
      - frontend
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
 
  app:
    image: ${APP_IMAGE:-myapp:latest}
    env_file:
      - .env
    environment:
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
      - REDIS_URL=redis://cache:6379/0
    volumes:
      - static_files:/app/static
      - upload_data:/app/uploads
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy
    networks:
      - frontend
      - backend
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
 
  db:
    image: postgres:16
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=${DB_NAME}
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - backend
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
 
  cache:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    networks:
      - backend
    restart: unless-stopped
    command: ["redis-server", "--appendonly", "yes"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
 
volumes:
  static_files:
  upload_data:
  db_data:
  redis_data:
 
networks:
  frontend:
  backend:

這裡有幾個重點你可能會忽略:

healthcheck 是 depends_on 的靈魂。 沒有 healthcheck 的 depends_on 只保證「容器啟動了」,不保證「服務 ready 了」。DB 容器可能啟動了但還在 recovery,App 就去連然後 crash。

logging 一定要限制大小。 max-size: "10m" + max-file: "3" 表示單個 log 檔最多 10MB,最多保留 3 個。不設的話,跑幾個月 log 把磁碟打爆是常見的慘案。

network 分層。 Nginx 跟 App 在 frontend,App 跟 DB/Redis 在 backend。DB 不需要也不應該出現在 frontend network 裡。


模板 2:監控 Stack

Prometheus + Grafana + Node Exporter,三分鐘搭好一套基本的監控系統。

# docker-compose.monitoring.yml
version: "3.8"
 
services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prom_data:/prometheus
    ports:
      - "9090:9090"
    restart: unless-stopped
 
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
    depends_on:
      - prometheus
    restart: unless-stopped
 
  node_exporter:
    image: prom/node-exporter:latest
    ports:
      - "9100:9100"
    restart: unless-stopped
 
volumes:
  prom_data:
  grafana_data:

Prometheus 的 config 用 git 管理,不要手動改了之後忘記 commit。Grafana 的 admin 密碼用環境變數傳入,不要寫死在 compose 裡。Node Exporter 如果想拿到更準確的主機指標,可以用 network_mode: host


模板 3:日誌 Stack(EFK)

Elasticsearch + Filebeat + Kibana。用來集中所有容器的 log。

# docker-compose.logging.yml
version: "3.8"
 
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
    environment:
      - discovery.type=single-node
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    ports:
      - "9200:9200"
    volumes:
      - es_data:/usr/share/elasticsearch/data
    restart: unless-stopped
 
  kibana:
    image: docker.elastic.co/kibana/kibana:8.12.0
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch
    restart: unless-stopped
 
  filebeat:
    image: docker.elastic.co/beats/filebeat:8.12.0
    volumes:
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
    depends_on:
      - elasticsearch
    restart: unless-stopped
 
volumes:
  es_data:

Elasticsearch 的 JVM heap 不要給太大。512MB 在 dev/staging 夠用了,prod 再按需調整。Filebeat 只需要 read-only 權限就能收 Docker log,不要給它不必要的權限。


模板 4:本機開發環境

開發的時候不想裝 PostgreSQL、Redis、MinIO 到本機?全部丟 Docker 裡。

# docker-compose.dev.yml
version: "3.8"
 
services:
  postgres:
    image: postgres:16
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=dev
      - POSTGRES_PASSWORD=dev
      - POSTGRES_DB=dev_db
    volumes:
      - dev_db:/var/lib/postgresql/data
 
  redis:
    image: redis:7
    ports:
      - "6379:6379"
 
  minio:
    image: minio/minio
    command: server /data --console-address ":9001"
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      - MINIO_ROOT_USER=minio
      - MINIO_ROOT_PASSWORD=minio123
    volumes:
      - minio_data:/data
 
  mailhog:
    image: mailhog/mailhog
    ports:
      - "8025:8025"
 
volumes:
  dev_db:
  minio_data:

這套不需要 healthcheck 也不需要 network 分層——它就是本機開發用的。密碼用 dev / minio123 這種,反正不會上 prod。如果你的 prod 密碼也是 dev,我們需要談談。


通用的 Checklist

不管用哪套模板,這些事都要做:

  • 環境變數放 .env,不要硬編碼在 compose 裡
  • 所有 service 加 restart: unless-stopped
  • 重要 service 加 healthcheck
  • logging 限制大小
  • Volume 命名要有意義(db_data 而不是 volume1
  • Network 分前後端
cp .env.example .env
docker compose -f docker-compose.app.yml up -d

延伸閱讀


Docker Compose 的最高境界是:新人第一天 clone repo,改一下 .env,docker compose up,五分鐘就能開始開發。如果你的 onboarding 需要半天,那不是新人的問題,是你的 compose 的問題。