任務七:Docker Compose 入門與實戰¶
開始之前¶
任務目標
在這個任務中,你將學習:
- 理解多容器管理的痛點與 Docker Compose 的解決方案
- 掌握 compose.yaml 的核心語法與設定
- 熟練使用 Docker Compose 常用指令
- 學會管理環境變數與多環境設定
- 透過實作練習部署完整的三層式應用(Web + Database + Cache)
版本確認
本教學使用 Docker Compose V2(內建於 Docker Desktop)。請確認你的 Docker 版本包含 Compose V2:
預期輸出類似:
如果你的系統只有舊版的 docker-compose(需要連字號),建議升級到最新版 Docker Desktop。
為何需要 Docker Compose¶
在前面的任務中,我們學會了操作單一容器。但在實際開發中,應用程式通常由多個服務組成:
- Web 應用程式
- 資料庫(如 PostgreSQL、MySQL)
- 快取系統(如 Redis)
- 訊息佇列(如 RabbitMQ)
手動管理多容器的痛點¶
假設我們要手動啟動一個包含 Web、資料庫和快取的應用:
# 1. 建立網路
docker network create myapp-network
# 2. 啟動資料庫
docker run -d \
--name db \
--network myapp-network \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=myapp \
-v db-data:/var/lib/postgresql \
postgres:18
# 3. 啟動 Redis
docker run -d \
--name cache \
--network myapp-network \
redis:7-alpine
# 4. 啟動 Web 應用
docker run -d \
--name web \
--network myapp-network \
-p 8000:8000 \
-e DATABASE_URL=postgresql://postgres:secret@db:5432/myapp \
-e REDIS_URL=redis://cache:6379 \
myapp:latest
這個過程有許多問題:
- 步驟繁瑣:需要記住每個容器的參數與順序
- 容易出錯:一個參數錯誤就需要重新啟動
- 難以分享:無法將設定分享給團隊成員
- 難以維護:修改設定需要重新輸入所有指令
- 啟動順序:需要手動確保服務啟動順序
Docker Compose 的解決方案¶
Docker Compose 讓你用一個 YAML 檔案定義整個應用的架構,然後用一個指令啟動所有服務:
services:
db:
image: postgres:18
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- db-data:/var/lib/postgresql
cache:
image: redis:7-alpine
web:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://postgres:secret@db:5432/myapp
REDIS_URL: redis://cache:6379
depends_on:
- db
- cache
volumes:
db-data:
啟動所有服務只需要:
優點:
- 設定即文件:
compose.yaml清楚描述整個應用架構 - 一鍵部署:單一指令啟動所有服務
- 易於分享:將
compose.yaml提交到版本控制,團隊成員都能使用相同設定 - 環境一致:確保開發、測試、生產環境使用相同的服務架構
- 自動化管理:自動建立網路、管理服務相依性
compose.yaml 語法詳解¶
compose.yaml(或 docker-compose.yml)是 Docker Compose 的設定檔,用於定義應用程式的所有服務、網路和資料卷。
services - 定義服務¶
services 是設定檔的核心區塊,定義應用程式包含哪些服務(容器)。
基本結構:
每個服務名稱會成為該容器在網路中的 DNS 名稱,其他容器可以用這個名稱存取它。
image 與 build - 指定映像來源¶
服務的容器可以來自現有映像檔,或從 Dockerfile 建構。
使用現有映像(image)¶
從 Dockerfile 建構(build)¶
services:
web:
build: .
# 從當前目錄的 Dockerfile 建構
api:
build:
context: ./backend
dockerfile: Dockerfile.dev
# 從指定目錄與 Dockerfile 建構
同時使用 image 與 build¶
ports - 埠號對應¶
ports 將容器的埠號對應到 Host,讓外部可以存取服務。
短格式¶
services:
web:
image: nginx
ports:
- "8080:80" # host:container
- "8443:443" # 多個埠號對應
- "127.0.0.1:9000:9000" # 指定 IP
長格式¶
services:
web:
image: nginx
ports:
- target: 80 # 容器埠號
published: 8080 # Host 埠號
protocol: tcp # 協定(tcp 或 udp)
mode: host # 模式(host 或 ingress)
何時使用 ports
只有需要從 Host 或外部網路存取的服務才需要設定 ports。服務間通訊不需要 port mapping,可以直接透過服務名稱與內部埠號存取。
volumes - 資料持久化¶
volumes 讓資料在容器重啟後仍然保留。
Named Volume(推薦)¶
services:
db:
image: postgres:18
volumes:
- db-data:/var/lib/postgresql
volumes:
db-data: # 在頂層宣告 volume
Bind Mount¶
services:
web:
image: nginx
volumes:
- ./html:/usr/share/nginx/html # 相對路徑
- /data/config:/etc/nginx/conf.d # 絕對路徑
volumes 長格式¶
services:
web:
image: nginx
volumes:
- type: volume
source: web-data
target: /data
- type: bind
source: ./config
target: /etc/config
read_only: true # 唯讀掛載
volumes:
web-data:
environment - 環境變數¶
environment 設定容器的環境變數。
字典格式¶
清單格式¶
使用 env_file¶
引號使用
YAML 會自動解析某些值(如 yes、no、true、false),建議用引號包裹字串值避免誤解析。
networks - 自訂網路¶
預設情況下,Compose 會自動建立一個網路並將所有服務連接到該網路。你也可以定義自訂網路實現更複雜的網路架構。
使用預設網路¶
自訂網路¶
services:
web:
image: nginx
networks:
- frontend
api:
image: myapi
networks:
- frontend
- backend
db:
image: postgres:18
networks:
- backend
networks:
frontend:
backend:
在這個範例中:
web只能存取apiapi可以存取web和dbweb無法直接存取db(提高安全性)
depends_on - 服務相依性¶
depends_on 定義服務的啟動順序。
services:
web:
image: myapp
depends_on:
- db
- cache
db:
image: postgres:18
cache:
image: redis:7-alpine
這個設定確保 db 和 cache 會在 web 之前啟動。
depends_on 的限制
depends_on 只控制啟動順序,不會等待服務「就緒」。例如,PostgreSQL 容器啟動後,資料庫可能還在初始化中。
如果需要等待服務就緒,可以:
- 在應用程式中實作重試邏輯
- 使用
depends_on的長格式(需要 Compose V2.20+):
restart - 重啟策略¶
restart 定義容器意外停止時的重啟行為。
選項說明:
no:不自動重啟(預設值)always:總是重啟,即使容器正常停止on-failure:只在容器異常退出時重啟unless-stopped:總是重啟,除非手動停止容器
使用建議:
- 生產環境的關鍵服務:使用
always或unless-stopped - 開發環境:使用
no或on-failure - 一次性任務:使用
no
Docker Compose 常用指令¶
Docker Compose 提供一系列指令來管理多容器應用。以下是最常用的指令。
docker compose up - 啟動服務¶
docker compose up 啟動設定檔中定義的所有服務。
# 啟動所有服務(前景執行,顯示所有日誌)
docker compose up
# 背景執行
docker compose up -d
# 只啟動特定服務
docker compose up -d web db
# 重新建構映像後啟動
docker compose up -d --build
# 強制重新建立容器
docker compose up -d --force-recreate
執行流程:
- 建立定義的網路
- 建立定義的 volume
- 啟動服務(依照
depends_on順序) - 顯示日誌(如果沒有使用
-d)
docker compose down - 停止並移除服務¶
docker compose down 停止並移除容器、網路。
# 停止並移除所有容器與網路
docker compose down
# 同時移除 volume
docker compose down -v
# 同時移除映像
docker compose down --rmi all
資料保存
docker compose down -v 會刪除所有 volume,包含資料庫資料。只在確定要清除所有資料時使用此選項。
docker compose ps - 查看服務狀態¶
# 列出所有服務的狀態
docker compose ps
# 列出所有容器(包含已停止的)
docker compose ps -a
# 只顯示執行中的容器 ID
docker compose ps -q
輸出範例:
NAME IMAGE COMMAND SERVICE STATUS PORTS
myapp-db-1 postgres:18 "docker-entrypoint.s…" db Up 2 minutes 5432/tcp
myapp-web-1 myapp:latest "python app.py" web Up 2 minutes 0.0.0.0:8000->8000/tcp
myapp-cache-1 redis:7-alpine "docker-entrypoint.s…" cache Up 2 minutes 6379/tcp
docker compose logs - 查看日誌¶
# 查看所有服務的日誌
docker compose logs
# 即時追蹤日誌
docker compose logs -f
# 查看特定服務的日誌
docker compose logs web
# 顯示最後 100 行日誌
docker compose logs --tail=100
# 顯示時間戳記
docker compose logs -t
docker compose exec - 在服務中執行指令¶
# 在 web 服務中執行 bash
docker compose exec web bash
# 在 db 服務中執行 SQL 指令
docker compose exec db psql -U postgres
# 執行一次性指令
docker compose exec web python manage.py migrate
# 以特定使用者身分執行
docker compose exec --user root web apt-get update
docker compose build - 建構映像¶
# 建構所有服務的映像
docker compose build
# 建構特定服務
docker compose build web
# 不使用快取建構
docker compose build --no-cache
# 平行建構(加快速度)
docker compose build --parallel
環境變數管理¶
在實際專案中,許多設定(如資料庫密碼、API 金鑰)會因環境而異。Docker Compose 提供多種方式管理環境變數。
.env 檔案¶
Compose 會自動載入專案根目錄的 .env 檔案。
範例:.env 檔案¶
# 資料庫設定
POSTGRES_VERSION=16
POSTGRES_PASSWORD=mysecret
POSTGRES_DB=myapp
# 應用程式設定
APP_PORT=8000
DEBUG_MODE=false
在 compose.yaml 中使用¶
services:
db:
image: postgres:${POSTGRES_VERSION}
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
web:
build: .
ports:
- "${APP_PORT}:8000"
environment:
DEBUG: ${DEBUG_MODE}
.env 檔案安全
.env 檔案通常包含敏感資訊,應加入 .gitignore 避免提交到版本控制。可以提供 .env.example 作為範本:
變數替換語法¶
Compose 支援多種變數替換語法:
services:
web:
image: "webapp:${TAG}" # 使用變數
image: "webapp:${TAG:-latest}" # 提供預設值
image: "webapp:${TAG-latest}" # 變數未設定時使用預設值
image: "webapp:${TAG:?err}" # 變數未設定時報錯
環境變數優先順序¶
容器內的環境變數常見來源與優先順序(由高到低):
- compose.yaml 的 environment
- env_file
- 映像檔中的 ENV 預設值
另外,compose.yaml 中的 ${VAR} 變數替換會依以下順序取值:
- Shell 環境變數
- .env 檔案
範例:
若 Shell 有 DEBUG=production,容器內的 DEBUG 會是 production;若沒有則使用 .env 或預設值 false。
多環境設定¶
實際專案中,開發環境與生產環境的設定往往不同。Compose 提供多種方式實現多環境設定。
compose.override.yaml - 自動覆蓋¶
Compose 會自動合併以下檔案:
compose.yaml(基礎設定)compose.override.yaml(自動載入的覆蓋設定)
範例:
services:
web:
build: . # 開發環境使用本地建構
volumes:
- ./src:/app/src # 掛載原始碼,實現熱重載
environment:
LOG_LEVEL: debug # 覆蓋 log level
ports:
- "8000:8000" # 開發環境才需要對外開放
執行 docker compose up 時,兩個檔案會自動合併。
override 檔案的用途
compose.override.yaml 適合:
- 開發環境專屬設定(如 volume mount、debug mode)
- 個人開發偏好設定
- 本地測試用的額外服務
此檔案通常也不提交到版本控制(加入 .gitignore)。
使用多個設定檔¶
你可以用 -f 參數明確指定要載入的設定檔:
# 使用生產環境設定
docker compose -f compose.yaml -f compose.prod.yaml up -d
# 使用測試環境設定
docker compose -f compose.yaml -f compose.test.yaml up -d
範例:生產環境設定¶
services:
web:
image: myapp:1.0.0 # 使用特定版本
restart: always # 生產環境自動重啟
deploy:
resources:
limits:
cpus: '2'
memory: 1G
db:
volumes:
- /data/postgres:/var/lib/postgresql # 使用實體路徑
範例:開發環境設定¶
services:
web:
build:
context: .
target: development
volumes:
- ./src:/app/src
environment:
DEBUG: "true"
command: python manage.py runserver 0.0.0.0:8000
設定檔命名慣例
常見的命名方式:
compose.yaml- 基礎設定compose.override.yaml- 開發環境覆蓋(自動載入)compose.prod.yaml- 生產環境compose.test.yaml- 測試環境compose.ci.yaml- CI/CD 環境
實作練習:部署三層式應用¶
現在讓我們實作一個完整的三層式應用,包含 Flask Web 應用、PostgreSQL 資料庫和 Redis 快取。
專案架構¶
應用程式碼¶
from flask import Flask, jsonify
import psycopg
import redis
import os
app = Flask(__name__)
# 資料庫連線
def get_db_connection():
conn = psycopg.connect(
host=os.getenv('DATABASE_HOST', 'db'),
dbname=os.getenv('DATABASE_NAME', 'myapp'),
user=os.getenv('DATABASE_USER', 'postgres'),
password=os.getenv('DATABASE_PASSWORD', 'secret')
)
return conn
# Redis 連線
cache = redis.Redis(
host=os.getenv('REDIS_HOST', 'cache'),
port=int(os.getenv('REDIS_PORT', 6379)),
decode_responses=True
)
@app.route('/')
def index():
return jsonify({
'message': 'Welcome to My App!',
'status': 'running'
})
@app.route('/health')
def health():
try:
# 檢查資料庫連線
conn = get_db_connection()
conn.close()
db_status = 'connected'
except Exception as e:
db_status = f'error: {str(e)}'
try:
# 檢查 Redis 連線
cache.ping()
cache_status = 'connected'
except Exception as e:
cache_status = f'error: {str(e)}'
return jsonify({
'database': db_status,
'cache': cache_status
})
@app.route('/count')
def count():
# 使用 Redis 計數器
count = cache.incr('visit_count')
return jsonify({
'visits': count
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
CMD ["python", "app.py"]
Compose 設定檔¶
services:
db:
image: postgres:18
environment:
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: ${DATABASE_NAME}
volumes:
- db-data:/var/lib/postgresql
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
web:
build: .
ports:
- "${APP_PORT:-8000}:8000"
environment:
DATABASE_HOST: db
DATABASE_NAME: ${DATABASE_NAME}
DATABASE_USER: postgres
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
REDIS_HOST: cache
REDIS_PORT: 6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
restart: unless-stopped
volumes:
db-data:
實作步驟¶
步驟 1:建立專案結構
步驟 2:建立所有檔案
將上述的 app.py、Dockerfile、requirements.txt、compose.yaml、.env 檔案建立在專案目錄中。
步驟 3:啟動所有服務
Compose 會自動:
- 建構 Web 應用的映像
- 下載 PostgreSQL 和 Redis 映像
- 建立網路
- 建立 volume
- 啟動所有服務
步驟 4:檢查服務狀態
預期所有服務的 STATUS 都是 Up。
步驟 5:測試應用程式
# 測試首頁
curl http://localhost:8000/
# 測試健康檢查
curl http://localhost:8000/health
# 測試計數器
curl http://localhost:8000/count
curl http://localhost:8000/count
curl http://localhost:8000/count
步驟 6:查看日誌
步驟 7:進入容器檢查
# 進入 web 容器
docker compose exec web bash
# 進入資料庫容器執行 SQL
docker compose exec db psql -U postgres -d myapp
# 進入 Redis 容器
docker compose exec cache redis-cli
步驟 8:停止服務
驗證重點¶
完成實作後,確認以下項目:
- 訪問
http://localhost:8000/顯示歡迎訊息 - 訪問
http://localhost:8000/health顯示資料庫與快取都已連線 - 訪問
http://localhost:8000/count計數器正常運作 - 執行
docker compose down後再docker compose up -d,所有服務正常啟動且健康檢查通過 - 執行
docker compose down -v確認 volume 已被清除(docker volume ls不再顯示db-data)
練習延伸¶
加入開發環境設定¶
建立 compose.override.yaml:
這樣可以把資料庫的埠號綁定到 Host 上,方便其他工具連線。
任務結束¶
完成!
恭喜你完成了這個任務!現在你已經學會:
- 理解多容器管理的痛點與 Docker Compose 的解決方案
- 掌握 compose.yaml 的核心語法與設定
- 熟練使用 Docker Compose 常用指令
- 學會管理環境變數與多環境設定
- 透過實作練習部署完整的三層式應用(Web + Database + Cache)
你現在已經掌握 Docker Compose 的核心技能,能夠用單一設定檔管理複雜的多容器應用。這是從開發邁向生產環境的重要里程碑!在實際專案中,Docker Compose 能大幅簡化本地開發環境的建立,確保團隊成員都能使用一致的開發環境。