測驗:FastAPI 後端 Docker 化
共 5 題,點選答案後會立即顯示結果
1. Docker 化的主要目的是什麼?
2. 在 Dockerfile 中,為什麼要分兩次使用 COPY 指令(先複製 requirements.txt,再複製其他檔案)?
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
3. 在 docker-compose.yml 中,depends_on 的作用是什麼?
4. 下列哪個指令可以讓 Docker 容器在背景執行?
5. .dockerignore 檔案的主要用途是什麼?
一句話說明
把你的 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 題,包含情境題與錯誤診斷題。
共 5 題,包含情境題與錯誤診斷題。
1. 你的 FastAPI 專案需要連接 PostgreSQL 資料庫,你想用 docker-compose 同時啟動 API 和資料庫服務。API 服務要如何在環境變數中指定資料庫的連線位址? 情境題
2. 你執行 docker build 後發現 image 很大(超過 1GB),想要優化 image 大小。以下哪個做法最有效? 情境題
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”]
4. 你想把建好的 Docker image 推送到 GitHub Container Registry (GHCR),以下哪個 image tag 格式是正確的? 情境題
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”