快轉到主要內容
  1. 教學文章/

Docker Compose:多容器服務編排實戰

·5 分鐘· loading · loading · ·
Docker Docker-Compose Devops Container 部署
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
DevOps - 本文屬於一個選集。
§ 1: 本文

一、前言
#

你已經學會用 Docker 把單一應用程式容器化了(還沒的話先去看 Docker for Python),但現實中的專案通常不只一個容器。

想像一下:一個典型的 Web 應用至少需要:

  • 🌐 前端 — React / Vue / Next.js
  • ⚙️ 後端 API — FastAPI / Django / Express
  • 🗄️ 資料庫 — PostgreSQL / MySQL / MongoDB
  • 📦 快取 — Redis
  • 🔄 反向代理 — Nginx / Traefik

如果每個容器都要手動 docker run,光是打指令就累死了。更別說它們之間的網路、啟動順序、環境變數……

這時候就是 Docker Compose 登場的時候!一個 docker-compose.yml 檔案,一行 docker compose up,所有服務一次搞定。

拍拍君今天帶你從零開始,用 Docker Compose 建構一個完整的多容器應用。🐳

二、安裝
#

Docker Compose v2 已經內建在 Docker Desktop 和最新版 Docker Engine 中,不需要額外安裝。

# 確認 Docker Compose 版本
docker compose version
# Docker Compose version v2.24.0

# 如果是舊版 Docker,可能需要安裝 plugin
# Ubuntu/Debian
sudo apt-get install docker-compose-plugin

# macOS (用 Docker Desktop 就不用)
brew install docker-compose

💡 v1 vs v2:舊版指令是 docker-compose(有 hyphen),新版是 docker compose(空格)。本文統一用 v2 語法。如果你還在用 v1,強烈建議升級!

三、第一個 Compose 檔案
#

先從最簡單的開始 — 一個 FastAPI 後端 + PostgreSQL 資料庫。

3.1 專案結構
#

my-project/
├── docker-compose.yml
├── backend/
│   ├── Dockerfile
│   ├── main.py
│   └── requirements.txt

3.2 後端程式碼
#

# backend/main.py
from fastapi import FastAPI
import asyncpg
import os

app = FastAPI(title="拍拍 API")

DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://pypy:secret@db:5432/pypydb")

@app.on_event("startup")
async def startup():
    app.state.pool = await asyncpg.create_pool(DATABASE_URL)

@app.on_event("shutdown")
async def shutdown():
    await app.state.pool.close()

@app.get("/")
async def root():
    async with app.state.pool.acquire() as conn:
        version = await conn.fetchval("SELECT version()")
    return {"message": "拍拍君的 API 運作中!", "db_version": version}

@app.get("/health")
async def health():
    return {"status": "ok"}
# backend/Dockerfile
FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# backend/requirements.txt
fastapi==0.115.0
uvicorn==0.30.0
asyncpg==0.30.0

3.3 docker-compose.yml
#

# docker-compose.yml
services:
  backend:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://pypy:secret@db:5432/pypydb
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: pypy
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: pypydb
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  pgdata:

3.4 啟動!
#

# 建構並啟動所有服務(前景模式)
docker compose up --build

# 背景模式(推薦)
docker compose up --build -d

# 查看運行中的容器
docker compose ps

# 查看 log
docker compose logs -f backend

就這樣!兩個容器自動串接,backend 透過 db 這個 hostname 就能連到 PostgreSQL。不需要知道 IP,不需要手動設定網路。✨

四、核心概念深入
#

4.1 Services(服務)
#

每個 service 定義一個容器。可以用 image(拉現成的)或 build(自己建構):

services:
  # 用現成 image
  redis:
    image: redis:7-alpine
    
  # 自己建構
  api:
    build:
      context: ./api          # Dockerfile 所在目錄
      dockerfile: Dockerfile   # 預設就是 Dockerfile
      args:
        - PYTHON_VERSION=3.12  # build arguments

4.2 Networks(網路)
#

Docker Compose 預設會建立一個 bridge network,所有 service 都在同一個網路裡,彼此用 service 名稱 當 hostname。

如果需要更精細的網路隔離:

services:
  frontend:
    build: ./frontend
    networks:
      - frontnet

  backend:
    build: ./backend
    networks:
      - frontnet
      - backnet

  db:
    image: postgres:16-alpine
    networks:
      - backnet

networks:
  frontnet:
  backnet:

這樣 frontend 看不到 db,只有 backend 能同時存取兩邊。在正式環境中,這種隔離非常重要!🔒

4.3 Volumes(資料持久化)
#

容器停止後資料就消失了。用 volumes 來保存資料:

services:
  db:
    image: postgres:16-alpine
    volumes:
      # Named volume — Docker 管理,適合資料庫
      - pgdata:/var/lib/postgresql/data
      
  backend:
    build: ./backend
    volumes:
      # Bind mount — 本地目錄對應,適合開發
      - ./backend:/app
      # 匿名 volume — 避免 node_modules 被覆蓋
      - /app/node_modules

volumes:
  pgdata:          # 宣告 named volume
    driver: local  # 預設就是 local,可省略
類型 語法 適用場景
Named volume pgdata:/data 資料庫、持久化資料
Bind mount ./src:/app 開發時 hot reload
Anonymous volume /app/node_modules 保護容器內目錄

4.4 depends_on(啟動順序)
#

depends_on 控制容器啟動順序,但預設只等「啟動」,不等「就緒」:

services:
  backend:
    build: ./backend
    depends_on:
      db:
        condition: service_healthy  # 等到 healthcheck 通過!

  db:
    image: postgres:16-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U pypy"]
      interval: 5s
      timeout: 5s
      retries: 5

⚠️ 重要:沒有 condition: service_healthydepends_on 只保證「容器啟動了」,不保證「服務可以用了」。對資料庫來說,通常需要幾秒鐘初始化,所以一定要加 healthcheck!

五、實戰:完整 Web 應用
#

來個更完整的範例 — 前端 + 後端 + 資料庫 + Redis + Nginx:

# docker-compose.yml
services:
  # ── Nginx 反向代理 ──
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - frontend
      - backend

  # ── React 前端 ──
  frontend:
    build: ./frontend
    expose:
      - "3000"

  # ── FastAPI 後端 ──
  backend:
    build: ./backend
    expose:
      - "8000"
    environment:
      DATABASE_URL: postgresql://pypy:secret@db:5432/pypydb
      REDIS_URL: redis://redis:6379/0
      SECRET_KEY: ${SECRET_KEY:-dev-secret-key}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  # ── PostgreSQL ──
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: pypy
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: pypydb
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U pypy"]
      interval: 5s
      timeout: 5s
      retries: 5

  # ── Redis 快取 ──
  redis:
    image: redis:7-alpine
    volumes:
      - redisdata:/data

volumes:
  pgdata:
  redisdata:

對應的 Nginx 設定:

# nginx/nginx.conf
upstream frontend {
    server frontend:3000;
}

upstream backend {
    server backend:8000;
}

server {
    listen 80;

    # 前端
    location / {
        proxy_pass http://frontend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

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

一行啟動整個堆疊:

docker compose up -d
# 五個容器同時運行!

六、進階技巧
#

6.1 環境變數管理
#

別把密碼寫死在 docker-compose.yml 裡!用 .env 檔案:

# .env(記得加進 .gitignore!)
POSTGRES_USER=pypy
POSTGRES_PASSWORD=super-secret-password
SECRET_KEY=my-production-secret
services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

也可以用 env_file 一次載入整個檔案:

services:
  backend:
    build: ./backend
    env_file:
      - .env
      - .env.local  # 覆蓋用

6.2 多環境設定(Override)
#

docker-compose.override.yml 在開發環境覆寫設定:

# docker-compose.yml(base)
services:
  backend:
    build: ./backend
    expose:
      - "8000"

# docker-compose.override.yml(開發環境,自動載入)
services:
  backend:
    volumes:
      - ./backend:/app    # hot reload
    ports:
      - "8000:8000"       # 對外開放 debug
    environment:
      - DEBUG=true
    command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# 開發環境(自動讀 override)
docker compose up

# 正式環境(明確指定檔案)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

6.3 資源限制
#

避免某個容器吃掉所有資源:

services:
  backend:
    build: ./backend
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M

6.4 Profiles(按需啟動)
#

有些服務只在特定情境需要(例如 debug 工具、monitoring):

services:
  backend:
    build: ./backend

  db:
    image: postgres:16-alpine

  # 只在 debug 時啟動
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    profiles:
      - debug

  # 只在需要監控時啟動
  prometheus:
    image: prom/prometheus
    profiles:
      - monitoring
# 只啟動核心服務
docker compose up -d

# 也啟動 debug 工具
docker compose --profile debug up -d

# 全部都啟動
docker compose --profile debug --profile monitoring up -d

6.5 常用操作指令
#

# 啟動 / 停止 / 重啟
docker compose up -d
docker compose stop
docker compose restart backend   # 只重啟特定服務

# 查看狀態與 logs
docker compose ps
docker compose logs -f --tail=100 backend

# 進入容器
docker compose exec backend bash
docker compose exec db psql -U pypy -d pypydb

# 重新 build(程式碼更新後)
docker compose up --build -d

# 完全清除(含 volumes)
docker compose down -v

# 只移除容器,保留 volumes
docker compose down

七、常見問題排除
#

連不上資料庫?
#

  1. 確認 hostname — 用 service 名稱(db),不是 localhost
  2. 確認啟動順序 — 加上 depends_on + healthcheck
  3. 確認網路 — 用 docker compose exec backend ping db 測試

Port 衝突?
#

# 查看誰佔了 port
lsof -i :5432

# 改用不同的對外 port
ports:
  - "15432:5432"  # 本機用 15432 存取

Volume 權限問題?
#

services:
  backend:
    build: ./backend
    user: "1000:1000"  # 指定 UID:GID

結語
#

Docker Compose 是多容器開發的必備工具。今天我們學了:

  • 📝 docker-compose.yml 的核心語法:services、networks、volumes
  • 🔗 容器之間如何透過 service 名稱自動連線
  • 🏥 depends_on + healthcheck 確保啟動順序
  • 🏗️ 完整的前後端 + 資料庫 + 快取 + 反向代理架構
  • 🔧 環境變數、多環境設定、profiles 等進階技巧

從開發環境到 CI/CD,Docker Compose 都是你最好的朋友。如果之後要上 Kubernetes,很多概念也是相通的。先把 Compose 搞熟,未來的路會更順!🚀

延伸閱讀
#

DevOps - 本文屬於一個選集。
§ 1: 本文

相關文章

Docker for Python:讓你的程式在任何地方都能跑
·6 分鐘· loading · loading
Python Docker Container Devops 部署
Python Profiling:cProfile + line_profiler 效能分析完全指南
·8 分鐘· loading · loading
Python Profiling CProfile Line_profiler Performance Optimization
MLX 入門教學:在 Apple Silicon 上跑機器學習
·4 分鐘· loading · loading
Python Mlx Apple-Silicon Machine-Learning Deep-Learning
Rust 錯誤處理:Result/Option vs Python try/except
·7 分鐘· loading · loading
Rust Python Error-Handling Result Option
Python pytest:fixture + parametrize + mock 完整指南
·8 分鐘· loading · loading
Python Pytest Testing Fixture Mock Parametrize TDD
Python threading:多執行緒並行的正確打開方式
·8 分鐘· loading · loading
Python Threading Concurrency 並行 GIL