「你把 Docker Compose 用在 Production 上?」——這句話在 2026 年的技術社群裡,依然能引發一番激烈討論。有人覺得這是職業自殺,也有人默默靠它撐起整個服務架構。

答案是:可以,但有條件。

Docker Compose 的定位從來不是 Kubernetes 的替代品,它最初是 dotCloud(就是後來的 Docker Inc.)為了解決「在我機器上可以跑」這個千古難題而打造的內部工具。一個 YAML 檔案描述多個容器、網路、磁碟區、環境變數,然後 docker compose up 就把整組服務拉起來。就這麼簡單。

但問題也出在這裡——簡單。Compose 沒有調度器、沒有控制平面、不會自動修復。它做完設定就退出了,留下來的運維工作,全部得由你(或者你的客戶)親自處理。

Docker Compose 的適用場景

在 2026 年的生產環境中,Docker Compose 最適合的場景其實很清楚:

單節點部署。這是最典型的甜蜜點。一個廠商把多容器應用推送到客戶環境、一個內部團隊跑長期服務但不需要 Kubernetes 叢集、一台零售門市的邊緣伺服器——這些場景的共通點是:規模小、運維負擔輕、一個有經驗的運維人員可以從一份 docker-compose.yaml 看懂整個系統。

資深工程師 Philip(任職於 Distr,一家協助軟體與 AI 公司進行軟體發布的公司)直言:「我看過的大多數 Docker Compose Production 事故,都來自同樣幾個通病——一個該刪的舊容器、半夜被塞滿的硬碟、偵測到異常卻什麼都沒做的健康檢查、指向了未知版本的 :latest 標籤、還有一個沒人想過的 socket 掛載。」

這些都不是 Docker 的 bug,而是設計上的取捨。Compose 假設你——或者管理主機的人——會搞定剩下的運維工作。但如果你把 Compose 檔案丟給客戶,客戶通常不會。

第一個坑:廢棄孤兒容器

這是最常見的坑。你從 docker-compose.yaml 移除了某個服務,跑了 docker compose up -d,然後那個容器繼續活得好好的。它脫離了專案管理,但依然綁在同一個網路和 Port 上。docker compose ps 不會顯示它——因為 Compose 只列出目前檔案中的服務。但 docker ps --filter label=com.docker.compose.project=<name> 會,因為 Docker 還保留著這個容器的標籤。

這就是六個月後你發現某個舊 worker 服務一直在默默吃 RAM 的方式。

解法很簡單:加上 --remove-orphans 參數

docker compose up -d --remove-orphans
docker compose down --remove-orphans

這個標籤告訴 Compose:任何曾經屬於這個專案、但現在不在檔案中的容器,全部刪掉。網路也會同步清理,但 Named Volume 是例外——Compose 預設會保留它們以保護資料。要回收這些空間,你得手動確認:docker volume ls --filter dangling=true,然後依名稱刪除。或者用 docker compose down -v 清掉整個專案的 Volume——前提是你確定資料不需要保留。

根據 Distr 團隊的實際運維經驗,光是在每次 Compose Up 時加上 RemoveOrphans: true,就消除了大量「舊版服務還在 port 8080 上回應」的客服工單。

第二個坑:永不停止增長的磁碟

每一次 docker compose pull 都會保留之前的 image。每一個使用預設 json-file 日誌驅動的容器,都會把無限制的 JSON 寫進 /var/lib/docker/containers/<id>/<id>-json.log

這是最常見的生產事故原因之一:磁碟滿了,Docker 無法寫入任何東西——日誌、中繼資料、image 層——然後容器開始以各種神祕的方式掛掉。

第一個要學的指令:

docker system df
docker system df -v

-v 參數會把每個 image、容器、Volume、Build Cache 的用量打散,通常一眼就能看出元凶在哪。接著是針對性的清理:

docker image prune -a --filter "until=168h" -f   # 刪除超過 7 天未使用的 image
docker container prune -f                         # 移除已停止的容器
docker builder prune -f                           # 清除 BuildKit cache

磁碟問題的另一半是日誌。在 /etc/docker/daemon.json 中設定日誌上限,一次搞定:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

重啟 Docker daemon 後,每個新建的容器會在日誌達到 10 MB 時自動輪替,最多保留 3 個舊檔——也就是 30 MB 的上限。既有的容器需要重建才會套用新設定。

Philip 的建議很直接:「這是值得在上線前先搞定的主題之一。」

第三個坑:Health Check 不會自動重啟

這大概是最讓人意外的一個。

你在 Dockerfile 加了 HEALTHCHECK,或者在 Compose 的 healthcheck: 區塊做了設定,然後看著容器狀態從 healthy 變成 unhealthy——然後,什麼都沒發生。Docker Engine 只回報狀態,它並不會採取任何行動。restart: unless-stopped 是容器退出時觸發的,不是容器被標記為不健康時觸發的。

你可以用這個指令確認 Docker 實際的判斷:

docker inspect --format='{{json .State.Health}}' <container> | jq

你會看到狀態、連續失敗次數、最近幾次探測結果——但這些資訊只是供你參考,Docker 自己不會用它來修復任何東西。

解決方法有幾種:用 docker events --filter 'event=health_status: unhealthy' 監聽事件,然後串接重啟腳本;或者用 systemd 管理容器,unit 檔案中設定 Restart=always 加上 ExecStartPre 的健康檢查腳本;最簡單的方式是引入一個輕量級的 agent 來處理,例如 Watchtower 或 Distr 的 Docker agent,它們有能力在檢測到不健康狀態時自動重啟容器。

第四個坑:Image 標籤的流沙

:latest 標籤看起來方便,但在 Production 環境中,它是一場災難的邀請函。

今天 deploy 的時候還好好的,明天因為某個相依套件更新,同樣的 docker compose pull 拿到的 image 可能就不一樣了。沒有版本鎖定,你就無法重現任何一個歷史狀態。

解法:永遠使用具體的版本標籤,並且在 CI/CD 流程中強制執行。像 GitHub Container Registry、Docker Hub 都支援多標籤,所以你可以同時打上 :v1.2.3:latest,但 deploy 的時候只用具體版本。

如果需要 rollback,你會感謝幾個月前那個堅持寫版本號的自己。

第五個坑:Docker Socket 的安全風險

把 Docker socket (/var/run/docker.sock) 掛載進容器是一個經典的「我知道這不對但我趕時間」的解決方案。

一旦容器內部有了 socket 存取權限,它就等同於擁有了整個 Docker daemon 的控制權——可以啟動任何 container、掛載任何目錄(包括宿主機的 /etc)、甚至可以刪除其他容器。這在開發環境也許還好,但在 Production 環境,這等於開了後門給任何能入侵這個容器的人。

如果必須讓容器管理 Docker,可以考慮更安全的替代方案:
– 用 Docker API 的 TLS 認證方式,限制 API 存取範圍
– 使用 Docker-in-Docker(DinD)或 Sysbox runtime 來隔離權限
– 或是在容器內部透過 SSH 連接到宿主機執行有限的操作

第六個坑:缺少自動清理與自動治癒

Compose 不會自動清理任何東西。它不會幫你刪除沒用到的 image,不會清除過期的 Build Cache,不會自動重啟不健康的容器。這些都是你在規劃 Compose Production 部署時必須補上的運維缺口。

你可以選擇手動維護,每月排程執行清理腳本;也可以引入輕量級的工具來補強:

那 Kubernetes 呢?

很多人把 Compose vs Kubernetes 視為二選一的問題,但這兩個工具解決的是不同的問題。

Kubernetes 擅長的是多節點、高可用、自動容錯的分散式架構。但它的代價是顯而易見的:學習曲線陡峭、運維複雜度高、對硬體資源的需求大。如果你只需要在一個節點上跑三五個服務,直接上 Kubernetes 就像開卡車去巷口買便當——能到,但不太對。

Docker Compose 的甜蜜點剛好是 Kubernetes 覆蓋不到的領域:邊緣運算、客戶端部署、小型團隊的內服務。「小巧、運維負擔輕」是它的優勢,但這也意味著運維工作不會自己消失。

如何在 Production 中用穩 Docker Compose

總結實務上最關鍵的幾點:

1. 永遠用 --remove-orphans
這是最低成本換來最高回報的一個習慣。把它寫進 deploy 腳本裡,不用多想。

2. 設定日誌上限
daemon.json 中設定 log rotation,避免磁碟被吃滿。比事後清倉更容易。

3. 補上自動重啟機制
無論是用 systemd 還是第三方工具,確保 container 不健康時會被自動重啟。

4. 鎖定 image 版本
絕對不要用 :latest。用具體版本標籤,確保每次部署都可預期。

5. 建立定期清理排程
每月執行 docker system prune 系列指令,或用自動化工具代勞。

回到最初的問題:Docker Compose 能不能上 Production?答案是肯定的,但它像一輛手排車——性能可能比自排車更好,但前提是你要知道怎麼開。

這也不只是 Docker Compose 的問題,而是整個 DevOps 工具鏈在 2026 年的一個縮影。每一層抽象都在簡化某個操作的同時,把複雜性推到了另一個地方。Kubernetes 簡化了叢集管理,但帶來了巨大的學習成本;Compose 簡化了多容器編排,但把運維細節留給了你;無伺服器簡化了基礎設施管理,但代價是靈活性與成本可控性。

對台灣的團隊來說,這其實透露了一個更深層的現象:工具選擇的討論,往往陷入了「重形式輕實務」的陷阱。我在一些聚會中聽到不少團隊,明明只是三五個服務、一台機器搞定的事,卻因為「大家都用 K8s」而把架構搞得異常複雜,反而讓最理解業務的人花大量時間在工具維運上。

這不只是 Docker Compose 的問題,而是整個 DevOps 工具鏈在 2026 年的一個縮影。每一層抽象都在簡化某個操作的同時,把複雜性推到了另一個地方。Kubernetes 簡化了叢集管理,但帶來了巨大的學習成本;Compose 簡化了多容器編排,但把運維細節留給了你;無伺服器簡化了基礎設施管理,但代價是靈活性與成本可控性。

最關鍵的或許不是找到「最好」的工具,而是清楚知道你的工具做不到什麼,並補上那些缺口。在這方面,Compose 至少做對了一件事:它把所有不足都擺在明處,不會讓你產生它什麼都能做的錯覺。而這種誠實,在現代雲端工具生態中,其實比你想像的還要珍貴。