Docker Docker Compose Linux 伺服器部署 DevOps

Docker Compose 生產環境部署:那些讓你踩坑的細節

Docker Compose 不只是開發用途,本文深入探討生產環境部署的關鍵細節,包含 image 版本釘定、網路隔離、健康檢查、secrets 管理與 Compose Specification v5 新特性,避免常見陷阱。

Docker Compose 經常被認為只適合本機開發,一到生產環境就得換 Kubernetes。這個觀點在 2026 年已經過時了。Compose Specification v5.0(代號 Mont Blanc,2025 年底發布)讓 Compose 的生產可用性大幅提升,對於單機或小規模多服務部署,Compose 仍是最省力、最易維護的選擇。

問題不是工具本身,而是大多數人把開發時的寫法直接搬到線上。

image: latest 只要寫過一次就會後悔

這是最常見、也最致命的錯誤。

1
2
3
4
# 別這樣做
image: postgres:latest
image: redis
image: myapp

latest 意味著下次 docker compose pull 會拉到不同版本。PostgreSQL 15 跟 16 之間的資料格式不相容,你的服務可能在某個凌晨兩點的自動更新後悄悄死掉。

正確做法是釘定到 minor 版本,並加上 digest 作為額外保險:

1
2
3
image: postgres:16.3-alpine3.20
image: redis:7.2-alpine
image: myapp:v2.4.1-abc1234

自訂 image 的 tag 應包含語意版本號加上 Git commit hash,這樣出問題時能立即追溯到哪個 commit 引入的。

網路隔離不是可選項

Compose 預設會把所有服務放進同一個 bridge network,表示你的 PostgreSQL 和 Redis 跟對外的 Nginx 容器在同一個網路裡。雖然外部流量進不來,但萬一 Nginx 容器被 RCE,攻擊者可以直接連到資料庫。

正確的做法是明確分層:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
services:
nginx:
image: nginx:1.27-alpine
networks: [frontend, backend]
ports:
- "80:80"
- "443:443"

api:
build: ./backend
networks: [backend] # 不對外暴露

db:
image: postgres:16.3-alpine3.20
networks: [db] # 只有 api 能連

redis:
image: redis:7.2-alpine
networks: [db]

networks:
frontend:
backend:
db:

這樣的結構讓 nginx 無法直接連到 dbdb 也無法主動對外發起連線。攻擊面縮小到一個服務被入侵只影響同網路的服務,而非整個 stack。

Secrets 管理:千萬別用環境變數傳密碼

environment: POSTGRES_PASSWORD=mypassword 這行字會被存進 docker inspect 的輸出、日誌系統、甚至可能被 CI/CD 工具截到。

最低限度的做法是用 .env 檔搭配 substitution:

1
2
3
4
5
# docker-compose.yml
services:
db:
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
1
2
# .env(不加入版本控制)
POSTGRES_PASSWORD=真正的密碼

進階做法是使用 Compose 原生的 secrets 機制,密碼會以唯讀檔案掛載到容器的 /run/secrets/ 路徑,不會出現在環境變數:

1
2
3
4
5
6
7
8
9
10
11
services:
db:
image: postgres:16.3-alpine3.20
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password

secrets:
db_password:
file: ./secrets/db_password.txt

.envsecrets/ 目錄都應該加進 .gitignore,並且以 .env.example 和空白的 secret 檔作為範本提交進版本庫。

健康檢查決定服務啟動順序是否可靠

很多人知道 depends_on 但不知道它預設只等容器「啟動」,不等服務「就緒」。資料庫容器啟動但 PostgreSQL 尚未完成初始化時,api 服務就已經開始嘗試連線,然後噴錯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
services:
db:
image: postgres:16.3-alpine3.20
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s

api:
build: ./backend
depends_on:
db:
condition: service_healthy # 等到 healthcheck 通過才啟動

start_period 是關鍵:在這段時間內失敗不計入 retries,避免資料庫正常初始化期間被誤判為不健康。

對 Redis 和其他服務也一樣要加 healthcheck,redis-cli ping 就夠了。

Resource Limits 在單機部署尤其重要

沒有設定資源限制的 Compose stack,單一服務的 OOM 會把整台機器的其他服務拖垮。

1
2
3
4
5
6
7
8
9
10
services:
api:
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 256M
cpus: '0.25'

limits 是硬上限,超過記憶體會被 OOM killer 殺掉;reservations 是排程用的軟保證,確保容器能拿到最低資源。在資源有限的 VPS 上,這兩個數值都得根據實際用量調整,不能憑感覺亂填。

restart policy 的選擇不是隨便的

1
restart: unless-stopped

這是生產環境的建議設定。它在容器 crash 後會自動重啟,機器重開機後也會自動啟動,但如果你手動 docker compose stop 就不會再自動啟。

always 的問題是即使你有意停掉某個服務進行維護,機器重開後它還是會復活,造成困惑。on-failure 則只在非 0 exit code 時重啟,對某些一次性任務容器才適合。

Compose Specification v5 帶來了什麼

最值得注意的是 develop.watch 模式正式成熟,以及 builder 被整合進 Docker Bake,讓 multi-platform builds 的支援更原生。不過對大多數生產部署影響更直接的是:version: 欄位現在明確廢棄,不再需要寫 version: "3.9"

舊版 docker-compose binary 也已正式退役,統一由 docker compose(中間有空格)替代。如果你的部署腳本還在用 docker-compose up,現在是時候更新了。

另一個實用的新工具是在部署前先跑:

1
docker compose config

這個指令會把所有 override 檔案、環境變數替換、預設值全部展開,讓你看到 Docker 實際會執行的完整設定。大多數「Compose 行為怪怪的」問題,在這步就能發現。

備份不在 Compose 裡,但一定要想清楚

Volume 裡的資料不會隨著 docker compose down 消失(除非加 -v),但也不會自動備份。PostgreSQL 資料、上傳的檔案、Redis 持久化資料,這些都需要獨立的備份機制。

最簡單的做法是用 cron 定期 pg_dump,把結果傳到異地儲存。如果是用 SQLite,直接 rsync 整個 .db 檔也行。無論用哪種方式,記得定期測試還原流程,只備份不測試等於沒備份。


如果你正在找一個能穩定跑 Docker Compose 的環境,NCSE Network 的 VPS 搭載 Intel Gold CPU 與 NVMe SSD,位於臺灣是方電訊機房,延遲低、頻寬穩,適合需要長期穩定運行 Docker 服務的開發者與中小企業。詳情可到 ncse.tw 查看方案。

需要穩定的雲端主機?

NCSE Network 提供企業級 VPS,7 天免費試用,臺灣是方電訊機房,99% SLA 保證。

查看 VPS 方案 →