【GitHub Actions CD 實戰】#03 FastAPI 後端 Docker 化:打包你的 API 服務

測驗:FastAPI 後端 Docker 化

共 5 題,點選答案後會立即顯示結果

1. Docker 化的主要目的是什麼?

  • A. 讓程式碼執行速度變快
  • B. 把程式碼和執行環境打包在一起,確保不同環境下都能正常運作
  • C. 減少程式碼的行數
  • D. 自動修復程式碼的 bug

2. 在 Dockerfile 中,為什麼要分兩次使用 COPY 指令(先複製 requirements.txt,再複製其他檔案)?

COPY requirements.txt . RUN pip install -r requirements.txt COPY . .
  • A. 因為 Docker 規定必須這樣做
  • B. 因為 requirements.txt 檔案太大,要分開複製
  • C. 為了利用 Docker 快取機制,當程式碼改變但依賴沒變時不需重新安裝套件
  • D. 為了避免複製到不需要的檔案

3. 在 docker-compose.yml 中,depends_on 的作用是什麼?

  • A. 指定服務要使用的 Docker image 版本
  • B. 指定服務的啟動順序,讓某個服務在依賴的服務啟動後才啟動
  • C. 設定服務之間的網路連線密碼
  • D. 指定要安裝的 Python 依賴套件

4. 下列哪個指令可以讓 Docker 容器在背景執行?

  • A. docker-compose up -f
  • B. docker-compose up -d
  • C. docker-compose up -b
  • D. docker-compose up --run

5. .dockerignore 檔案的主要用途是什麼?

  • A. 指定 Docker 要使用的基底 image
  • B. 設定 Docker 容器的環境變數
  • C. 定義 Docker 容器的啟動指令
  • D. 告訴 Docker 哪些檔案不要複製進 image,減少大小並避免複製機密檔案

一句話說明

把你的 FastAPI 專案打包成 Docker image,讓它在任何地方都能跑。

為什麼要 Docker 化?

你讓 AI 幫你寫了一個 FastAPI 後端,本機跑得好好的。但部署到伺服器時可能遇到:

  • 「我的電腦 Python 3.11,伺服器只有 3.9」
  • 「本機裝了某個套件,伺服器沒裝」
  • 「環境變數設定不一樣」

Docker 解決的問題:把程式碼 + 執行環境打包在一起,保證「我這邊能跑,你那邊也能跑」。


Dockerfile 快速看懂

最小範例

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Code language: JavaScript (javascript)

逐行翻譯

FROM python:3.11-slim      # 以 Python 3.11 精簡版為基底
WORKDIR /app               # 在容器內建立並切換到 /app 目錄
COPY requirements.txt .    # 先複製 requirements.txt 到容器
RUN pip install -r requirements.txt  # 安裝依賴套件
COPY . .                   # 複製專案所有檔案到容器
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]  # 啟動指令
Code language: PHP (php)

為什麼分兩次 COPY?

這是快取優化的技巧:

COPY requirements.txt .    # 第一次 COPY
RUN pip install ...        # 安裝依賴
COPY . .                   # 第二次 COPY
Code language: PHP (php)

翻譯

  • Docker 會把每一行指令的結果存成一「層」
  • 如果 requirements.txt 沒變,pip install 那層會用快取
  • 只有程式碼改變時才重新執行 COPY . .
  • 結果:改程式碼時建置超快(不用重裝套件)

常見 Dockerfile 模式

模式 1:單階段建置(最簡單)

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Code language: JavaScript (javascript)

翻譯:一次把所有東西打包進去。簡單但 image 較大。

你會看到 意思
FROM python:3.11-slim 使用精簡版 Python image(比完整版小很多)
--no-cache-dir 不要儲存 pip 快取,減少 image 大小
EXPOSE 8000 標記這個容器會用 8000 port(文件用途)

模式 2:多階段建置(進階優化)

# 第一階段:建置
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt

# 第二階段:執行
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Code language: PHP (php)

翻譯

  • 第一階段安裝套件
  • 第二階段只複製安裝好的套件
  • 結果:最終 image 不包含建置工具,更小
你會看到 意思
AS builder 給這個階段取名叫 builder
--from=builder 從 builder 階段複製檔案過來
--user 安裝到使用者目錄而非系統目錄

模式 3:帶環境變數的版本

FROM python:3.11-slim
WORKDIR /app

# 環境變數設定
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PORT=8000

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

EXPOSE ${PORT}
CMD uvicorn main:app --host 0.0.0.0 --port ${PORT}
Code language: PHP (php)
你會看到 意思
PYTHONDONTWRITEBYTECODE=1 不要產生 .pyc 檔案
PYTHONUNBUFFERED=1 讓 print 立即輸出(方便看 log)
${PORT} 使用環境變數的值

完整 FastAPI Dockerfile 範例

這是 AI 最常產出的完整版本:

# 基底 image
FROM python:3.11-slim

# 設定工作目錄
WORKDIR /app

# 環境變數
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

# 安裝系統依賴(如果需要)
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# 複製並安裝 Python 依賴
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 複製專案檔案
COPY . .

# 開放 port
EXPOSE 8000

# 建立非 root 使用者(安全考量)
RUN adduser --disabled-password --gecos '' appuser
USER appuser

# 啟動指令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Code language: PHP (php)

逐段翻譯

區塊 在做什麼
apt-get install gcc 某些 Python 套件需要編譯,裝完就刪掉清單
rm -rf /var/lib/apt/lists/* 清理暫存檔,減少 image 大小
adduser ... appuser 建立一個沒有密碼的普通使用者
USER appuser 之後的指令都用這個使用者執行(更安全)

docker-compose.yml 快速看懂

最小範例

services:
  api:
    build: .
    ports:
      - "8000:8000"
Code language: JavaScript (javascript)

翻譯:從當前目錄的 Dockerfile 建置 image,把容器的 8000 port 對應到主機的 8000 port。

常見配置

version: "3.8"

services:
  # FastAPI 服務
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
    depends_on:
      - db
    volumes:
      - .:/app
    restart: unless-stopped

  # PostgreSQL 資料庫
  db:
    image: postgres:15
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  postgres_data:
Code language: PHP (php)

逐行翻譯

你會看到 意思
build: . 從當前目錄的 Dockerfile 建置
image: postgres:15 直接用現成的 PostgreSQL image
ports: - "8000:8000" 主機 port : 容器 port
environment: 設定環境變數
depends_on: - db api 服務要等 db 服務啟動後才啟動
volumes: - .:/app 把當前目錄掛載到容器的 /app(開發用)
volumes: postgres_data: 資料持久化,刪除容器資料還在
restart: unless-stopped 容器掛掉自動重啟(除非手動停止)

服務間連線

environment:
  - DATABASE_URL=postgresql://user:pass@db:5432/mydb
Code language: JavaScript (javascript)

翻譯:在 docker-compose 裡,服務名稱就是主機名稱。@db 指的是名為 db 的服務。


建置與執行指令

基本指令

# 建置 image
docker build -t my-fastapi-app .

# 執行容器
docker run -p 8000:8000 my-fastapi-app

# 用 docker-compose
docker-compose up -d        # -d 是背景執行
docker-compose down         # 停止並移除容器
docker-compose logs -f api  # 看 api 服務的 log
Code language: PHP (php)
指令 意思
-t my-fastapi-app 給 image 取名(tag)
-p 8000:8000 port 對應(主機:容器)
-d detach 模式,背景執行
-f follow 模式,持續輸出新 log

推送到 Registry

Docker Hub

# 登入
docker login

# 標記 image(格式:帳號/image名稱:版本)
docker tag my-fastapi-app username/my-fastapi-app:v1.0

# 推送
docker push username/my-fastapi-app:v1.0
Code language: PHP (php)

GitHub Container Registry (GHCR)

# 登入(用 Personal Access Token)
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin

# 標記 image
docker tag my-fastapi-app ghcr.io/username/my-fastapi-app:v1.0

# 推送
docker push ghcr.io/username/my-fastapi-app:v1.0
Code language: PHP (php)
Registry Image 格式
Docker Hub username/image:tag
GHCR ghcr.io/username/image:tag

.dockerignore 檔案

跟 .gitignore 一樣,告訴 Docker 哪些檔案不要複製進去:

# .dockerignore
__pycache__
*.pyc
.git
.env
.venv
venv/
*.md
.pytest_cache
.coverage
Code language: PHP (php)

為什麼重要

  • 減少 image 大小
  • 避免把機密檔案(.env)打包進去
  • 加快建置速度

Vibe Coder 檢查點

看到 Dockerfile 時確認:

  • [ ] FROM 用的 Python 版本對嗎?
  • [ ] requirements.txt 有沒有被正確複製?
  • [ ] CMD 的啟動指令正確嗎?(main:app 要對應你的檔案)
  • [ ] 有沒有 .dockerignore 避免複製不必要的檔案?

看到 docker-compose.yml 時確認:

  • [ ] port 對應是否正確?(避免衝突)
  • [ ] 環境變數有沒有設定?(尤其是資料庫連線)
  • [ ] 資料庫有沒有用 volumes 做持久化?
  • [ ] 服務之間的 depends_on 順序對嗎?

實作練習:Docker 化你的 FastAPI 專案

假設你有這樣的專案結構:

my-api/
  main.py
  requirements.txt

步驟 1:建立 Dockerfile

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Code language: JavaScript (javascript)

步驟 2:建立 .dockerignore

__pycache__
*.pyc
.git
.env
.venv
Code language: CSS (css)

步驟 3:建置並測試

docker build -t my-api .
docker run -p 8000:8000 my-api
Code language: CSS (css)

打開瀏覽器訪問 http://localhost:8000/docs 確認 API 正常運作。


重點整理

概念 一句話解釋
Dockerfile 建置 image 的腳本
FROM 指定基底 image
COPY 複製檔案到容器
RUN 建置時執行指令
CMD 容器啟動時執行的指令
docker-compose 管理多個容器的工具
Registry 存放 image 的地方(像 GitHub 存程式碼)

下一步

現在你的 FastAPI 專案已經 Docker 化了。下一篇我們會學習如何用 GitHub Actions 自動建置 Docker image 並部署到伺服器。

進階測驗:FastAPI 後端 Docker 化

測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。

1. 你的 FastAPI 專案需要連接 PostgreSQL 資料庫,你想用 docker-compose 同時啟動 API 和資料庫服務。API 服務要如何在環境變數中指定資料庫的連線位址? 情境題

  • A. DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
  • B. DATABASE_URL=postgresql://user:pass@db:5432/mydb
  • C. DATABASE_URL=postgresql://user:[email protected]:5432/mydb
  • D. DATABASE_URL=postgresql://user:pass@postgres:5432/mydb

2. 你執行 docker build 後發現 image 很大(超過 1GB),想要優化 image 大小。以下哪個做法最有效? 情境題

  • A. 把所有 Dockerfile 指令合併成一行
  • B. 使用 docker build --compress 參數
  • C. 使用多階段建置(multi-stage build)並採用 slim 版本的基底 image
  • D. 刪除 requirements.txt 中不必要的註解

3. 小明的 Dockerfile 如下,但每次修改程式碼後建置都很慢。問題出在哪裡? 錯誤診斷

FROM python:3.11-slim WORKDIR /app COPY . . RUN pip install -r requirements.txt CMD [“uvicorn”, “main:app”, “–host”, “0.0.0.0”, “–port”, “8000”]
  • A. 應該使用 python:3.11 而非 python:3.11-slim
  • B. 應該先複製 requirements.txt 並安裝依賴,再複製其他檔案,以利用 Docker 快取
  • C. CMD 指令應該放在 RUN 指令之前
  • D. WORKDIR 應該設定為 /home/app 而非 /app

4. 你想把建好的 Docker image 推送到 GitHub Container Registry (GHCR),以下哪個 image tag 格式是正確的? 情境題

  • A. github.com/username/my-api:v1.0
  • B. username/my-api:v1.0
  • C. ghcr.io/username/my-api:v1.0
  • D. registry.github.com/username/my-api:v1.0

5. 小華的 docker-compose.yml 設定如下,但啟動後發現資料庫資料在容器重啟後就消失了。問題最可能出在哪裡? 錯誤診斷

services: api: build: . ports: – “8000:8000” depends_on: – db db: image: postgres:15 environment: – POSTGRES_USER=user – POSTGRES_PASSWORD=pass – POSTGRES_DB=mydb ports: – “5432:5432”
  • A. 沒有設定 restart: always
  • B. port 對應設定錯誤
  • C. 缺少 depends_on 設定
  • D. 資料庫服務沒有設定 volumes 做資料持久化

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *