Docker Compose 多容器部署教學:從入門到實戰
還記得第一次用 Docker 部署應用的時候嗎?一個容器跑一個服務,感覺挺簡單的。但等到你的專案開始長大,需要資料庫、快取、訊息佇列、反向代理……一堆服務要同時跑的時候,手動管理每個容器就變成了一場噩夢。這就是 Docker Compose 存在的意義——它讓你用一個 YAML 檔案就能定義和管理整個多容器應用。
為什麼需要 Docker Compose
我先說個真實的故事。之前在一個專案中,我需要同時跑 Node.js API 伺服器、PostgreSQL 資料庫、Redis 快取和 Nginx 反向代理。一開始我用四個獨立的 docker run 指令來啟動它們,每次重啟都要記住一堆參數,還要手動設定網路讓它們能互相通訊。有一次不小心打錯參數,整個環境就壞了,花了快一個小時才修好。
後來改用 Docker Compose 之後,所有設定都寫在一個 docker-compose.yml 檔案裡,一個 docker compose up 指令就能把所有服務跑起來。要停掉的時候,docker compose down 一秒搞定。再也不用記那些複雜的參數了。
除了方便之外,Docker Compose 還有幾個重要的優勢。第一,它提供了一致的開發環境,團隊裡每個人拿到同一份 docker-compose.yml 就能跑出一模一樣的環境。第二,它自動幫你建立網路,同一個 Compose 檔案裡的服務可以直接用服務名稱互相存取。第三,它支援服務之間的相依關係設定,確保服務按照正確的順序啟動。
docker-compose.yml 基本結構
一個 docker-compose.yml 檔案的基本結構其實很直覺。讓我們從最簡單的範例開始。
version: '3.9'
services:
web:
image: node:20-alpine
ports:
- "3000:3000"
volumes:
- ./src:/app/src
environment:
- NODE_ENV=production
depends_on:
- db
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=secret123
volumes:
pgdata:這個範例定義了兩個服務:一個 Node.js 的 web 服務和一個 PostgreSQL 的 db 服務。depends_on 告訴 Compose,web 服務需要等 db 服務啟動後才能啟動。底下的 volumes 區塊定義了一個具名的 volume,用來持久化資料庫的資料。
不過這裡有一個常見的陷阱要注意:depends_on 只會等容器「啟動」,不會等服務「準備好」。也就是說,db 容器啟動了不代表 PostgreSQL 已經可以接受連線。這個問題我們後面會用 health checks 來解決。
服務定義與映像檔設定
在服務定義中,你可以選擇使用現成的映像檔,或者用 Dockerfile 來建立自己的映像檔。實務上兩種方式經常混用——自己開發的服務用 Dockerfile,第三方的服務(像資料庫、快取)直接用官方映像檔。
services:
api:
build:
context: ./api
dockerfile: Dockerfile
args:
- BUILD_ENV=production
ports:
- "8080:8080"
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
restart: alwaysrestart 策略也很重要。unless-stopped 表示除非你手動停止,否則容器掛了會自動重啟。always 則是不管什麼情況都重啟。對於生產環境,我通常建議對核心服務用 always,非核心服務用 unless-stopped。如果你的 API 服務有遵循REST API 設計最佳實踐,那搭配適當的 restart 策略可以確保服務的高可用性。
Networks 網路配置
Docker Compose 預設會為整個專案建立一個 bridge 網路,所有服務都能透過服務名稱互相存取。但在比較複雜的架構中,你可能會想把不同的服務隔離到不同的網路。
services:
web:
networks:
- frontend
- backend
api:
networks:
- backend
db:
networks:
- backend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true在這個例子中,web 服務同時連接 frontend 和 backend 兩個網路,所以它可以跟外部世界通訊,也可以跟 api 和 db 通訊。但 db 只在 backend 網路中,外部完全無法直接存取它。internal: true 的設定更進一步禁止了 backend 網路的對外連線,提供了額外的安全性。
網路隔離在安全性上非常重要。你不會希望資料庫直接暴露在外部網路上,對吧?如果你在處理身份驗證相關的服務,搭配JWT 身份驗證教學可以讓你的微服務架構更加安全。
Volumes 資料持久化
容器本身是暫時的——當你刪除容器後,裡面的資料就消失了。如果你不想每次重啟都遺失資料庫的資料,就需要使用 volumes。
services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
app:
volumes:
- ./src:/app/src
- /app/node_modules
volumes:
pgdata:
driver: local這裡有兩種不同類型的 volume。pgdata:/var/lib/postgresql/data 是具名 volume,Docker 會自動管理它的儲存位置。./src:/app/src 是綁定掛載(bind mount),直接把主機上的目錄對應到容器裡。
開發的時候,bind mount 特別好用,因為你改了程式碼之後容器裡面立刻就會更新。但在生產環境中,建議用具名 volume 來管理資料,這樣比較安全也比較好管理。注意那個 :ro 後綴,它表示唯讀掛載,可以防止容器意外修改主機上的檔案。如果你在選擇資料庫方案,可以看看PostgreSQL vs MySQL 比較來決定哪個更適合你的專案。
Health Checks 健康檢查
前面提到 depends_on 只會等容器啟動,不會等服務真正準備好。Health checks 就是用來解決這個問題的。
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin -d myapp"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
api:
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3透過 condition: service_healthy,api 服務會等到 db 的健康檢查通過後才啟動。start_period 給了 PostgreSQL 30 秒的緩衝時間來完成初始化,在這段時間內的健康檢查失敗不會計入重試次數。
我的建議是,每個服務都應該要有 health check。這不只是為了啟動順序,更是為了讓你隨時都能知道服務的狀態。當某個服務不健康的時候,Docker 可以自動重啟它。
環境變數管理
把機密資訊直接寫在 docker-compose.yml 裡面是一個很糟糕的做法。正確的方式是使用 .env 檔案或者外部的 secrets 管理工具。
# .env 檔案
POSTGRES_DB=myapp
POSTGRES_USER=admin
POSTGRES_PASSWORD=super_secret_password
REDIS_URL=redis://redis:6379
API_PORT=8080services:
db:
image: postgres:16
env_file:
- .env
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
api:
env_file:
- .env
ports:
- "${API_PORT}:${API_PORT}"記得把 .env 加到 .gitignore 裡面!這一點千萬不能忘記。你可以提供一個 .env.example 檔案,列出需要的環境變數但不包含實際的值,方便其他開發者設定自己的環境。
多階段部署策略
在實務中,你可能需要不同環境(開發、測試、生產)使用不同的配置。Docker Compose 支援多個檔案的覆蓋機制,讓你可以優雅地處理這個需求。
# docker-compose.yml(基礎設定)
services:
api:
build: ./api
environment:
- NODE_ENV=production
# docker-compose.override.yml(開發環境覆蓋)
services:
api:
volumes:
- ./api/src:/app/src
environment:
- NODE_ENV=development
- DEBUG=true
ports:
- "9229:9229" # debug portDocker Compose 會自動合併 docker-compose.yml 和 docker-compose.override.yml。在開發環境中,它會自動使用覆蓋的設定。部署到生產環境時,你可以明確指定只用基礎檔案:docker compose -f docker-compose.yml -f docker-compose.prod.yml up。
2026 年最佳實踐
隨著 Docker 生態系統的持續發展,2026 年有一些新的最佳實踐值得關注。
首先,Docker Compose V2 現在已經是預設版本,指令從 docker-compose 變成了 docker compose(注意是空格不是連字號)。如果你還在用 V1,強烈建議升級。
其次,善用 profiles 來管理不同場景的服務組合。比如,你可能只有在開發的時候才需要跑 Mailhog 這種郵件測試工具,就可以用 profile 來控制。
services:
mailhog:
image: mailhog/mailhog
profiles:
- dev
ports:
- "8025:8025"啟動的時候用 docker compose --profile dev up 就能包含這些開發專用的服務。
另外,現在 Docker 對 Apple Silicon(M 系列晶片)的支援已經非常成熟了。如果你的團隊中有人用 ARM 架構的機器,記得在 build 的時候指定 platform,或者使用 multi-arch 映像檔,確保跨平台相容性。如果你的前端專案用到了最新的框架,可以參考React 19 新功能教學,搭配 Docker Compose 打造完整的全端開發環境。
常見問題與解決方案
最後來聊聊一些我和身邊的開發者朋友們經常遇到的問題。
第一個是連接埠衝突。如果你本機已經有個 PostgreSQL 跑在 5432 埠,Docker 裡的 PostgreSQL 就沒辦法再綁定同一個埠。解決方式是改用不同的主機埠,例如 "5433:5432"。
第二個是 volume 權限問題。有時候容器內的使用者和主機的使用者不一樣,會導致檔案讀寫權限的問題。解決方式是在 Dockerfile 中指定正確的使用者,或者使用 user 選項。
第三個是映像檔太大。每次 build 都下載一堆依賴,非常浪費時間和空間。善用 multi-stage build 和 .dockerignore 可以大幅減少映像檔的大小。Alpine 版本的基礎映像檔通常比完整版小很多,優先考慮使用。
Docker Compose 是現代後端開發幾乎必備的工具。掌握了這些知識之後,你應該能夠應付大多數的多容器部署場景。持續練習,遇到問題多查文件,很快你就能成為容器化部署的高手。
你可能也喜歡
探索其他領域的精選好文
LangChain vs LlamaIndex 完整比較:2026 年 RAG 框架到底該怎麼選?
在 RAG 應用開發中,LangChain 和 LlamaIndex 是最常被拿來比較的兩大框架。這篇文章從架構設計、效能數據到實戰經驗,幫你釐清到底該選哪一個。
Google SGE 對 SEO 的影響:2026 年你必須知道的因應策略
Google AI Overview 已經出現在將近一半的搜尋結果中。SEO 不會死,但規則正在改變。這篇整理最新數據和五個你現在就該開始做的因應策略。
DaVinci Resolve 免費影片剪輯入門教學:從安裝到完成第一支影片
DaVinci Resolve 是好萊塢等級的剪輯軟體,但免費版就能滿足 90% 的需求。這篇帶你從安裝開始,一步步完成第一支影片。
CSS Container Queries 教學:響應式設計的新時代終於來了
Media Queries 看的是視窗大小,但元件不一定只活在全寬的地方。Container Queries 讓你的 CSS 根據容器大小來變化,真正實現元件級的響應式設計。