Docker Compose 是用一個 YAML 檔描述「一整套容器服務」的工具:服務、網路、儲存卷、密鑰、甚至 AI 模型都寫在同一個 compose.yaml,再用 docker compose up -d 一次起停。它現在是 v5.6.1(GitHub 38,491 星、Apache-2.0),2026 年新增的 pre_start、watch、models 已經讓它從「開發用的小工具」變成能管生產環境的規格。
站上二十幾篇自架教學——Tailscale、Caddy、Traefik、Nextcloud、Paperless、Uptime Kuma、Jellyfin、Syncthing——每一個的安裝步驟都是「貼一段 compose.yaml」。但很少有人回頭講清楚:這段 YAML 為什麼這樣寫、depends_on 到底保證了什麼、.env 跟 secrets 差在哪、以及 2026 年的 Compose 已經多了哪些以前要另外裝工具才有的功能。
這篇就是補上那一段。所有語法都對照 Docker 官方文件(2026-10-26 查證),範例可直接複製。
一表看懂:Compose 的現況與定位
| 項目 | 內容 |
|---|---|
| 專案 | docker/compose(Go 撰寫) |
| 星數/授權 | 38,491 星、5,887 forks、Apache-2.0(2026-10-26 以 GitHub API 查詢) |
| 最新版本 | v5.6.1(2026-10-21 發布)、v5.6.0(10-02)、v5.5.1(09-03) |
| 檔案名稱 | 建議 compose.yaml(也接受 compose.yml、docker-compose.yaml) |
| 需要的主題 | 不需要;version: 頂層欄位已被規格標示為 obsolete,別再寫 |
| 適用場景 | 單機多容器、開發環境、小型生產、CI |
| 不適用場景 | 多節點排程、跨機器自動擴縮(要 Swarm/Kubernetes,或改用 Compose Bridge 轉 manifest) |
先講一個很多人卡住的觀念:Compose 不是容器,是「宣告」。 它把你要的狀態寫成文件,然後比對現況、算出差異、只動需要動的地方。所以 docker compose up -d 第二次執行幾乎不會有任何動作——這不是它偷懶,是它正確。
Compose 應用模型:五個頂層元素
一份 compose.yaml 的骨架只有這幾層:
| 頂層元素 | 作用 |
|---|---|
services | 每個容器(或 provider 能力)的定義 |
networks | 自訂網路;沒寫也會自動建一個 <專案名>_default |
volumes | 具名儲存卷,資料放在 Docker 管理區而非容器內 |
configs | 非敏感設定檔,直接掛進容器 |
secrets | 敏感資料,掛在 /run/secrets/<名稱> |
models | AI 模型宣告(2026 新制,稍後詳談) |
同一網路內的服務,可以直接用服務名稱當主機名互連。這就是為什麼 Nextcloud 的設定裡資料庫主機可以只寫 db、Redis 只寫 redis——不用管 IP、不用管容器重啟後位址變了。
最小可用範例:WordPress + MySQL
以下這份可以直接存成 compose.yaml 執行,是理解全部概念的最小樣本:
services:
db:
image: mysql:8.4
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD_FILE: /run/secrets/db_password
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u root -p$$(cat /run/secrets/db_root_password) --silent"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
secrets:
- db_root_password
- db_password
wordpress:
image: wordpress:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
restart: true
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_root_password:
file: ./db_root_password.txt
db_password:
file: ./db_password.txt
volumes:
db_data:
三個重點:
$$是跳脫符號。Compose 會先用$做變數插值,要傳「字面上的錢字號」給容器時必須寫$$,上面 healthcheck 裡的$$(cat ...)就是這個原因。_FILE結尾的環境變數是官方映像的慣例,不是 Docker 的功能。MySQL、Postgres 這類映像看到MYSQL_ROOT_PASSWORD_FILE會自己去讀檔,所以密碼不會出現在docker inspect的 env 裡。db_data這種具名 volume 第一次建立時擁有者是 root;如果服務用非 root 使用者跑,就會撞權限——這個問題下面用pre_start解。
指令表:日常真正會用到的 12 個
| 指令 | 用途 | 備註 |
|---|---|---|
docker compose up -d | 背景啟動全套 | 有異動的容器才會重建 |
docker compose up --wait | 啟動並等到 healthy | 隱含 detached,CI 首選 |
docker compose up --watch | 啟動+監看檔案變更 | 需develop.watch 設定 |
docker compose ps | 看狀態與 health | -a 含已停止 |
docker compose logs -f web | 追單一服務日誌 | --tail=100 限行數 |
docker compose exec web sh | 進入執行中的容器 | 服務必須是 running |
docker compose run --rm web sh | 跑一次性指令 | 不會覆蓋既有容器 |
docker compose config | 印出合併後的最終設定 | 排錯第一招,看插值結果 |
docker compose config --quiet | 只驗證語法 | 適合放進 CI |
docker compose pull | 拉新映像 | 配合 up -d 才是完整更新 |
docker compose restart web | 重啟(不重建) | 改 image 無效 |
docker compose down | 停並移除容器/網路 | -v 會一併刪 volume |
最後一項是本站最常被問到的資料遺失原因:docker compose down -v 的 -v 是「把具名與匿名 volume 一起刪掉」。資料庫、Nextcloud 檔案、Uptime Kuma 的 kuma.db 全都在 volume 裡,按下去就沒了。備份策略請看文末的延伸閱讀。
啟動順序:depends_on 到底保證什麼
這是誤解率最高的一條。官方文件寫得很清楚:啟動時 Compose 只等到容器「running」,不等「ready」。 所以資料庫容器已經 running、但還沒能接受連線時,應用服務照樣會被拉起來,然後連線失敗。
解法是把 condition 疊上去:
| condition | 意思 | 適用 |
|---|---|---|
service_started | 只要啟動就往下走(預設行為) | 對相依性不敏感的服務 |
service_healthy | 等 healthcheck 通過 | 資料庫、訊息佇列 |
service_completed_successfully | 等它執行完成且 exit 0 | 一次性遷移、seed 資料 |
加上 restart: true(如上面範例)還有一層效果:當 db 因為 docker compose restart 之類的操作被重啟時,web 會跟著被重啟,避免應用握著斷掉的連線不放。
healthcheck 的四個時間參數建議這樣理解:interval(多久檢查一次)、timeout(單次多久算失敗)、retries(連續失敗幾次判定 unhealthy)、start_period(啟動寬限期,這段時間內的失敗不計入 retries)。資料庫這種要跑初始化腳本的服務,start_period 給 30 秒以上是常態。
profiles:一份檔案管 dev 與 prod
不要為了「生產環境不想開 phpMyAdmin」而複製兩份 compose.yaml。用 profiles:
services:
backend:
image: myapp-backend
db:
image: postgres:18
phpmyadmin:
image: phpmyadmin
depends_on: [db]
profiles: [debug]
mailhog:
image: mailhog/mailhog
profiles: [debug, mail]
行為規則:
- 沒有
profiles的服務永遠啟用(backend、db屬於這類,建議核心服務都不要掛 profile)。 docker compose --profile debug up才會拉起phpmyadmin。- 可以用多個
--profile,或COMPOSE_PROFILES=debug,mail環境變數。 --profile "*"一次開全部。- 在指令列直接點名某個有 profile 的服務(例如
docker compose run phpmyadmin)時,Compose 會自動啟用它的 profile,不用手動加參數。
pre_start:2026 最值得學的新功能
以前要「在服務啟動前跑資料庫遷移」,標準做法是把它寫成另一個服務、restart: "no",再讓主服務用 depends_on: condition: service_completed_successfully 等它。缺點是 docker compose ps 會多一個 exited 的服務、多步驟時 depends_on 會變成一張網。
現在改用 init container(Compose 稱之為 pre_start 生命週期掛鉤)。這個功能是 v5.3.0(2026-07-02) 才引入的原生支援,舊版會直接回 additional properties 'pre_start' not allowed——升版前先確認 docker compose version:
services:
app:
image: myapp:latest
depends_on:
db:
condition: service_healthy
pre_start:
- command: ["./manage.py", "migrate"]
- command: ["./manage.py", "loaddata", "fixtures.json"]
db:
image: postgres:18
它的運作規則(官方文件明列):
- 每一步都在獨立的臨時容器裡跑,依序執行,前一步 exit 0 才跑下一步。
- 預設繼承服務的 image,可用
image:覆寫。 - 加入與服務相同的網路,所以能連到
depends_on的服務。 - 共享服務的 volume 掛載,寫入的檔案立刻對服務可見。
- 任一步非 0 退出 → 服務不啟動,且 Compose 會保留該 hook 容器讓你看日誌:
docker ps -a --filter label=com.docker.compose.hook=pre_start,再docker logs <id>。
第二個殺手級用法是修 volume 權限。具名 volume 預設 root 擁有,非 root 服務寫不進去:
services:
app:
image: myapp:latest
user: "1000:1000"
volumes:
- data:/data
pre_start:
- image: busybox
user: root
command: sh -c 'chown -R 1000:1000 /data'
volumes:
data:
限制也要知道:pre_start 每個服務只跑一次,不是每個 replica 一次(per_replica: true 尚未支援);擴容(scale up)不會重新觸發;只有在定義變更、上次失敗、或 --force-recreate 時才會重跑。至於靜態檔案與密鑰,官方明確建議直接用 configs/secrets,不需要動用 init container。
watch:把 bind mount 換成有規則的同步
開發時掛 bind mount 很常見,但整個目錄掛進容器有兩個副作用:node_modules 這種大量小檔會拖慢 I/O,而且主機與容器的作業系統/架構不同時,編譯產物不能共用。
Compose 的解法是 develop.watch:
services:
web:
build: .
command: npm start
develop:
watch:
- action: sync
path: ./web
target: /src/web
initial_sync: true
ignore:
- node_modules/
- action: sync+restart
path: ./proxy/nginx.conf
target: /etc/nginx/conf.d/default.conf
- action: rebuild
path: package.json
三種 action 的分工:
| action | 行為 | 典型用途 |
|---|---|---|
sync | 把變更檔案同步進容器,不重啟 | 支援 hot reload 的框架(Vite、Flask) |
sync+restart | 同步後重啟主程序 | 改 nginx.conf、資料庫設定 |
rebuild | 重新 build image 並換掉容器 | 改 package.json、requirements.txt |
幾個實務細節:watch 只對用 build: 建置的服務有效(用 image: 的服務不會被追蹤);容器內需要有 stat、mkdir、rmdir 三個執行檔;容器使用者必須能寫入目標路徑(Dockerfile 用 COPY --chown 處理);glob 不支援,且 .dockerignore 的規則會生效。預設每次 rebuild 會清掉上一版 dangling image,要保留用 docker compose watch --prune=false。
.env 與 secrets:兩種完全不同的東西
這是最常被搞混的一組。用一張表分開:
.env(專案目錄) | env_file:(服務層級) | secrets: | |
|---|---|---|---|
| 給誰用 | Compose 自己做 ${VAR} 插值 | 注入容器的環境變數 | 掛成容器內的檔案 |
會不會出現在 docker inspect | 插值後會(成為容器 env) | 會 | 不會 |
| 適用 | 連接埠、映像 tag、路徑 | 非敏感的應用設定 | 密碼、API key、憑證 |
| 平台限制 | 無 | 無 | 僅 Linux 容器(靠 bind mount 單檔實作) |
secrets 的價值在於它不是環境變數。環境變數對所有行程可見、出錯時常被印進日誌,而 secret 是一個只有指定服務能讀的檔案,權限可控。官方範例的用法就是 file: 指向本機檔案,或 environment: NPM_TOKEN 從環境變數取値——後者常用在 build secret(build.secrets),避免把 npm token 寫進 image layer。
實務建議:.env 只放「就算外流也不太痛」的東西,真正的憑證走 secrets,並記得 .gitignore。順帶一提,Compose 有自己的信任模型(trust model)文件,講清楚它假設「Compose 檔案與執行它的使用者是同一個信任邊界」——所以不要把來路不明的 compose.yaml 直接 up。
把 AI 模型寫進 compose.yaml
2026 年 Compose 最明顯的變化,是它開始把 AI 模型當成一等公民。用頂層 models 宣告,服務再用 models: 綁定:
services:
chat-app:
image: my-chat-app
models:
llm:
endpoint_var: AI_MODEL_URL
model_var: AI_MODEL_NAME
models:
llm:
model: ai/smollm2
context_size: 4096
runtime_flags:
- "--temp"
- "0.1"
短語法(只寫 - llm)會自動產生 <名稱>_URL 與 <名稱>_MODEL 兩個環境變數,長語法可以自訂變數名稱。執行時由平台負責「拉模型、跑起來、把 endpoint 注入服務」——在 Docker Model Runner 啟用的環境下,這段就是本機 LLM 的標準啟動方式。
官方標示的前置條件是 Compose v2.38 以上(現行版本已到 v5.6.1)+ 支援 Compose 模型的平台。runtime_flags 直接透傳給推理引擎(llama.cpp/vLLM),所以溫度、top-k、--embeddings(embedding 模型必加)、--threads 都能在 YAML 裡宣告,不用再寫一層啟動腳本。
同一脈絡下還有 provider services:服務可以宣告 provider: {type: ...},代表這個「服務」不是容器,而是由第三方外掛提供的平台能力,Compose 負責呼叫它、拿到連線資訊、注入環境變數給依賴它的服務。例如 type: model 會去找 docker-model 外掛或 PATH 上的 model 執行檔。對自架族的意義是:基礎設施依賴開始可以宣告在同一份文件裡,不必再手寫 shell 去等外部資源。
多環境:override 檔與 include
三種合併方式,選一種就好:
| 方式 | 寫法 | 適用 |
|---|---|---|
| 自動 override | 放一個 compose.override.yaml,up 時自動疊上 | 本機開發差異 |
手動 -f | docker compose -f compose.yaml -f compose.production.yaml up -d | 明確的環境分層 |
include | include: [{path: ../shared/monitoring.yaml}] | 跨團隊共用的子堆疊 |
生產環境的調整項目,官方建議是:移除應用程式的 bind mount(讓程式碼鎖在 image 裡,外部改不到)、換 host 連接埠、降低日誌詳細度、加上 restart 政策、補上日誌聚合器。這些寫進 compose.production.yaml 就好,不用複製整份檔案。
只更新單一服務時,標準兩步是:
docker compose build web
docker compose up --no-deps -d web
--no-deps 是關鍵,否則 Compose 會把你相依的資料庫等服務一起重建。遠端部署則只要設好 DOCKER_HOST、DOCKER_TLS_VERIFY、DOCKER_CERT_PATH,所有 docker compose 指令原封不動就能對遠端主機操作。
要上 Kubernetes?用 Compose Bridge
如果同一份 compose.yaml 遲早要搬到 K8s,官方有一條轉換路徑:Compose Bridge 會把 Compose 模型轉成 Kubernetes manifest 與 Kustomize overlay。
它的機制是把「轉換」包成一個 Docker image:輸入是解析後的 Compose 模型(/in/compose.yaml),輸出是任何格式(/out)。預設的 K8s 轉換用 Go template 寫,平台團隊可以自訂 template,把安全上下文、資源限制、labels、network policy 一次注入——開發者照樣寫 Compose,治理規則集中在一份 template 裡改一次、全部團隊下次轉換就生效。它也支援搭配 Docker Model Runner 的 LLM 應用。
12 個實際踩雷
| 踩雷 | 症狀 | 解法 |
|---|---|---|
down -v 當成 down | 資料庫、檔案全消失 | 備份 policy 先做;只停服務用 stop |
depends_on 沒加 condition | 應用先啟動、連線失敗 | 用 service_healthy + healthcheck |
healthcheck 裡的 $ 沒跳脫 | 變數被 Compose 吃掉,指令壞掉 | 寫 $$ |
| 非 root 服務寫不進 volume | permission denied | pre_start 跑 chown,或調整 image 使用者 |
改了 compose.yaml 卻沒生效 | 舊設定繼續跑 | 確認沒加 --no-recreate;必要時 --force-recreate |
restart 當成「重新讀 image」 | 更新 image 後版本沒變 | pull + up -d,或 build + up -d |
.env 放了資料庫密碼 | 密碼出現在 inspect/日誌 | 改用 secrets |
| 兩個專案同名 | 容器/網路互撞 | 用 -p 指定專案名,或 COMPOSE_PROJECT_NAME |
只開 expose 沒開 ports | 主機連不到 | 對外要 ports,expose 只給同網路 |
watch 在有 image: 的服務上沒反應 | 檔案改了但沒同步 | watch 只支援 build: 的服務 |
| 專案名取自資料夾 | 換目錄就變新專案、volume 重建 | 明確設 name: 或 -p |
| 把 Compose 當 K8s 用 | 沒有跨節點、沒有自動修復 | 單機夠用就好;要多節點走 Swarm/Bridge |
該不該用 Compose?三個對照
| 需求 | Compose | Kubernetes | 純 docker run |
|---|---|---|---|
| 單機多容器 | ✅ 最適 | 過重 | 要自己寫腳本 |
| 宣告式設定進版控 | ✅ 一份 YAML | ✅ | ❌ |
| 跨節點排程/自動擴縮 | ❌ | ✅ | ❌ |
| 學習成本 | 低 | 高 | 最低 |
| 從開發到生產同一份設定 | ✅ | 部分 | ❌ |
一句話:一台機器上跑得動的服務,用它;要跨機器、要自動排程,再往上換。 中間的過渡期就用 Compose Bridge 轉 manifest,不必重寫。
站內延伸閱讀
- Traefik 完整教學 2026:Docker Labels 自動路由與自動 HTTPS——Compose 起服務、Traefik 用 labels 自動配路由,是本站最常見的組合。
- Caddy 完整教學 2026:Caddyfile、反向代理到 Docker 部署——服務少於五個時的更輕選擇。
- Uptime Kuma 完整教學 2026:Docker 安裝、狀態頁與 Telegram 通知——把
kuma.db備份講清楚的那篇,搭配本文的down -v警告一起看。 - Nextcloud 完整教學 2026:Docker Compose 安裝、Redis 快取到 S3 外部儲存——多服務 compose 的真實規模範例。
- Nginx 反向代理完整指南 2026——
sync+restart最常改的那個nginx.conf怎麼寫。
資料來源與查證方法
- 一手來源:Docker 官方文件
docs.docker.com/compose/(來源 repodocker/docs,Apache-2.0),逐篇對照compose-application-model、startup-order、profiles、init-containers、file-watch、use-secrets、models-and-compose、provider-services、production、multiple-compose-files、bridge與docker compose upCLI 參考頁。 - 版本與星數:
api.github.com/repos/docker/compose(38,491 星、5,887 forks、Apache-2.0)與 releases API(v5.6.1,2026-10-21),查證時間 2026-10-26。 - 官方自述 vs 本文補充:
compose.yaml為建議檔名、version:已 obsolete、pre_start的per_replica尚未支援、secrets僅支援 Linux 容器、watch只追build:服務,以上皆為官方文件明載。表格中「適用場景」的判斷、踩雷清單與最後的三方對照表,是本文依實務經驗整理的建議,不是官方立場。 - 原始來源連結:
- Compose 官方文件總覽:https://docs.docker.com/compose/
- 應用模型(services/networks/volumes):https://docs.docker.com/compose/intro/compose-application-model/
- 定義 AI 模型(
models):https://docs.docker.com/ai/compose/models-and-compose/ - init container(
pre_start):https://docs.docker.com/compose/how-tos/init-containers/ - 啟動順序與 healthcheck:https://docs.docker.com/compose/how-tos/startup-order/
- Compose Watch:https://docs.docker.com/compose/how-tos/file-watch/
- profiles:https://docs.docker.com/compose/how-tos/profiles/
- secrets:https://docs.docker.com/compose/how-tos/use-secrets/
- provider services:https://docs.docker.com/compose/how-tos/provider-services/
- 多檔案與 include:https://docs.docker.com/compose/how-tos/multiple-compose-files/
- 生產環境設定:https://docs.docker.com/compose/how-tos/production/
- Compose Bridge:https://docs.docker.com/compose/bridge/
docker compose upCLI 參考:https://docs.docker.com/reference/cli/docker/compose/up/- 原始碼與版本:https://github.com/docker/compose/releases
- 未實測部分:本文未在真實生產環境跑過
models與provider services(需要 Docker Model Runner 與外掛平台),語法與前置條件取自官方文件。YAML 片段已用本機docker compose config(v5.1.0)做語法驗證:WordPress 範例、watch、models三塊通過;pre_start兩塊在本機版本回additional properties 'pre_start' not allowed,原因是該功能自 v5.3.0(2026-07-02) 才引入、本機版本早於它,語法本身依官方文件撰寫,但未在支援的版本上實跑。實際行為仍以你環境的 Compose 版本為準。
