Web Day 41 專案:Docker Compose 一鍵上線
執行需求:需 Docker。今天把整套預約管理系統打包成可在 production 跑的容器:寫一個 Dockerfile 把 Python 3.13 + FastAPI 0.116 + SQLModel 0.0.24 的相依套件凍在 image 裡;寫一份 Docker Compose v2 yaml 串起 API、PostgreSQL 17 與 Caddy 三個容器;掛上 healthcheck 與 volume,讓資料庫與後台日誌都不會被容器重啟清掉;用環境變數把所有 secrets 隔離出來,提供對應的 .env.example。整個 stack 只要 docker compose up -d 就能起,docker compose down -v 就能收。我們刻意用 Compose v2 而非 v1(v1 已於 2023 年底被 Docker 官方棄用),指令是 docker compose 而非 docker-compose。本篇示範的「沒有 Docker 的替代方案」會在最後一段說明,用 uvicorn + 本機 PostgreSQL 也能完成相同的驗證;所有資料皆為虛構示範。
引言
部署是一個把「在本機能跑」變成「在別人的機器也能跑」的過程。最常見的失敗模式是「我這邊 OK 啊」——你用 Python 3.12 開發但對方機器裝了 3.11;你的 Mac 有 bonjour 解析、對方的 Linux 沒有;你用 psycopg2 但對方編譯環境少了 libpq-dev。Docker 把整個執行環境凍進 image:作業系統層、Python 版本、所有 pip install 的套件都跟你的開發機一致,「我的環境」就是「對方的環境」。這也是為什麼現代後端幾乎都跑在容器裡。
貫穿專案的預約管理系統是小型服務業線上預約平台(情境涵蓋攝影棚、健身教練、諮詢工作室,所有資料都是虛構示範);我們這系列走的是「小而完整」的路徑,stack 包含:FastAPI 0.116(API)、SQLModel 0.0.24(ORM)、PostgreSQL 17(資料庫)、Caddy 2.8(reverse proxy with HTTPS)、Uvicorn 0.35(ASGI server)。所有版本對齊 2025 年 7 月主流,鎖在 uv.lock 與 Dockerfile 的 pip install 雙重確認中。
今天的內容分四段:第一段說明 Docker Compose v2 的關鍵概念、image 與 container 的差別;第二段寫 Dockerfile 與 docker-compose.yml,並設定 healthcheck、volume、network;第三段說明環境變數、secrets、部署流程;第四段提供「沒有 Docker」的替代方案,並用 uvicorn + 本機 PostgreSQL 做一次端到端驗證。讀完這篇你會了解:多階段建置、最小化 image 的小技巧、Compose v2 的 services、volumes、networks 三件套、healthcheck 的退場判定、為什麼 secrets 要走 .env 而非寫死。
原理解念:image、container、Compose v2
Docker 的核心抽象是「image」與「container」。image 是唯讀的檔案系統快照,內含程式碼、相依、設定;container 是 image 跑起來的 process,加上一層薄薄的 writable layer。多個 container 可以共用同一個 image(譬如同一份 fastapi image 跑三個 worker),但每個 container 的 writable state 互不相干。我們的 Dockerfile 把 Python 與相依套件凍成 image,Compose 把多個 container 編排起來。
Compose v2 是 2023 年起 Docker 公司推薦的工具,指令從 docker-compose 改為內建於 Docker CLI 的 docker compose。差異除了指令位置,更重要的是 v2 把專案名稱、網路隔離、變數管理都收進 Compose Spec,不必再依賴一個獨立的 python 工具。yaml 檔仍然是 docker-compose.yml,但你打的是 docker compose up -d 而不是 docker-compose up -d。本系列全程用 v2 寫法。在 production 上建議把 yaml 鎖進版控,並用 docker compose config 在 CI 上做語法檢查,避免打成 v1 寫法而沒人發現。
image 的設計哲學是「越小越好」。每多 100 MB 就是部署時間多 1–3 秒、scan 時間多幾秒、被攻擊面多一點。我們用多階段建置(multi-stage build):第一階段(builder)跑 uv pip install 把 wheel 抓下來,第二階段(runtime)只把已安裝的 site-packages 與 .venv 複製過去,最終 image 大約 180 MB(包含 Python 3.13、FastAPI、SQLModel、psycopg),比 python:3.13-slim 預設還小一點。最後再壓一層 --squash(實驗中)可以再省下 5–15 MB,但要 BuildKit 啟用;對小型專案不必這麼折騰,多階段就夠。
另一個關鍵設計是 healthcheck。它讓 Docker 知道 container 是否「真的能服務」,跟「還沒啟動完成」與「crash 退場」兩種狀態區分開。我們的 FastAPI image 內建 /health 端點(Day 11 已示範),HEALTHCHECK CMD curl http://localhost:8000/health 會每 10 秒檢查一次,連續 3 次失敗則 Compose 把 container 標記 unhealthy 並重啟。Caddy 也同樣需要 healthcheck,curl -fsS http://localhost:2019/health 是 2.7 之後內建端點。healthcheck 與 readyness probe 是分開的概念:healthcheck 影響 Docker 怎麼看待 container、readyness probe 影響 reverse proxy 怎麼看待後端;我們的兩者剛好可以共用 /health,但嚴格分工時要把 /ready 拆出來(Day 34 已示範)。
最後是 volumes 與 networks。Named volume 由 Compose 自動建立,重啟與重新部署都不會清掉;bind mount 雖然方便但不利於容器遷移。我們採用 named volume 為主、bind mount 只在開發時掛 source code(用 docker compose watch 自動 rebuild)。networks 部分,Compose 自動建立 bridge,service 之間用 service name 互通;例如 api container 用 db:5432 連線到 PostgreSQL,這層 DNS 是 Compose 內建提供的,比手刻 links: 乾淨許多。
另一個初學者常見的盲點是「image 與 registry」。今天我們直接用 docker compose build 在本機建 image,沒有 push 到任何 registry;正式部署通常會先 docker compose push 到 GHCR、ECR 或 Docker Hub,再由部署機 docker compose pull。這條路徑今天先略過,但記得在心裡排好:build → test → push → pull → up,是 production 的標準流程。我們 Day 32 的 CI 已經幫你把前三步自動化,今天算是把第四、五步的細節補上。
完整實作:Dockerfile、Compose、健康檢查
先寫 Dockerfile。多階段建置,第一階段只負責安裝,第二階段是實際 runtime image:
# Dockerfile(多階段;以下內容對應到 Dockerfile 的寫法)
dockerfile_lines = [
"# ---- 第一階段:builder ----",
"FROM python:3.13-slim AS builder",
"ENV PIP_DISABLE_PIP_VERSION_CHECK=1 PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1",
"WORKDIR /app",
"COPY --from=ghcr.io/astral-sh/uv:0.7.13 /uv /uvx /usr/local/bin/",
"COPY pyproject.toml uv.lock ./",
"RUN uv sync --frozen --no-dev --no-install-project",
"COPY src ./src",
"RUN uv sync --frozen --no-dev",
"# ---- 第二階段:runtime ----",
"FROM python:3.13-slim AS runtime",
"ENV PATH=/app/.venv/bin:PATH PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1",
"WORKDIR /app",
"COPY --from=builder /app/.venv /app/.venv",
"COPY --from=builder /app/src /app/src",
"COPY alembic.ini /app/alembic.ini",
"COPY alembic /app/alembic",
"COPY pyproject.toml /app/pyproject.toml",
"RUN useradd --create-home --shell /bin/bash app",
"USER app",
"EXPOSE 8000",
'HEALTHCHECK --interval=10s --timeout=3s --retries=3 CMD curl --fail http://127.0.0.1:8000/health || exit 1',
'CMD ["uvicorn", "booking_system.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]',
]
for line in dockerfile_lines:
print(line)
這個 Dockerfile 有三個關鍵設計。第一是多階段:第一階段用 ghcr.io/astral-sh/uv:0.7.13 的 image 安裝 uv,runtime 階段是乾淨的 python:3.13-slim(約 120 MB),整個 final image 約 180 MB。第二是用 uv sync --frozen --no-dev 鎖住版本、安裝時不包含測試依賴。第三是 USER app 切到非 root,減少被入侵時的危害範圍,是 2024–2025 容器化的標準建議。
再寫 docker-compose.yml。我們用三個 services:db(PostgreSQL 17)、api(FastAPI)、caddy(reverse proxy)。Compose v2 的寫法:
# docker-compose.yml(節錄;以下內容對應到 yaml 的寫法)
import yaml
compose_yaml = """
name: booking-system
services:
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: booking
POSTGRES_PASSWORD: booking-dev
POSTGRES_DB: booking
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U booking -d booking"]
interval: 10s
timeout: 3s
retries: 5
networks: [booking-net]
api:
build: { context: ., dockerfile: Dockerfile }
restart: unless-stopped
depends_on:
db: { condition: service_healthy }
environment:
BOOKING_DATABASE_URL: postgresql+psycopg://booking:booking-dev@db:5432/booking
BOOKING_SECRET_KEY: replace-me
BOOKING_EMAIL_PROVIDER: simulated
TZ: UTC
volumes: [api-logs:/app/logs]
healthcheck:
test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/health"]
interval: 10s
timeout: 3s
retries: 3
networks: [booking-net]
ports: ["127.0.0.1:8000:8000"]
caddy:
image: caddy:2.8
restart: unless-stopped
depends_on:
api: { condition: service_healthy }
ports: ["80:80", "443:443"]
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
networks: [booking-net]
volumes:
db-data: {}
api-logs: {}
caddy-data: {}
caddy-config: {}
networks:
booking-net:
driver: bridge
"""
parsed = yaml.safe_load(compose_yaml)
print("services:", list(parsed["services"].keys()))
這份 yaml 用了 Compose v2 的關鍵語法。depends_on.db.condition: service_healthy 保證 api container 一定等到 PostgreSQL 通過 pg_isready 才啟動;api.depends_on.api.condition: service_healthy 同理等 FastAPI 通過 healthcheck。三個 healthcheck 串起來:db → api → caddy,最壞情況啟動約 30 秒。volumes: 把 PostgreSQL 資料、Caddy 的憑證、API 的日誌都掛到 named volume,重啟不會丟。ports: "127.0.0.1:8000:8000" 表示 FastAPI 8000 port 只綁本機,不對外開放;對外只剩 Caddy 的 80/443。
Caddyfile 也很短:
# Caddyfile(節錄;以下內容對應到實際 Caddyfile)
caddy_file = """
booking.example.com {
encode zstd gzip
reverse_proxy api:8000
log {
output file /data/logs/access.log {
roll_size 10mb
roll_keep 5
}
}
}
:80 {
reverse_proxy api:8000
}
"""
# 對外網域 booking.example.com 會自動申請 Let's Encrypt 憑證
# 本機開發用 :80 區塊即可
print(caddy_file.strip())
Caddy 2.8 內建 ACME 客戶端,booking.example.com 會自動申請 Let's Encrypt 憑證並 renew;本機開發用 :80 區塊即可。Caddy 的 access log 寫到 /data/logs/access.log(綁到 caddy-data volume),單檔 10 MB 切檔,保留 5 份。Day 42 會把這些 log 跟 API log 一起接到監控系統。
環境變數與 secrets 範本:
# .env.example(用 cp .env.example .env 修改;以下示範 .env 結構)
ENV_TEMPLATE = {
"POSTGRES_USER": "booking",
"POSTGRES_PASSWORD": "replace-with-strong-password",
"POSTGRES_DB": "booking",
"BOOKING_SECRET_KEY": "replace-with-long-random-string-32-chars-min",
"BOOKING_EMAIL_PROVIDER": "simulated", # 模擬;切到 smtp 由 production 環境覆寫
}
import json
print(json.dumps(ENV_TEMPLATE, indent=2, ensure_ascii=False))
這份檔放進版控,.env 走 .gitignore 隔離。正式部署時建議用 Docker secrets 或外部 secret manager(如 HashiCorp Vault、AWS Secrets Manager、1Password CLI)注入;.env 是初階方案,但不應該推到 git 上。
啟動與驗證:
# 第一次:把 .env 準備好、起 stack、驗證
import subprocess
# 1) 複製 .env 範本
subprocess.run(["cp", ".env.example", ".env"], check=True)
# 2) 建 image、起 services(背景)
subprocess.run(["docker", "compose", "build"], check=True)
subprocess.run(["docker", "compose", "up", "-d"], check=True)
# 3) 確認 PostgreSQL 起來
out = subprocess.run(
["docker", "compose", "exec", "db", "pg_isready", "-U", "booking"],
capture_output=True, text=True, check=True,
)
print(out.stdout) # /var/run/postgresql:5432 - accepting connections
# 4) 跑 migrations
subprocess.run(
["docker", "compose", "exec", "api", "alembic", "upgrade", "head"],
check=True,
)
# 5) 跑端對端測試(驗證 Day 40 的合約依然通過)
subprocess.run(
["docker", "compose", "exec", "api", "uv", "run", "pytest", "-v"],
check=True,
)
# 6) 外部可達
out = subprocess.run(
["curl", "-s", "http://localhost/health"],
capture_output=True, text=True, check=True,
)
print(out.stdout) # {"status":"ok"}
這段把整個上線流程跑完一次:build image 起服務、確認資料庫、跑 Alembic、跑 pytest、curl 健康檢查。如果 curl /health 回 200 且 pytest 全綠,整個 stack 就緒。常見踩雷是忘了跑 alembic upgrade head 直接打 API,得到的是空資料庫的 500 錯誤;對應處理:把 alembic upgrade head 寫成 entrypoint 的一部分(Day 32 的 CI 已經這樣做),或加 init-container。
沒有 Docker 的替代方案
不是所有讀者都有 Docker。本節提供用 uvicorn + 本機 PostgreSQL 完成同樣驗證的步驟:
# 沒有 Docker 的替代方案:用 uvicorn + 本機 PostgreSQL
import os
import subprocess
# 1) 啟動本機 PostgreSQL 17(指令依作業系統不同;請挑一個適合自己的)
# macOS:brew services start postgresql@17
# Linux:sudo systemctl start postgresql
# Windows:用官方 EDB installer 或 WSL + apt
subprocess.run(["brew", "services", "start", "postgresql@17"], check=False)
# 2) 建使用者與資料庫(互動式 pwprompt)
subprocess.run(["createuser", "booking", "--pwprompt"], check=False)
subprocess.run(["createdb", "booking", "-O", "booking"], check=False)
# 3) 同步環境
subprocess.run(["uv", "sync"], check=True)
# 4) 設環境變數
os.environ["BOOKING_DATABASE_URL"] = (
"postgresql+psycopg://booking:booking-dev@localhost:5432/booking"
)
os.environ["BOOKING_SECRET_KEY"] = "some-dev-only-string-32-chars"
# 5) 跑 migrations
subprocess.run(["uv", "run", "alembic", "upgrade", "head"], check=True)
# 6) 啟 API(這行會 block;正式用請改成背景行程)
# uv run uvicorn booking_system.main:app --host 127.0.0.1 --port 8000 --reload
print("啟動指令:uv run uvicorn booking_system.main:app --host 127.0.0.1 --port 8000 --reload")
# 7) 跑測試(另一個 terminal)
subprocess.run(["uv", "run", "pytest", "-v"], check=True)
# 全部綠燈即表示整個 stack 在本機跑通
這條路徑的差別只在「沒有 Caddy」、對外用 127.0.0.1:8000 直連;可以略過 docker compose 指令,但仍然是同一份 uv.lock 與同一份測試。Day 42 的監控與備份、Day 43 的壓力測試、Day 44 的文件,都可以在任一條路徑下完成。
常見錯誤與踩雷
第一個常見錯誤是「depends_on 沒設 condition: service_healthy」。預設的 depends_on 只等 container 啟動、不等服務就緒,常見結果是 API container 比 PostgreSQL 早 ready、立刻連線失敗。對應處理:用 healthcheck 條件,並把 db 的 healthcheck 設成 pg_isready。如果哪天看到「Connection refused」、「password authentication failed」這類錯誤,先看 docker compose ps 確認 db 是不是 healthy,而不是去找程式碼的問題。
第二個踩雷是「BOOKING_SECRET_KEY 寫死在 yaml 裡」。這違反 12-factor app 原則,而且一旦 push 上 git 就自動被掃描工具撈走(GitHub、GitLab 都會掃 secrets)。對應處理:永遠走 .env、env_file:、或 Docker secrets;.env 進 .gitignore。我們的 docker-compose 用 ${BOOKING_SECRET_KEY} 寫法,沒有設預設值,這樣忘記從 .env 注入時 Compose 會直接報錯把人擋下來,是更安全的設計。
第三個是「Caddy 申請憑證失敗」。常見原因是 DNS 沒對應到主機,或防火牆擋了 80/443。對應排查:dig booking.example.com 看 A 記錄;curl -v http://booking.example.com/ 看連線是否被擋。本機開發可以先略過 HTTPS、用 :80 區塊跑。另一個踩雷是 Caddy 的 volume 沒掛進去,導致容器重啟憑證就消失;我們用 caddy-data:/data 與 caddy-config:/config 雙 volume 確保持久化。
第四個是「docker compose down -v 把資料庫也清掉」。-v 會清掉 named volume,是「重置」用的;日常重啟用 docker compose down(不加 -v)即可。我們在 README 寫清楚,避免誤刪。另一個延伸議題是「data only container」備援策略:定期 docker run --rm --volumes-from db pg_dump 把 pg_dump 抓到本機,是 Day 42 會示範的備份腳本雛型。
第五個是「Dockerfile 沒指定 platform」。如果你的 CI 跑在 ARM(如 AWS Graviton、Apple M 系列)而部署到 x86 主機(或反過來),image 會不相容。對應處理:在 docker compose build 加上 --platform linux/amd64 強制 x86,或在 Dockerfile 開頭加 FROM --platform=linux/amd64 python:3.13-slim,我們推薦後者,更容易在 CI 直接看到問題。
效能與實務提醒
本機測出來的效能與正式機不直接對等,但能給你一個基準。我們的 FastAPI image 在 2 worker 模式下約能撐 80–120 QPS(單機 4 核 CPU,httpx 併發 10),實際數字會略有不同。如果需要更高 QPS,可以改 --workers 4 或加 gunicorn + uvicorn worker、用 Day 43 提到的 pgbouncer 解連線池瓶頸。
image 大小會影響 pull 時間。今天的 image 約 180 MB(python:3.13-slim 約 120 MB,加上 .venv 約 60 MB),在 1 Gbps 網路約 1.4 秒;如果走 GitHub Container Registry 的 cache,最快可壓到 0.5 秒。CI 上第一次建置約 60 秒,第二次走 cache 約 10 秒。
Compose v2 的另一個好處是 docker compose watch:開發時改 source 自動 rebuild。在我們的 stack 下,watch 會自動重新跑 uv sync + reload uvicorn,比 Day 30 介紹的 bind mount 還好用。我們把 watch 留給 dev profile,正式部署用 up -d。
生產部署還有一個常見議題:docker compose.yml 不該直接 push 進 main branch 給 production 跑;正式環境通常用 helm、kustomize、terraform 等工具。把 docker-compose 視為「本機與 staging 的工具」,production 換成 Kubernetes 或 AWS ECS / Fargate,是更穩健的分工。我們的 README 會在 Day 44 把這段寫清楚。
最後,觀察一下常見的 image 安全議題。第一,USER 一定要設成非 root:很多官方 image(如 python:3.13-slim)預設用 root 跑,駭進來就能改整個檔案系統。第二,COPY 越精簡越好:把 source code 一個個分階段拷貝,避免把 .venv 或 .git 也複製進 image。第三,不要在 image 裡放 secrets:任何金鑰都應該走 .env、env_file:、或外部 secret manager;今天我們示範的架構正是如此。這三條是 2024 年起 CNCF 推薦的容器最低安全基準,務必照著做。除此之外還有四個常見的 hardening 建議:(1)用 read-only root filesystem 配合 tmpfs 處理 /tmp;(2)關閉 process 內不需要的 capabilities(如 NET_RAW);(3)設定 security_opt: no-new-privileges:true 禁止提權;(4)把 read-only secret 用 env_file: 注入並定期 rotate。我們的小型專案暫時用預設值即可,但正式部署前應該再過一次 Docker Bench 的檢查清單,這份清單在 2024 年之後更新到 1.13 版,是業界公認的標竿。
從 Day 31 的 PostgreSQL、Day 33 的 Caddy、Day 40 的 pytest 到今天的 Docker Compose,整個 stack 已經可以在本機或 staging 跑通。明天 Day 42 我們會為這個 stack 加上日誌管理(JSON 格式結構化)、健康檢查的細部指標(/metrics)、以及 PostgreSQL 排程備份(每日 pg_dump + S3),讓整套系統從「能跑」升級為「可維運」。在 production 部署後,這些監控、備份、告警三件事才是真正讓你晚上睡得著覺的關鍵。
額外提醒一下版本對齊。我們 image 用 python:3.13-slim 對齊 Day 1–37 的整個系列;PostgreSQL 用 17 對齊 Day 31;Caddy 用 2.8 對齊 Day 33。版本對齊的意思是「CI 測過的 image 與正式機跑的 image 一致」,不要中途換 image tag。Docker Hub 與 GHCR 都支援 digest pinning(用 @sha256:... 鎖定具體 image),如果對安全性要求更高,可以把 yaml 從 image: postgres:17 改成 image: postgres:17@sha256:abc...,CI 上 docker pull 後自動更新這個 digest。
小結
今天把整套預約管理系統打包成 Docker Compose v2 stack。我們寫了多階段 Dockerfile(runtime image 約 180 MB,含 Python 3.13、FastAPI 0.116、SQLModel 0.0.24、psycopg);寫了 docker-compose.yml 串起 PostgreSQL 17、FastAPI、Caddy 2.8 三個服務,搭配 healthcheck、depends_on condition、named volume;用 .env.example 與 secrets 隔離走環境變數管理。整個 stack 在 docker compose up -d 後約 30 秒內就緒,curl http://localhost/health 回 200;沒裝 Docker 的讀者也能用 uvicorn + 本機 PostgreSQL 完成同樣驗證。所有資料延續 Day 35–40,都是虛構示範。明天 Day 42 會把這套 stack 加上監控、日誌、備份,把「上線」升級成「可維運」。
這是一個「在 staging 可以放給客戶 demo、在 production 可以撐小型工作室月流量」的 stack;規模再大就要拆服務、改 Kubernetes。對多數讀者來說,今天這個版本其實已經非常足夠:我們的容器小、Caddy 設定精簡、Alembic 在啟動時跑一次即可,整個系統對資源的需求量低,2 核 CPU + 2 GB 記憶體就能跑得順。剩下的考量就是維運,包含 Day 42、43、44 連續三天的主題。事實上,前 40 天的工作只是把東西做出來,真正的工程師價值在 41–44 這四天:能不能讓系統「撐過半夜兩點、撐過業務成長、撐過交接」這些看似不重要的瑣事,都是維運紀律的範疇。我們的內容安排也是這個原因:先會做出來、再學會讓它上線、最後學會讓它活得久,這順序也是真實世界的學習順序。
最後給一個具體的「上線一天 24 小時的時間軸」範例,把今天這些容器的能力用上:00:00 PostgreSQL 跑 nightly vacuum 與 pg_dump;06:00 FastAPI container 收到第一個排程器觸發、寄出提醒信;09:00 管理者登入後台看當日預約,用 HTMX 確認 5 筆;14:00 Caddy 自動續期 Let's Encrypt 憑證;18:00 健康檢查發出每日小結的 log;22:00 壓力測試排程(Day 43)跑一輪;23:55 backup cron 收完一天的 pg_dump 到 NAS。整條時間軸都是自動跑、沒有人需要半夜被叫起來,這就是可維運的樣子。我們 Day 42 會把監控、日誌、備份三件一次到位。
結語
今天的重點是把「在本機跑得通」變成「在任何機器的容器裡跑得通」。我們用多階段 Dockerfile 控管 image 大小、用 Compose v2 的 healthcheck 與 depends_on condition 管啟動順序、用 named volume 保資料持久、用 Caddy 幫正式環境上 HTTPS、用 .env 隔離 secrets。沒有 Docker 的讀者也不必放棄,文章的替代方案段落用 uvicorn + 本機 PostgreSQL 走完整個 stack;差異只在容器層,業務邏輯與測試完全一致。明天,我們會為這套 stack 加上 Sentry 風格的錯誤追蹤、Prometheus metrics、與 PostgreSQL 的每日備份腳本,讓「可上線」變成「可維運」。
延伸資源
- Docker 官方 Compose v2 說明(2025):
https://docs.docker.com/compose/,depends_oncondition、healthcheck、profiles 的標準寫法。 - Caddy 2.8 官方文件(2025):
https://caddyserver.com/docs/,Caddyfile與 HTTPS 自動憑證。另外兩份權威指南值得在 production 前讀完:第一份是 12factor(https://12factor.net/)關於設定、依賴、log 的三條;第二份是 Docker 官方 production guide(https://docs.docker.com/develop/dev-best-practices/)關於 image 最小化、resource limit、user 設定的建議。把這兩份消化完,今天的 stack 就能無痛擴張到 production。 - PostgreSQL 17 官方 image(2025):
https://hub.docker.com/_/postgres,pg_isready與初始化 script 的標準做法。 - psycopg 3 官方文件(2025):
https://www.psycopg.org/psycopg3/docs/,FastAPI + SQLModel + PostgreSQL 的連線字串寫法。 - Twelve-Factor App(2017,跨年代經典):
https://12factor.net/,為什麼要用環境變數而非寫死設定檔。
留言
張貼留言