跳轉到

任務七:Docker Compose 入門與實戰

開始之前

任務目標

在這個任務中,你將學習:

  • 理解多容器管理的痛點與 Docker Compose 的解決方案
  • 掌握 compose.yaml 的核心語法與設定
  • 熟練使用 Docker Compose 常用指令
  • 學會管理環境變數與多環境設定
  • 透過實作練習部署完整的三層式應用(Web + Database + Cache)

版本確認

本教學使用 Docker Compose V2(內建於 Docker Desktop)。請確認你的 Docker 版本包含 Compose V2:

docker compose version

預期輸出類似:

Docker Compose version v2.40.3

如果你的系統只有舊版的 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 檔案定義整個應用的架構,然後用一個指令啟動所有服務:

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:

啟動所有服務只需要:

docker compose up -d

優點:

  • 設定即文件compose.yaml 清楚描述整個應用架構
  • 一鍵部署:單一指令啟動所有服務
  • 易於分享:將 compose.yaml 提交到版本控制,團隊成員都能使用相同設定
  • 環境一致:確保開發、測試、生產環境使用相同的服務架構
  • 自動化管理:自動建立網路、管理服務相依性

compose.yaml 語法詳解

compose.yaml(或 docker-compose.yml)是 Docker Compose 的設定檔,用於定義應用程式的所有服務、網路和資料卷。

services - 定義服務

services 是設定檔的核心區塊,定義應用程式包含哪些服務(容器)。

基本結構:

services:
  service-name:
    # 服務設定...

  another-service:
    # 服務設定...

每個服務名稱會成為該容器在網路中的 DNS 名稱,其他容器可以用這個名稱存取它。

image 與 build - 指定映像來源

服務的容器可以來自現有映像檔,或從 Dockerfile 建構。

使用現有映像(image)

services:
  db:
    image: postgres:18

  cache:
    image: redis:7-alpine

  web:
    image: myapp:1.0.0

從 Dockerfile 建構(build)

services:
  web:
    build: .
    # 從當前目錄的 Dockerfile 建構

  api:
    build:
      context: ./backend
      dockerfile: Dockerfile.dev
    # 從指定目錄與 Dockerfile 建構

同時使用 image 與 build

services:
  web:
    image: myapp:latest
    build: .
    # build 建構的映像會標記為 myapp:latest

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 設定容器的環境變數。

字典格式

services:
  web:
    image: myapp
    environment:
      DEBUG: "true"
      DATABASE_HOST: db
      DATABASE_PORT: "5432"

清單格式

services:
  web:
    image: myapp
    environment:
      - DEBUG=true
      - DATABASE_HOST=db
      - DATABASE_PORT=5432

使用 env_file

services:
  web:
    image: myapp
    env_file:
      - .env
      - .env.local

引號使用

YAML 會自動解析某些值(如 yesnotruefalse),建議用引號包裹字串值避免誤解析。

environment:
  ENABLE_FEATURE: "yes"  # 引號確保值為字串 "yes"
  PORT: "8080"           # 數字也建議加引號

networks - 自訂網路

預設情況下,Compose 會自動建立一個網路並將所有服務連接到該網路。你也可以定義自訂網路實現更複雜的網路架構。

使用預設網路

services:
  web:
    image: nginx
  db:
    image: postgres:18
# 所有服務自動連接到同一個網路

自訂網路

services:
  web:
    image: nginx
    networks:
      - frontend

  api:
    image: myapi
    networks:
      - frontend
      - backend

  db:
    image: postgres:18
    networks:
      - backend

networks:
  frontend:
  backend:

在這個範例中:

  • web 只能存取 api
  • api 可以存取 webdb
  • web 無法直接存取 db(提高安全性)

depends_on - 服務相依性

depends_on 定義服務的啟動順序。

services:
  web:
    image: myapp
    depends_on:
      - db
      - cache

  db:
    image: postgres:18

  cache:
    image: redis:7-alpine

這個設定確保 dbcache 會在 web 之前啟動。

depends_on 的限制

depends_on 只控制啟動順序,不會等待服務「就緒」。例如,PostgreSQL 容器啟動後,資料庫可能還在初始化中。

如果需要等待服務就緒,可以:

  1. 在應用程式中實作重試邏輯
  2. 使用 depends_on 的長格式(需要 Compose V2.20+):
services:
  web:
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:18
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

restart - 重啟策略

restart 定義容器意外停止時的重啟行為。

services:
  web:
    image: nginx
    restart: always

選項說明:

  • no:不自動重啟(預設值)
  • always:總是重啟,即使容器正常停止
  • on-failure:只在容器異常退出時重啟
  • unless-stopped:總是重啟,除非手動停止容器

使用建議:

  • 生產環境的關鍵服務:使用 alwaysunless-stopped
  • 開發環境:使用 noon-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

執行流程:

  1. 建立定義的網路
  2. 建立定義的 volume
  3. 啟動服務(依照 depends_on 順序)
  4. 顯示日誌(如果沒有使用 -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 logs -f web db

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 檔案

.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 作為範本:

# .gitignore
.env

# 提供範本
cp .env.example .env

變數替換語法

Compose 支援多種變數替換語法:

services:
  web:
    image: "webapp:${TAG}"           # 使用變數
    image: "webapp:${TAG:-latest}"   # 提供預設值
    image: "webapp:${TAG-latest}"    # 變數未設定時使用預設值
    image: "webapp:${TAG:?err}"      # 變數未設定時報錯

環境變數優先順序

容器內的環境變數常見來源與優先順序(由高到低):

  1. compose.yaml 的 environment
  2. env_file
  3. 映像檔中的 ENV 預設值

另外,compose.yaml 中的 ${VAR} 變數替換會依以下順序取值:

  1. Shell 環境變數
  2. .env 檔案

範例:

services:
  web:
    environment:
      DEBUG: ${DEBUG:-false}

若 Shell 有 DEBUG=production,容器內的 DEBUG 會是 production;若沒有則使用 .env 或預設值 false

多環境設定

實際專案中,開發環境與生產環境的設定往往不同。Compose 提供多種方式實現多環境設定。

compose.override.yaml - 自動覆蓋

Compose 會自動合併以下檔案:

  1. compose.yaml(基礎設定)
  2. compose.override.yaml(自動載入的覆蓋設定)

範例:

compose.yaml
services:
  web:
    image: myapp:latest
    environment:
      LOG_LEVEL: info
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

範例:生產環境設定

compose.prod.yaml
services:
  web:
    image: myapp:1.0.0  # 使用特定版本
    restart: always     # 生產環境自動重啟
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 1G

  db:
    volumes:
      - /data/postgres:/var/lib/postgresql  # 使用實體路徑

範例:開發環境設定

compose.dev.yaml
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 快取。

專案架構

myapp/
├── compose.yaml
├── .env
├── Dockerfile
├── requirements.txt
└── app.py

應用程式碼

requirements.txt
Flask==3.1.2
psycopg[binary]==3.3.2
redis==7.1.0
app.py
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)
Dockerfile
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 設定檔

compose.yaml
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:
.env
# 資料庫設定
DATABASE_NAME=myapp
DATABASE_PASSWORD=mysecret123

# 應用程式設定
APP_PORT=8000

實作步驟

步驟 1:建立專案結構

mkdir myapp
cd myapp

步驟 2:建立所有檔案

將上述的 app.pyDockerfilerequirements.txtcompose.yaml.env 檔案建立在專案目錄中。

步驟 3:啟動所有服務

docker compose up -d

Compose 會自動:

  1. 建構 Web 應用的映像
  2. 下載 PostgreSQL 和 Redis 映像
  3. 建立網路
  4. 建立 volume
  5. 啟動所有服務

步驟 4:檢查服務狀態

docker compose ps

預期所有服務的 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:查看日誌

# 查看所有服務的日誌
docker compose logs

# 即時追蹤 web 服務的日誌
docker compose logs -f web

步驟 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:停止服務

# 停止所有服務
docker compose down

# 停止並刪除 volume(會清除資料庫資料)
docker compose down -v

驗證重點

完成實作後,確認以下項目:

  • 訪問 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

compose.override.yaml
services:
  db:
    ports:
      - "5432:5432"

這樣可以把資料庫的埠號綁定到 Host 上,方便其他工具連線。

任務結束

完成!

恭喜你完成了這個任務!現在你已經學會:

  • 理解多容器管理的痛點與 Docker Compose 的解決方案
  • 掌握 compose.yaml 的核心語法與設定
  • 熟練使用 Docker Compose 常用指令
  • 學會管理環境變數與多環境設定
  • 透過實作練習部署完整的三層式應用(Web + Database + Cache)

你現在已經掌握 Docker Compose 的核心技能,能夠用單一設定檔管理複雜的多容器應用。這是從開發邁向生產環境的重要里程碑!在實際專案中,Docker Compose 能大幅簡化本地開發環境的建立,確保團隊成員都能使用一致的開發環境。