一、前言 #
你已經學會用 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_healthy,depends_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
七、常見問題排除 #
連不上資料庫? #
- 確認 hostname — 用 service 名稱(
db),不是localhost - 確認啟動順序 — 加上
depends_on+healthcheck - 確認網路 — 用
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 搞熟,未來的路會更順!🚀