Web Day 29 環境變數與設定管理
執行需求:CPU 可跑。今天是「前端整合」這個區塊的最後一篇,也是「上線」這個區塊的第一篇。我們要做的是:把硬編碼的設定(Web Day 27 的 SECRET = "dev-secret-change-me"、Web Day 25 的 BASE = "http://127.0.0.1:8000")抽離出來,改用 pydantic-settings 2.x 管理。完成後,你的程式碼裡不會再有任何「裸字串」當作設定值——所有可調整的參數都來自環境變數或 .env 檔案,可以依 dev / staging / prod 切換。
引言
前面二十八天,我們為了「讓範例能跑」寫了不少 hardcoded 字串:API URL、JWT secret、log 層級、CORS 白名單。在本機開發這樣沒問題,但程式碼要上線時就會爆掉——正式環境的 URL 不同、secret 不一樣、CORS 白名單更嚴格。我們需要一個系統性的做法:把「程式碼」與「設定」分開,把設定外部化到環境變數或設定檔。
這篇文章會用 pydantic-settings 2.x(2025 年 7 月的主流版本)做完整的設定管理。我們會建立一個 Settings class 把所有設定欄位化、用 .env 檔案做本機預設值、用環境變數做正式覆寫、用多重環境(dev/staging/prod)做切換。我們也會涵蓋「敏感資訊不進版控」、「測試時用隔離設定」、「啟動時驗證設定完整性」這些工程細節。pydantic-settings 2.x 的好處是「型別檢查 + 預設值 + 驗證」三件事一次做完,比 os.environ + 手動轉型乾淨很多。
為什麼要外部化設定
十二因子應用(12-Factor App)方法論把「設定」列為第三個原則:在環境中儲存設定。這聽起來抽象,但實際意義很明確——同一份程式碼要在不同環境跑(開發、測試、正式),不能因為「換個 URL」就要改程式碼。違反這個原則的典型症狀:
if DEBUG: BASE_URL = "http://localhost" else: BASE_URL = "https://api.example.com"——把環境判斷寫進商業邏輯。- 把 secret 直接寫在
main.py然後 commit 到 Git,結果 secret 永遠公開。 - 不同工程師本機用不同設定,「在我電腦可以跑」變成日常工作模式。
把設定外部化解決這三個問題:環境變數決定行為、敏感資訊不入版控、.env.example 列出需要的所有 key(不含真實值)、每位工程師複製一份 .env 改自己的值。
| 來源 | 適合的設定 | 不適合 |
|---|---|---|
| 環境變數 | 正式部署、CI/CD、敏感資訊 | 需要預設值的本機開發 |
.env 檔案 |
本機開發、團隊共享的非敏感設定 | 正式部署(不進容器) |
| 設定檔(YAML/JSON) | 複雜的結構化設定 | 簡單的 key-value |
| 密鑰管理服務(Vault、AWS Secrets Manager) | 高度敏感的資訊 | 一般設定 |
用 pydantic-settings 建立 Settings 類別
pydantic-settings 2.x 是 Pydantic 的官方延伸套件,把環境變數讀取、型別轉換、驗證整合進單一類別。我們建立一個 Settings class:
# config.py
# 集中管理所有設定(pydantic-settings 2.x)
from typing import Literal
from pydantic import Field, PostgresDsn, RedisDsn, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""應用設定:從環境變數或 .env 檔案讀取"""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
# 環境識別
environment: Literal["dev", "staging", "prod"] = "dev"
debug: bool = False
# 應用基本
app_name: str = "預約管理 API"
host: str = "127.0.0.1"
port: int = 8000
workers: int = 1
# 認證(敏感)
jwt_secret: str = Field(min_length=32)
jwt_algorithm: str = "HS256"
access_token_ttl_seconds: int = 15 * 60
refresh_token_ttl_seconds: int = 7 * 24 * 60 * 60
# 資料庫
database_url: str = "sqlite:///./app.db"
database_echo: bool = False
# CORS
cors_allowed_origins: list[str] = Field(default_factory=list)
# Logging
log_level: str = "INFO"
@field_validator("cors_allowed_origins", mode="before")
@classmethod
def split_csv(cls, v):
# 環境變數通常是逗號分隔字串,這裡自動轉成 list
if isinstance(v, str):
return [s.strip() for s in v.split(",") if s.strip()]
return v
@field_validator("workers")
@classmethod
def workers_at_least_one(cls, v: int) -> int:
if v < 1:
raise ValueError("workers 至少 1")
return v
@field_validator("jwt_secret")
@classmethod
def jwt_secret_not_dev_in_prod(cls, v: str, info) -> str:
env = info.data.get("environment", "dev")
if env == "prod" and v.startswith("dev-") or v == "change-me":
raise ValueError("正式環境禁止使用 dev 開頭或 change-me 的 JWT secret")
return v
# module-level singleton
settings = Settings()
這個 Settings class 展示 pydantic-settings 2.x 的核心能力。BaseSettings 子類別自動從 .env 與環境變數讀值(環境變數優先)。Field(min_length=32) 確保 JWT secret 長度足夠;Literal["dev", "staging", "prod"] 限制 environment 只能是這三個值、輸入錯就拋驗證錯誤。cors_allowed_origins: list[str] 是 list 欄位,預期用逗號分隔字串(CORS_ALLOWED_ORIGINS="http://localhost:3000,http://localhost:8000"),@field_validator(mode="before") 自動 split。
最關鍵的是 jwt_secret_not_dev_in_prod 這個自訂驗證器:正式環境禁止使用 dev 開頭或 "change-me" 的 secret。這是「啟動時把關」的好習慣——不要等到 secret 真的被攻擊者拿來簽 token 才發現設定錯了。這類驗證可以在 staging 就抓到,比正式出事好太多。
把設定整合進應用
把所有 hardcoded 值換成 settings.xxx:
# main.py
# 把 Web Day 25–28 的硬編碼值換成 settings 引用
import logging
import uvicorn
from fastapi import FastAPI, WebSocket
from fastapi.middleware.cors import CORSMiddleware
from config import settings
def create_app() -> FastAPI:
app = FastAPI(
title=settings.app_name,
debug=settings.debug,
)
# CORS:從設定讀白名單
if settings.cors_allowed_origins:
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_allowed_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Logging:依設定調整層級
logging.basicConfig(
level=settings.log_level,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
return app
app = create_app()
if __name__ == "__main__":
uvicorn.run(
"main:app",
host=settings.host,
port=settings.port,
workers=settings.workers,
reload=settings.debug,
)
這份檔案展示「設定如何驅動行為」。reload=settings.debug 在開發時自動重啟、正式環境關掉;workers=settings.workers 在生產多開幾個 process;CORS 白名單從設定讀,正式環境只允許正式網域。create_app() 是工廠函式,方便測試時建立不同設定的實例(Web Day 16 已示範過)。
分環境設定檔
實務上我們會為不同環境準備不同的預設值。最常見的做法是用 .env.dev、.env.staging、.env.prod 三個檔案,啟動時用 ENV_FILE 環境變數指定:
# config.py 修訂:支援多環境
from pydantic_settings import BaseSettings, SettingsConfigDict
import os
class Settings(BaseSettings):
model_config = SettingsConfigDict(
# 優先讀 ENV_FILE,其次讀 .env
env_file=os.environ.get("ENV_FILE", ".env"),
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
environment: str = "dev"
# ... 其他欄位同上
settings = Settings()
啟動時指定環境:
# 開發(本機)
ENV_FILE=.env.dev python main.py
# 正式(容器)
ENV_FILE=.env.prod gunicorn main:app --workers 4
.env.dev 範例:
# .env.dev
ENVIRONMENT=dev
DEBUG=true
JWT_SECRET=dev-secret-please-change-32chars-min
DATABASE_URL=sqlite:///./dev.db
CORS_ALLOWED_ORIGINS=http://localhost:3000
LOG_LEVEL=DEBUG
.env.prod 範例:
# .env.prod
ENVIRONMENT=prod
DEBUG=false
JWT_SECRET=CHANGE_ME_TO_RANDOM_64_CHARS_FROM_VAULT
DATABASE_URL=postgresql://app:secret@db.internal:5432/booking
CORS_ALLOWED_ORIGINS=https://booking.example.com
LOG_LEVEL=WARNING
WORKERS=4
這兩個檔案的差別在於正式環境把所有「便利開發」的設定關掉(DEBUG=false、LOG_LEVEL=WARNING),並把敏感資訊指向正式資源(資料庫連線、生產網域)。注意 JWT_SECRET=CHANGE_ME... 是 placeholder——正式部署前要從 Vault 或密鑰管理服務注入真實值,不會出現在 .env.prod 裡。
敏感資訊的管理
JWT secret、資料庫密碼、API 金鑰這些敏感資訊不該放在 .env.prod 檔案(檔案可能被 commit、可能被誤傳)。正確的做法是用密鑰管理服務:
| 服務 | 特色 | 適合規模 |
|---|---|---|
| 環境變數(手動設定) | 簡單、無外部依賴 | 小型專案 |
| Docker secrets / k8s secrets | 容器編排整合 | 中型團隊 |
| HashiCorp Vault | 完整 audit log、動態 secret | 大型 / 合規需求 |
| 雲端密鑰管理(AWS Secrets Manager / GCP Secret Manager) | 雲原生整合 | 雲端部署 |
對大多數小團隊,環境變數搭配 .env.example(不含真實值)就夠了。我們的 .env.example:
# .env.example:列出所有 key,不含真實值
# 複製成 .env 後填入真實值
ENVIRONMENT=dev
DEBUG=true
HOST=127.0.0.1
PORT=8000
WORKERS=1
# 認證:dev 可以用 dev- 開頭;prod 必須是真實亂數
JWT_SECRET=please-generate-with-openssl-rand-base64-48
JWT_ALGORITHM=HS256
# 資料庫
DATABASE_URL=sqlite:///./app.db
DATABASE_ECHO=false
# CORS:逗號分隔
CORS_ALLOWED_ORIGINS=http://localhost:3000
# Logging
LOG_LEVEL=INFO
這個檔案 commit 到 Git,所有工程師看得到「需要哪些 key」、但看不到值。新工程師複製成 .env、填自己的值。CI/CD 通常在 pipeline 設定真實環境變數、不依賴 .env 檔案。
產生安全 JWT secret 的指令:
# 產生 48 bytes 隨機 base64 字串
openssl rand -base64 48
# 輸出(範例):7HjK9mNqP2vR3sT4uV5wX6yZ1aB0cD8eF9gH0iJ1kL2mN3oP4qR=
用 pytest 驗證設定
設定錯誤往往在啟動時才發現,但這時已經來不及。我們用 pytest 在啟動前驗證設定:
# test_config.py
# 設定驗證測試
import pytest
from pydantic import ValidationError
from config import Settings
def test_default_settings_load(tmp_path, monkeypatch):
# 沒有 .env 時應用預設值
monkeypatch.chdir(tmp_path)
s = Settings()
assert s.environment == "dev"
assert s.jwt_algorithm == "HS256"
def test_reads_env_file(tmp_path, monkeypatch):
env_file = tmp_path / ".env"
env_file.write_text(
"ENVIRONMENT=staging\n"
"JWT_SECRET=test-secret-with-at-least-32-chars\n"
"DATABASE_URL=sqlite:///./staging.db\n"
)
monkeypatch.chdir(tmp_path)
s = Settings()
assert s.environment == "staging"
assert s.database_url == "sqlite:///./staging.db"
def test_env_var_overrides_file(tmp_path, monkeypatch):
# 環境變數優先於 .env 檔案
env_file = tmp_path / ".env"
env_file.write_text("ENVIRONMENT=dev\n")
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("ENVIRONMENT", "prod")
monkeypatch.setenv(
"JWT_SECRET", "a-real-64-char-secret-not-starting-with-dev-or-change"
)
s = Settings()
assert s.environment == "prod"
def test_jwt_secret_min_length():
with pytest.raises(ValidationError):
Settings(JWT_SECRET="too-short")
def test_environment_literal():
with pytest.raises(ValidationError):
Settings(ENVIRONMENT="unknown", JWT_SECRET="x" * 40)
def test_prod_rejects_dev_secret():
with pytest.raises(ValidationError):
Settings(
ENVIRONMENT="prod",
JWT_SECRET="dev-this-is-not-allowed-in-production",
)
def test_cors_csv_parsing(tmp_path, monkeypatch):
env_file = tmp_path / ".env"
env_file.write_text(
"JWT_SECRET=test-secret-with-at-least-32-chars\n"
"CORS_ALLOWED_ORIGINS=http://a.com,http://b.com\n"
)
monkeypatch.chdir(tmp_path)
s = Settings()
assert s.cors_allowed_origins == ["http://a.com", "http://b.com"]
def test_workers_must_be_positive():
with pytest.raises(ValidationError):
Settings(WORKERS=0, JWT_SECRET="x" * 40)
八個測試覆蓋了設定系統的所有關鍵路徑:預設值、讀檔案、環境變數優先、長度驗證、Literal 限制、prod 拒絕 dev secret、CSV 解析、正整數。tmp_path 是 pytest 內建的暫存目錄 fixture;monkeypatch.chdir(tmp_path) 把當前目錄切到暫存目錄,這樣 Settings() 找不到 .env 檔案時就會用預設值或環境變數。monkeypatch.setenv(...) 在測試結束後自動清除環境變數,避免污染其他測試。
關鍵的 test_prod_rejects_dev_secret 展示了「正式環境拒絕 dev secret」的驗證——這個測試保證未來就算改了 jwt_secret_not_dev_in_prod 的邏輯,只要不小心改壞,CI 會立刻失敗。
用 FastAPI Depends 注入設定
另一個好做法是把 Settings 透過 FastAPI 的 Depends 注入,而不是直接 import singleton:
# dependencies.py
# 把設定包成 dependency,方便測試時覆寫
from functools import lru_cache
from fastapi import Depends
from config import Settings
@lru_cache
def get_settings() -> Settings:
# lru_cache 確保整個 process 只讀一次設定
return Settings()
# 在 endpoint 裡用:
# def endpoint(settings: Settings = Depends(get_settings)):
# secret = settings.jwt_secret
lru_cache 確保 Settings() 只實例化一次(讀環境變數很貴)。在測試裡可以用 app.dependency_overrides[get_settings] = lambda: test_settings 替換,這是 FastAPI 官方推薦的測試模式。
常用設定模式速查
把幾個常見的設定模式整理成速查表,方便日後套用:
| 模式 | 寫法 | 用途 |
|---|---|---|
| 布林值 | debug: bool = False |
DEBUG、feature flag |
| 整數(含範圍) | port: int = Field(default=8000, ge=1, le=65535) |
port、timeout、TTL |
| 字串列舉 | env: Literal["dev","prod"] |
環境識別 |
| list 從 CSV | origins: list[str] + field_validator(split_csv) |
CORS 白名單 |
| SecretStr | secret: SecretStr |
密碼、API 金鑰 |
| 選填設定群組 | redis: RedisSettings | None = None |
分模組的子設定 |
SecretStr 是 Pydantic 內建的「不會印到 log」的密碼型別。把它用在 secret 欄位可以避免不小心把密碼印到 traceback:
# config.py:用 SecretStr 保護敏感資訊
from pydantic import SecretStr
class Settings(BaseSettings):
# ...
jwt_secret: SecretStr = Field(min_length=32)
database_password: SecretStr | None = None
存取 SecretStr 時用 settings.jwt_secret.get_secret_value() 才能拿到明文;印 settings.jwt_secret 只會看到 **********,不會洩漏。
常見錯誤與踩雷
第一個常見踩雷:.env 進了版控。.gitignore 一定要加 .env;commit 之前 git status 確認沒有 .env。如果不小心 commit 了,secret 立刻撤銷、重新產生、推到所有機器。
第二個常見踩雷:JWT_SECRET=change-me 推到正式環境。正式部署的 checklist 一定要有「檢查每個 secret 不是 placeholder」。我們的 jwt_secret_not_dev_in_prod 驗證器就是擋這個——如果忘了換 secret、應用會在啟動時崩潰,而不是被攻擊者發現。
第三個常見踩雷:把不同環境的 .env 都 commit。每個環境的敏感資訊不同,commit 任何一份都會洩漏。建議:.env 都不進版控、只有 .env.example 進;正式部署用雲端密鑰管理。
第四個常見踩雷:在測試裡用真的 Settings()。測試本來就要隔離,用真設定可能會碰到「測試環境沒設某個 env var、應用崩潰」。對應策略:在 conftest.py 用 monkeypatch.setenv(...) 設定測試專用的環境變數,或用 Settings(...) 直接傳值。
第五個常見踩雷:Settings() 在 import 時就讀取。pydantic-settings 在物件建立時就讀環境變數,如果這時環境變數不完整(例如部分 CI 環境沒設),整個應用 import 就會崩潰。對應策略:在 main.py 內呼叫 Settings()、不要在 module 層級;或在 model_config 設 validate_default=False。
效能與實務提醒
Settings() 物件很輕量,但讀取 .env 檔案有檔案 I/O 成本。我們用 @lru_cache 確保只讀一次,避免每個請求都重新讀檔。在 100 RPS 的應用下,這可以省下可觀的時間。
另一個提醒:pydantic-settings 2.x 預設不會讀 .env.local(常見慣例是 dev 個人設定)。我們可以在 env_file 設定成 [".env.local", ".env"](優先讀 local),讓每個工程師用自己的 .env.local 覆蓋預設值。
最後一個細節:Settings 的設定變動需要重啟應用才生效。如果你想做「不重啟熱更新設定」(例如調 log level 不重啟),可以讀環境變數 + 自己寫 reload 機制、或用專門的設定中心(etcd、Consul)。對大多數應用,重啟就夠了——Web Day 30 的 Docker 化會讓重啟成本接近零。
啟動時檢查設定完整性的範本
我們在前面的 jwt_secret_not_dev_in_prod 驗證器展示了「啟動時驗證」的好處。再擴充幾個常見的檢查,把整套啟動檢查寫進 main.py:
# startup_checks.py
# 啟動時做完整的健康檢查
import sys
from config import settings
def check_database_url() -> list[str]:
errors = []
if not settings.database_url:
errors.append("DATABASE_URL 不能為空")
return errors
def check_cors_in_prod() -> list[str]:
errors = []
if settings.environment == "prod":
if not settings.cors_allowed_origins:
errors.append("正式環境必須設定 CORS 白名單")
if "http://" in " ".join(settings.cors_allowed_origins):
errors.append("正式環境禁止 http:// 的 CORS origin")
return errors
def check_workers_in_prod() -> list[str]:
errors = []
if settings.environment == "prod" and settings.workers < 2:
errors.append("正式環境 WORKERS 至少 2")
return errors
def run_all_checks() -> None:
all_errors = []
all_errors.extend(check_database_url())
all_errors.extend(check_cors_in_prod())
all_errors.extend(check_workers_in_prod())
if all_errors:
print("啟動檢查失敗:")
for e in all_errors:
print(f" - {e}")
sys.exit(1)
print(f"啟動檢查通過:{settings.environment} 環境")
在 main.py 開頭呼叫 run_all_checks(),啟動時就把問題抓出來。這比「應用跑起來才 500」好太多。Web Day 34 會把這套邏輯擴充成正式的健康檢查端點(/health),給監控系統與負載平衡器用。
把設定管理納入 CI/CD
設定管理不只在程式裡,也要延伸到 CI/CD。GitHub Actions(Web Day 32 詳細展開)的 workflow 應該把 .env 與 secret 分開處理:
- 非敏感設定:放在
.env.staging檔案、commit 到 repo、CI 直接讀。 - 敏感設定:放在 GitHub Actions 的
Settings → Secrets,CI 用secrets.JWT_SECRET引用,不進 log。
GitHub Actions 的 secret 在日誌中會被自動遮罩(***),但仍要避免「印出整個環境變數」這種行為——遮罩只對「字串完全等於 secret 名稱」有效,部分遮罩(例如只印 secret 的前 4 字元)會洩漏。
多服務共享設定的設計
「預約管理系統」貫穿專案會有多個服務:FastAPI(後端)、Next.js(前端)、PostgreSQL(資料庫)、Redis(快取)、Caddy(反向代理)。每個服務都有自己的設定,但會共享一些「跨服務」的設定(例如資料庫 URL)。常見做法是把共用設定抽到一個 config/common.env 檔案,每個服務用 env_file 引用:
# docker-compose.yml(Web Day 41 完整版)
services:
api:
env_file:
- config/common.env
- config/api.env
environment:
- DATABASE_URL=postgresql://app:secret@db:5432/booking
web:
env_file:
- config/common.env
- config/web.env
db:
env_file:
- config/db.env
config/common.env 放共用設定(如 ENVIRONMENT=staging),每個服務自己的 .env 放專屬設定(如資料庫密碼、API 金鑰)。Docker Compose 的 env_file 支援多檔案、後面的會覆蓋前面的,這讓設定組合有彈性。
敏感資訊(如資料庫密碼)通常用 Docker secrets(Swarm 模式)或 Kubernetes secrets 注入,不放進 .env 檔案。今天的範例先用 env_file 簡化、貫穿專案 Day 41 會正式用 Docker Compose 的完整機制。
設定演進的策略
應用成長後,設定會越來越多,Settings class 會膨脹。建議的演進策略:
- 第一階段:所有設定放在單一
Settingsclass(今天示範的方式)。 - 第二階段:把相關設定組成巢狀結構,例如
DatabaseSettings、AuthSettings、LoggingSettings,每個獨立可驗證。 - 第三階段:把巢狀結構拆成獨立的設定檔(如
config/database.py、config/auth.py),每個檔案用各自的 pydantic model。
貫穿專案「預約管理系統」會走到第二階段:DatabaseSettings 處理連線池與遷移、AuthSettings 處理 JWT 與 OAuth、LoggingSettings 處理 log 輸出。今天先建立基礎結構,未來章節會逐步擴充。
回顧與銜接
今天結束後,我們的應用有了完整的「設定管理」能力:所有設定欄位化、型別化、驗證化;環境變數與 .env 分層;敏感資訊不進版控;正式環境有啟動檢查。這些能力讓 Web Day 30 的 Docker 化、Day 31 的 PostgreSQL 上線、Day 32 的 CI/CD 都有乾淨的基礎。沒有設定管理就上線,會發生「為什麼正式環境連不到資料庫」這種痛苦問題——把所有設定外部化、變動只需改環境變數,是把「我電腦能跑」變成「正式環境能跑」的工程紀律。
明天我們進入 Docker 化,把這個能跨環境跑的應用打包成容器、部署到任何支援 Docker 的平台。Web Day 30 標示為「需 Docker」,沒有 Docker 的讀者可以先讀全文、之後再補做;有了今天建立的設定管理,明天的工作會非常順暢。
小結
今天把「設定管理」這個上線前的關卡打通。我們用 pydantic-settings 2.x 建立 Settings 類別,所有設定欄位化、型別化、驗證化;用 .env 檔案做本機預設值、用環境變數做正式覆寫;用驗證器擋「prod 用 dev secret」這類錯誤;用 pytest 保護設定不被改壞。整合進 FastAPI 後,main.py 不再有任何 hardcoded 字串,整個應用可以用同一份程式碼在 dev/staging/prod 跑。明天(Web Day 30)我們進入 Docker 化:把這個能跨環境跑的應用打包成容器,讓部署從「改檔案」變成「拉新 image」。
結語
今天的重點是「設定外部化是上線的基本功」。我們用 pydantic-settings 2.x 把所有設定欄位化、用環境變數與 .env 分層管理、用驗證器擋 prod 用 dev secret 的錯誤、用 pytest 保護設定邏輯。讀完這篇你應該能回答:為什麼 Settings 要驗證?.env 與環境變數的優先順序?怎麼防止 secret 進版控?正式部署要怎麼注入 secret?
明天,我們進入 Docker 化。Docker Compose 會讓「部署一個完整應用(API + 資料庫 + 反向代理)」變成「拉 image、設定環境變數、啟動」三個步驟。Web Day 30 標示為「需 Docker」,沒裝 Docker 的讀者可以先用本機 uvicorn + 本機 SQLite 跟著做、之後再回頭補 Docker。準備好迎接容器化時代了嗎?我們出發。
延伸資源
- pydantic-settings 2.x 官方文件(2025):
https://docs.pydantic.dev/latest/concepts/pydantic_settings/,BaseSettings、SettingsConfigDict、Sources 的完整用法。 - 十二因子應用(2025):
https://12factor.net/config,設定外部化的經典方法論。 - openssl rand 用法(2025):
https://www.openssl.org/docs/manmaster/man1/rand.html,產生高品質亂數作為 secret。 - OWASP Secrets Management Cheat Sheet(2024):
https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html,secret 不進版控、不在 log、不在錯誤訊息的具體做法。 - FastAPI Settings and Environment Variables(0.116,2025):
https://fastapi.tiangolo.com/advanced/settings/,FastAPI 對 pydantic-settings 的整合慣例。
留言
張貼留言