🎯 重點摘要: Docker Compose 是用一個 YAML 檔定義並管理多容器應用的工具:
① 一檔定義全家:web、資料庫、快取、反向代理全寫在compose.yaml,一條指令整組啟停
② v2 已內建:現在是 Docker CLI 的子指令docker compose(沒有連字號),舊的 python 版docker-compose已淘汰
③ 啟動順序可控:depends_on配healthcheck能等資料庫「真正就緒」才啟動 web,不再連線被拒
④ 開發體驗起飛:Profiles 按需開關服務群組;Compose Watch 讓程式碼改完自動同步進容器
⑤ 環境分離靠覆蓋:基底檔 +compose.override.yaml(開發)+compose.prod.yaml(正式)各管各的
Docker Compose 是什麼?
真實世界的應用很少只有一個容器——web 後面通常還有資料庫、快取、訊息佇列、反向代理。用裸 docker run 一個一個起,參數長到懷疑人生,網路要手接、順序要手顧、改一行要重打十行。Compose 把這一切收斂成一個 YAML 檔:宣告每個服務用什麼映像、掛什麼磁碟、開什麼埠、誰依賴誰,然後 docker compose up 一鍵整組拉起來。
還沒熟習單容器的話,先看我們的 Docker 容器化指南再回來。
v1 與 v2:別再用舊的了
| 舊版 docker-compose (v1) | 現行 docker compose (v2) | |
|---|---|---|
| 型態 | 獨立 Python 套件 | Docker CLI 內建 plugin(Go 寫的) |
| 指令 | docker-compose up(有連字號) | docker compose up(空格) |
| 維護狀態 | 已停止維護 | 持續更新,新功能都在這 |
確認版本很簡單:
$ docker compose version
Docker Compose version v2.39.2
安裝
- macOS / Windows:裝 Docker Desktop 就內建,什麼都不用做
- Linux:跟著 Docker Engine 一起裝 plugin:
sudo apt-get update
sudo apt-get install docker-compose-plugin
✅ 檔名慣例:官方推薦compose.yaml,docker-compose.yml也認得。放在專案根目錄,之後所有 compose 指令都會自動抓它。
第一個 Compose:三分鐘上手
經典入門場景:一個計數器 web + Redis。新建 compose.yaml:
services:
web:
build: .
ports:
- "8000:5000"
depends_on:
- redis
redis:
image: redis:7-alpine
然後:
docker compose up -d # 背景啟動整組服務
curl localhost:8000 # 驗證
docker compose down # 整組收掉
就這樣。Compose 自動建了一個專屬 network 讓 web 用主機名稱 redis 直接連到 Redis——不需要手動 docker network create、不需要寫 IP。
核心語法詳解
image 與 build
services:
api:
image: myapp/api:1.4 # 直接用現成映像
worker:
build: # 或從本地 Dockerfile 建
context: ./worker
dockerfile: Dockerfile
args:
NODE_ENV: development
image: myapp/worker:dev # 順便命名建出來的映像
ports vs expose
ports:映射到宿主機("8000:5000"主機 8000 → 容器 5000),給人類和外部流量用expose:只在同一 network 內聲明埠,不映射出去——純文件用途,服務間互連其實根本不用宣告也能連
⚠️ 埠字串要加引號:"8000:5000"寫成8000:5000在某些 YAML 解析情境會被當成六十進位數字出包,養成加引號的習慣。
volumes 三種掛法
| 類型 | 寫法 | 用途 |
|---|---|---|
| Named volume | pgdata:/var/lib/postgresql/data | 資料庫等需要持久化的資料,由 Docker 管理 |
| Bind mount | ./src:/app/src | 本地開發即時同步程式碼進容器 |
| Anonymous / tmpfs | /tmp | 拋棄式暫存 |
Named volume 要在頂層 volumes: 區塊聲明:
services:
db:
image: postgres:17
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
environment 與變數替換
services:
api:
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
REDIS_HOST: cache
env_file:
- ./api.env # 整包變數檔,適合塞一堆設定
${DB_PASSWORD} 是從 Compose 所在目錄的 .env 檔讀出來做字串替換——注意這是「寫進 YAML 前」的替換,跟容器的環境變數是兩回事。.env 記得放進 .gitignore。
restart 政策
| 值 | 行為 |
|---|---|
| no | 預設,掛了不自動重啟(YAML 裡要加引號 "no",不然會被解析成 false!) |
| always | 永遠重啟,含宿主機開機後 |
| unless-stopped | 同 always,但手動 stop 後就不自動拉起(伺服器最常用的選擇) |
| on-failure | 只在非零退出時重啟,可加上限 on-failure:5 |
networks
預設 Compose 會建一個讓所有服務互通的 network。要隔離(例如資料庫不想被前端層摸到)就自訂:
services:
nginx:
networks: [frontend]
api:
networks: [frontend, backend]
db:
networks: [backend] # 只在 backend 網段,nginx 連不到它
networks:
frontend:
backend:
internal: true # 完全不上外網
啟動順序:depends_on × healthcheck
新手最常撞牆的一課:depends_on 只保證「容器啟動了」,不保證裡面的 PostgreSQL「接受連線了」。web 秒啟動、db 還在初始化,連線直接被拒。
解法是讓 db 自己報告健康狀態,web 等「健康」而不是等「活著」:
services:
web:
build: .
depends_on:
db:
condition: service_healthy # 等健康檢查通過
restart: true # db 重啟時跟著重啟 web
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s # 每 10 秒測一次
timeout: 5s # 單次超過 5 秒算失敗
retries: 5 # 連續失敗 5 次判 unhealthy
start_period: 30s # 前 30 秒的失敗不計入(初始化寬限)
| condition | 意義 |
|---|---|
| service_started | 容器啟動就好(等同舊版的 depends_on) |
| service_healthy | 健康檢查通過才算 |
| service_completed_successfully | 適合一次性 job(如 migration 容器),跑完且成功才繼續 |
💡 觀察健康狀態:docker compose ps會顯示 (healthy)/(unhealthy);想看細節用docker inspect --format '{{.State.Health.Status}}' <容器名>。
常用指令速查表
| 指令 | 作用 |
|---|---|
| docker compose up -d | 背景建立並啟動全部服務(--build 強制重建映像) |
| docker compose down | 停止並移除容器與 network;-v 連 volume 一起刪(資料會消失!) |
| docker compose ps | 列出本專案容器狀態與健康情況 |
| docker compose logs -f api | 追蹤某服務日誌(不加服務名就是全部) |
| docker compose exec api sh | 進入運行中容器開 shell |
| docker compose build | 只重建有 build 定義的映像 |
| docker compose pull | 拉取最新映像 |
| docker compose restart api | 重啟指定服務 |
| docker compose config | 把最終合併後的完整配置印出來驗證(除錯神器) |
| docker compose top | 看各容器內的行程 |
| docker compose cp api:/app/log.txt . | 容器與宿主機之間複製檔案 |
Profiles:按需啟動的服務群組
有些服務只在特定情境需要——本地的郵件測試工具、除錯面板、GPU 推理服務。給它標上 profile,平時 up 不會啟動:
services:
api:
image: myapp/api # 沒有 profiles = 永遠啟動
mailpit:
image: axllent/mailpit
profiles: ["devtools"] # 只有啟用 devtools 才會跑
k6:
image: grafana/k6
profiles: ["loadtest"]
docker compose --profile devtools up -d # 啟用單一群組
docker compose --profile devtools --profile loadtest up -d # 同時多個
COMPOSE_PROFILES=devtools docker compose up -d # 用環境變數也行
⚠️ Profiles 不是安全邊界——它只是「預設不啟動」的開關,明確點名服務(docker compose run mailpit sh)照樣能啟動它。別拿來藏不該跑的東西。
Compose Watch:改 code 不用 rebuild
v2.22 加入的殺手級開發功能。在服務下宣告 develop.watch 規則,然後 docker compose up --watch,檔案一存就依規則處理:
services:
web:
build: .
develop:
watch:
- action: sync # 原地同步,配框架熱重載(React/Vite/Django)
path: ./src
target: /app/src
ignore: [node_modules/]
- action: rebuild # 依賴變了就重建映像
path: package.json
- action: sync+restart # 同步後重啟,適合 nginx.conf 這類設定
path: ./nginx.conf
target: /etc/nginx/nginx.conf
| action | 觸發時行為 | 適用 |
|---|---|---|
| sync | 直接把檔案同步進運行中的容器 | Python/JS 等直譯語言+熱重載 |
| rebuild | 重建映像並重建容器 | Go/Rust/Java 等編譯語言 |
| sync+restart | 同步後重啟容器 | 設定檔修改 |
| sync+exec | 同步後在容器內執行指令 | 同步後跑 migration/重新載入 |
經驗法則:直譯語言用 sync、編譯語言用 rebuild、設定檔用 sync+restart,而且只對需要的服務開 watch。
多檔案配置:一套 YAML 走天下
開發和正式環境的需求天生不同(bind mount vs 穩定映像、暴露 debug 埠 vs 不暴露)。官方約定的分層做法:
compose.yaml—— 共同基底(服務清單、networks、volumes)compose.override.yaml—— 開發差異;up 時自動載入compose.prod.yaml—— 正式差異;明確指定載入
# 開發(自動疊 override)
docker compose up -d
# 正式(只用基底 + prod)
docker compose -f compose.yaml -f compose.prod.yaml up -d
# 疊完到底長怎樣?先看再跑
docker compose -f compose.yaml -f compose.prod.yaml config
同名欄位是「覆蓋」而非合併(ports、environment 這類列表會以後載入者為準)。更大的專案還能用頂層 include: 把多個 compose 檔組織成一棵樹。
實戰範例:四服務架構
把前面學的全部拼起來——Nginx 反代 + API + PostgreSQL + Redis:
services:
nginx:
image: nginx:1.27-alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
api:
condition: service_healthy
restart: unless-stopped
api:
build: ./api
expose:
- "3000"
environment:
DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
REDIS_URL: redis://cache:6379/0
env_file:
- ./api.secrets.env
develop:
watch:
- action: sync
path: ./api/src
target: /app/src
ignore: ["**/__pycache__/"]
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/healthz"]
interval: 15s
timeout: 5s
retries: 3
start_period: 20s
restart: unless-stopped
networks: [frontend, backend]
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
networks: [backend]
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
restart: unless-stopped
networks: [backend]
networks:
frontend:
backend:
volumes:
pgdata:
這份配置做到了:資料庫只在 backend 網段外界摸不到、API 健康後 Nginx 才啟動、資料存在 named volume 重啟不丟、開發時 --watch 即時同步原始碼。
最佳實務與常見坑
down -v是核彈:它會刪掉 named volumes,本地資料庫直接歸零。日常請用down,確定要重置資料再-v- healthcheck 只管啟動順序:db 中途掛掉不會自動重啟你的 api,應用層仍要有連線重試邏輯
- 密碼不要寫死在 YAML:用
.env+env_file,正式環境換 secrets 管理方案 - 改了 yaml 沒生效?Compose 不會自動偵測配置變更,要
up -d重新套用;改 Dockerfile 要up -d --build - 孤兒容器:改過 service 名字後舊容器可能還留著,看到警告加
--remove-orphans - 先
config再up:多檔案覆蓋疊完的結果常常跟想像不同,docker compose config看最終形狀再執行
常見問題 FAQ
docker compose 和 docker-compose 有差嗎?
指令幾乎完全相容,但 v1(連字號版)已停止維護,新功能(watch、profiles 完整語義、include)都只在 v2。現在寫教學、腳本一律用 docker compose。
可以像 swarm 一樣 scale 嗎?
無狀態服務可以 docker compose up -d --scale api=3,但要注意埠衝突(用 expose 別用 ports)且這只是本機多開,不是生產級編排——那種需求請上 Kubernetes。
資料要怎麼備份?
named volume 的內容可以用 docker run --rm -v 專案名_pgdata:/data -v $PWD:/backup alpine tar czf /backup/pgdata.tgz /data 打包出來,或直接對 db 容器下 pg_dump。
up 的時候說 port already in use?
宿主機的埠被占了(可能是上一份沒關乾淨的專案或系統服務)。lsof -i :8000 找出兇手,或換一個映射埠。
CI/CD 裡怎麼用?
GitHub Actions 的 runner 有內建 Docker Compose,可以直接 docker compose up -d db 起測試資料庫、跑完 down 收掉。搭配 profiles 可以讓 CI 跳過用不到的重型服務。
總結
Compose 的價值在於把「我的環境怎麼跑」變成一份可版本控制的宣言:新人 clone 下來 docker compose up 就能跑,正式環境同一份檔案換個 overlay 就上線。掌握三件事就算畢業:services/volumes/networks 的宣告式語法、healthcheck × depends_on 的就緒控制、override 分層的環境分離。之後再把 Profiles 和 Compose Watch 加進日常流程,開發體驗會徹底不一樣。
🎯 快速上手:專案根目錄寫一份最小compose.yaml→docker compose up -d→docker compose logs -f看日誌 →docker compose down收工。先跑通兩個服務互連,再慢慢補 healthcheck 和多環境,一天就能內化。