iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
Software Development

30 天 Python 專案工坊:環境、結構、測試到部署全打通系列 第 23

Day 23 -容器化最佳實務:多階段 Dockerfile 與非 root 執行

  • 分享至 

  • xImage
  •  

本篇聚焦在「映像瘦身與強化安全」。依舊延續前面章節的專案骨架、依賴鎖定、結構化日誌與 API 分層,但不談 CI/CD(之後再提)。你可以直接把範例放進專案,立刻得到乾淨、可重現、預設安全的容器。


  • 多階段 Dockerfile:把建置依賴留在 builder,runtime 只帶最小執行體與程式碼,體積小、啟動快、攻擊面小。
  • 非 root:固定 UID/GID、權限最小化,結合唯讀檔系統與捨棄 capabilities,降低入侵影響面。
  • 可重現:用鎖檔(如 uv.lock)在建置時強制一致依賴,避免「今天裝到不一樣的包」那種開發者崩潰。

1) 前置:專案骨架與檔案

以 Day 4 的建議為例,容器只帶執行所需的內容:

my_project/
├─ pyproject.toml
├─ uv.lock                 # 鎖住依賴
├─ src/my_project/         # 執行用程式碼
├─ tests/                  # 只在本機或開發流程跑,不進 runtime
└─ .dockerignore

.dockerignore(先放這份,之後你再微調):

.git
__pycache__/
*.pyc
.env*
tests/
notebooks/
dist/
build/
.hatch/
.cache/
.vscode/
.idea/


2) 完整範例:FastAPI 服務的多階段 Dockerfile

重點:builder 安裝依賴與可選的原生套件,runtime 只拷貝「結果」。同時建立非 root 使用者,並以 stdout 打 JSON 日誌(對 Day 13 友善)。

# syntax=docker/dockerfile:1.7

############################
# Stage 1: builder(有編譯工具)
############################
FROM python:3.12-slim AS builder
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
    UV_SYSTEM_PYTHON=1

# 依實際需求加入原生依賴(例如需要編譯的 wheels)
RUN apt-get update && apt-get install -y --no-install-recommends \
      build-essential curl ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# 安裝 uv(高速、可重現)
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
ENV PATH="/root/.local/bin:${PATH}"

WORKDIR /app

# 先複製 metadata 與鎖檔以最大化快取命中
COPY pyproject.toml uv.lock ./

# 依鎖檔同步依賴到虛擬環境(不含 dev 依賴)
RUN uv sync --frozen --no-dev --python=/usr/local/bin/python

# 再帶入程式碼(避免動到程式碼就讓前面步驟失效)
COPY src/ ./src/

############################
# Stage 2: runtime(最小執行環境)
############################
FROM python:3.12-slim AS runtime

# 建立非 root 使用者(固定 UID/GID 便於掛載)
RUN groupadd -g 10001 app && \
    useradd -m -u 10001 -g 10001 -s /usr/sbin/nologin app

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1

WORKDIR /app
RUN mkdir -p /app && chown -R app:app /app

# 從 builder 帶入程式碼與已解決好的虛擬環境
COPY --from=builder /app/src/ /app/src/
COPY --from=builder /root/.cache/uv/venv/*/ /opt/venv/
ENV PATH="/opt/venv/bin:${PATH}"

# 服務埠
EXPOSE 8000

# 切換非 root
USER app:app

# 建議在執行時加入 --read-only 並掛載 /tmp 或資料目錄
# 例如:docker run --read-only -v tmpfs:/tmp ...

# 以 Uvicorn 起服務(依你的專案模組路徑調整)
CMD ["uvicorn", "my_project.adapters.web.app:app", "--host", "0.0.0.0", "--port", "8000"]


3) 關鍵設計說明

為何一定要多階段

  • 把「會變大的」建置依賴留在 builder,runtime 就不需要編譯器、headers、apt 快取,映像立刻瘦身。
  • 更新程式碼時,依賴層可被快取命中,建置速度快很多。

非 root 的落地做法

  • 建專用帳號 app:app,固定 UID/GID。
  • 容器啟動改用 USER app:app
  • 執行時加 -read-only,需要寫入就掛 tmpfs 或 volume。
  • 若平台支援,移除多餘 capabilities(如 NET_ADMIN, SYS_PTRACE)。

可重現的依賴

  • 建置時使用鎖檔(uv.lock),並加上 -frozen,確保安裝版本完全一致。
  • 鎖檔應該跟著原始碼一起版控。

4) 運行與除錯

本地測試:

# 建置
docker build -t myapp:dev .

# 執行(唯讀檔系統 + 暫存寫入)
docker run --rm -p 8000:8000 \
  --read-only \
  --tmpfs /tmp:rw,size=64m \
  myapp:dev

健康檢查與日誌建議:

  • 提供 /healthz/readyz 簡單回應與關鍵依賴檢查。
  • 以 JSON 格式輸出到 stdout,方便平台收集與搜尋。

5) 常見錯誤與排解速查

症狀 可能原因 解法
執行期找不到原生動態庫 builder 有裝套件,runtime 沒有 把必要的系統動態庫也安裝在 runtime,或改至 builder 輸出完全靜態/對應 wheel
非 root 下啟動失敗 檔案權限或目錄不存在 在 Dockerfile 中 chown 必要目錄,或改用 volume 並指定擁有者
每次改一行就重裝依賴 複製順序不對 先 COPY pyproject.toml/uv.lock,完成安裝後才 COPY src/
需要可寫路徑 rootfs 唯讀 在 run 時掛載 --tmpfs /tmp 或將 data/ 做成命名 volume
體積仍然過大 apt 快取未清、帶入多餘檔 rm -rf /var/lib/apt/lists/*、調整 .dockerignore、檢查是否把 tests/notebooks 帶進 runtime

6) 安全與體積最佳化清單

  • 基底映像用 slim 或你確實需要的最小發行版。
  • 所有 apt-get install 後清理快取與 headers。
  • 只把執行需要的檔案拷貝到 runtime。
  • 預設非 root,固定 UID/GID,盡量唯讀。
  • 移除不必要 capabilities,限制容器網路與檔案權限。
  • 映像掃描與依賴查核可以日後再補,但「減少攻擊面」從現在就做。

7) 最小運作樣板:應用程式入口

為了對齊容器啟動命令,入口建議穩定且清楚,例如 my_project.adapters.web.app:app,裡面維持乾淨的組態與日誌初始化:

# src/my_project/adapters/web/app.py
from fastapi import FastAPI

def create_app() -> FastAPI:
    app = FastAPI(title="My Service")
    # init logging to JSON here (structlog / logging.config)
    @app.get("/healthz")
    def healthz():
        return {"status": "ok"}
    return app

app = create_app()



結語

容器化做對了,等於把「可重現」與「預設安全」內建到產品生命週期。多階段讓映像乾淨,非 root 把風險打到最低;鎖檔與穩定入口讓你今天建什麼,明天還是那個。等後續要談流程與自動化,再把這套基礎往外延伸就好。


上一篇
Day 22 -發佈與版本:Hatch build wheel、SemVer、Changelog
系列文
30 天 Python 專案工坊:環境、結構、測試到部署全打通23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言