導入:os.environ["..."] は本番設定の負債である
設定を os.environ["DATABASE_URL"] で都度読むコードは、一見シンプルですが、本番で必ず破綻します。理由は 4 つ。
- 型が
str固定:os.environ["MAX_CONNECTIONS"]は文字列。int(...)変換を読むたびに書き、忘れればバグる。 - 存在保証がない:環境変数を設定し忘れても、その値を実際に使う瞬間まで誰も気づかない。本番の深夜に
KeyErrorで落ちる。 - デフォルトが散在する:
os.environ.get("DEBUG", "false")が各所にバラまかれ、どれが正なのか分からなくなる(DRY 違反)。 - シークレットがログに漏れる:設定オブジェクトを
print/ ログ出力した瞬間、API キーやパスワードが平文で流出する。
pydantic-settings は、この 4 つを構造的に解決します。設定を型付きの単一モデルに集約し、環境変数・.env・シークレットファイル・クラウドの Secret Manager から自動でロードし、必須項目が欠けていれば起動時に ValidationError で止める(Fail Fast)。これは 12-factor App の「設定を環境に格納する」原則を、型安全とシークレット非漏洩の両立で実現する定石です。
本記事は 公式ドキュメント(pydantic-settings 2.14.x)に忠実に、それより一段わかりやすく、最小構成から本番のシークレット管理・クラウド連携までを実コードで解説します。Pydantic 本体の基礎は Pydantic v2 実践ガイド を、設定値の検証ロジックは 高度な型・カスタムバリデータ を併せて参照してください。
1. 最小の設定モデル:起動時に Fail Fast させる
BaseSettings は pydantic 本体ではなく別パッケージです(v2 で分離されました)。
pip install pydantic-settings
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="APP_")
database_url: str # APP_DATABASE_URL(必須)
debug: bool = False # APP_DEBUG("1"/"true" を bool へ型強制)
max_connections: int = Field(default=10, gt=0) # APP_MAX_CONNECTIONS
# アプリ起動時に一度だけ生成。必須項目が欠けていればここで即座に失敗する
settings = Settings()
⚠️ インポート元に注意:
BaseSettings/SettingsConfigDictはfrom pydantic_settings import ...、一方SecretStr/Field/AliasChoicesはfrom pydantic import ...です。from pydantic import BaseSettingsは v1 の書き方で、v2 では動きません。
なぜこれが優れているのか?
Settings() をアプリの起動時に一度だけ呼べば、設定が揃っていない状態でアプリは起動できなくなります(Fail Fast)。settings.max_connections は静的に int と分かり、settings.databse_url のようなタイポは型チェッカーが検出する。debug: bool は "1" / "true" のような環境変数文字列を型強制で bool に変換してくれる。「本番で初めて設定ミスに気づく」のではなく、デプロイ前に検出できるのが最大の価値です。
2. .env と複雑な型:ネストした設定を読む
SettingsConfigDict で読み込み挙動を細かく制御できます。
from pydantic import BaseModel
from pydantic_settings import BaseSettings, SettingsConfigDict
class RedisConfig(BaseModel):
host: str
port: int = 6379
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env", # .env を読む
env_file_encoding="utf-8",
env_prefix="APP_", # APP_ プレフィックスで名前衝突を防ぐ
case_sensitive=False, # 既定。大文字小文字を無視
env_nested_delimiter="__", # ネストを FOO__BAR で表現
extra="ignore", # 余分な環境変数を無視(forbid だと起動失敗)
)
allowed_hosts: list[str] = [] # JSON としてパースされる
redis: RedisConfig
主要オプションと、環境変数からの読み方は次のとおりです。
| 設定 | 効果 |
|---|---|
env_file | .env ファイルのパス |
env_prefix | 環境変数名の接頭辞(APP_DATABASE_URL 等) |
env_nested_delimiter | ネストモデルの区切り(APP_REDIS__HOST) |
case_sensitive | 大文字小文字を区別するか(既定 False) |
extra | 未知の環境変数の扱い(ignore / forbid / allow) |
list / dict / ネストモデルなどの複雑な型は、環境変数を JSON としてパースします。
export APP_ALLOWED_HOSTS='["a.com", "b.com"]' # JSON 配列
export APP_REDIS='{"host": "cache", "port": 6380}' # JSON オブジェクト、または↓
export APP_REDIS__HOST=cache # env_nested_delimiter で個別指定
export APP_REDIS__PORT=6380
💡 JSON と区切り文字は併用できる:
APP_REDISで JSON 全体を渡しつつ、APP_REDIS__PORTで一部だけ上書きする、といった指定が可能です。区切り文字(__)はフィールド名に含めないでください(パースが壊れます)。
3. シークレットを守る:SecretStr と secrets_dir
設定管理で最も事故が多いのがシークレットの漏洩です。API キーやパスワードを普通の str で持つと、ログ出力・例外トレース・model_dump() のすべてで平文露出のリスクがあります。SecretStr はこれを構造的に防ぎます。
from pydantic import SecretStr
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
api_key: SecretStr
db_password: SecretStr
settings = Settings()
print(settings.api_key) # ********** ← マスクされる
print(settings.model_dump()) # {'api_key': SecretStr('**********'), ...}
print(settings.api_key.get_secret_value()) # 実際の値(明示的に取り出した時だけ)
SecretStr で包んだ値は、repr / str / シリアライズのすべてで ********** にマスクされ、実際の値は .get_secret_value() を明示的に呼んだときだけ取り出せます。「うっかりログに出した」事故が、型レベルで起きなくなります。
H3: Docker / Kubernetes のシークレットは secrets_dir
コンテナ環境では、シークレットを環境変数ではなくファイルとしてマウントするのがベストプラクティスです(環境変数は docker inspect 等で見えてしまうため)。secrets_dir は、指定ディレクトリ内のフィールド名と同名のファイルを読み込みます。
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
# /var/run/database_password というファイルの中身が database_password に入る
model_config = SettingsConfigDict(secrets_dir="/var/run")
database_password: str
Kubernetes の Secret を /var/run 配下にマウントすれば、コードを一切変えずにシークレットを安全に注入できます。
⚠️
secrets_dirの挙動:ファイル名はフィールド名(または alias)と一致させる必要があります。該当ファイルがなければ静かにスキップされ、次のソース(環境変数等)にフォールバックします。本番でシークレットが空になる事故を避けるため、必須フィールドにしておき、欠落を起動時に検出させてください。
4. 設定の優先順位:6 段階のルールを把握する
同じ設定が複数のソースで指定されたとき、どれが勝つか。公式が定める優先順位は、高い順に 6 段階です。
| 優先 | ソース | 典型的な用途 |
|---|---|---|
| 1(最強) | CLI 引数(cli_parse_args 有効時) | 一時的な上書き・運用コマンド |
| 2 | Settings(...) への初期化引数 | テストでの明示的な注入 |
| 3 | 環境変数 | 本番・ステージングの設定 |
| 4 | .env ファイル | ローカル開発 |
| 5 | secrets_dir のファイル | コンテナのシークレット |
| 6(最弱) | モデルのデフォルト値 | フォールバック |
この順序を理解していれば、「ローカルの .env を、本番では環境変数で上書きする」「テストでは初期化引数で全部上書きする」といった運用が予測可能になります。順序そのものは次章の settings_customise_sources で並べ替え可能です。
5. ソースを拡張する:JSON/TOML/YAML とクラウド Secret Manager
設定ソースは環境変数だけではありません。settings_customise_sources をオーバーライドすれば、任意のソースを合成・並べ替えできます。
from pydantic_settings import (
BaseSettings,
PydanticBaseSettingsSource,
TomlConfigSettingsSource,
)
class Settings(BaseSettings):
model_config = SettingsConfigDict(toml_file="config.toml")
app_name: str
workers: int
@classmethod
def settings_customise_sources(
cls,
settings_cls,
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
# タプルの順 = 優先順位(先頭が最強)。環境変数 → TOML の順で解決する
return (env_settings, TomlConfigSettingsSource(settings_cls))
組み込みのソースクラスが揃っています:EnvSettingsSource / DotEnvSettingsSource / SecretsSettingsSource / JsonConfigSettingsSource / TomlConfigSettingsSource / YamlConfigSettingsSource / PyprojectTomlConfigSettingsSource / CliSettingsSource。
H3: AWS / Azure / GCP の Secret Manager を直接読む
pydantic-settings は、主要クラウドの Secret Manager との統合を公式に同梱しています。
import os
from pydantic_settings import (
AWSSecretsManagerSettingsSource,
BaseSettings,
PydanticBaseSettingsSource,
)
class Settings(BaseSettings):
db_password: str
api_key: str
@classmethod
def settings_customise_sources(cls, settings_cls, init_settings, env_settings,
dotenv_settings, file_secret_settings):
aws = AWSSecretsManagerSettingsSource(
settings_cls, os.environ["AWS_SECRET_ID"]
)
return (init_settings, env_settings, aws)
| クラウド | ソースクラス | extra |
|---|---|---|
| AWS | AWSSecretsManagerSettingsSource | pydantic-settings[aws-secrets-manager] |
| Azure | AzureKeyVaultSettingsSource | pydantic-settings[azure-key-vault] |
| GCP | GoogleSecretManagerSettingsSource | pydantic-settings[gcp-secret-manager] |
💡 これがアーキテクチャ上効く理由:シークレットを「環境変数にコピーするデプロイスクリプト」を書く必要がなくなり、アプリが起動時に直接 Secret Manager から型付きで読む。シークレットのローテーションもアプリ側のコード変更なしで反映でき、IAM で最小権限を効かせられる。CLAUDE.md の「シークレットをハードコードしない・最小権限」というセキュリティ原則を、設定レイヤーで体現します。なお各クラウド SDK(
boto3等)が必要なので、対応する extra を必ずインストールしてください。
6. CLI:設定モデルからコマンドラインツールを生やす
同じ設定モデルから、コマンドライン引数のパースまで賄えます。バッチや運用スクリプトで重宝します。
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(cli_parse_args=True)
workers: int = 4
verbose: bool = False
# python app.py --workers 8 --verbose で起動できる
settings = Settings()
print(settings.model_dump())
より構造化された CLI(サブコマンド等)には CliApp を使います。
from pydantic_settings import CliApp
settings = CliApp.run(Settings, cli_args=["--workers", "8"])
CLI 引数は優先順位の最上位(第4章)なので、環境変数やデフォルトを一時的に上書きする運用に向きます。ネストフィールドは CLI ではドット記法(--redis.port 6380)で、環境変数では __ 区切りで指定する、という違いに注意してください。
7. テストとベストプラクティス
H3: シングルトン化(lru_cache)
Settings() は毎回ソースを読みに行きます。アプリ全体で一度だけ生成するため、lru_cache でシングルトン化するのが定石です(FastAPI なら Depends と組み合わせます)。
from functools import lru_cache
@lru_cache
def get_settings() -> Settings:
return Settings()
H3: テストでの上書き
設定のテストは、初期化引数による直接注入が最もクリーンです(プロセスの環境変数を汚さないため、並列テストでも安全)。環境ロード経路そのものを検証したいときだけ monkeypatch を使います。
def test_uses_injected_config():
# 環境に依存せず、値を直接注入して上書き(優先順位2位=環境変数より強い)
settings = Settings(database_url="sqlite://", max_connections=1)
assert settings.max_connections == 1
def test_reads_from_env(monkeypatch):
monkeypatch.setenv("APP_MAX_CONNECTIONS", "20") # プレフィックス付きで設定
assert Settings().max_connections == 20
💡 環境別設定の作法:
env_file=(".env", f".env.{os.environ.get('ENV', 'local')}")のように複数の.envを重ねるか、環境ごとにSettingsサブクラスを分けます。いずれにせよ「設定の真実源は型付きモデル一つ」という原則を崩さないこと。設定のテスト容易性は Pydantic テスト戦略 でさらに掘り下げます。
結論:設定を「型システムの一部」にする
pydantic-settings は、設定管理という地味だが事故の多い領域を、型安全・Fail Fast・シークレット保護の三点で本番品質に引き上げます。本記事の要点を再掲します。
BaseSettings(別パッケージ) で設定を型付きモデルに集約し、起動時に Fail Fast させる。.env/env_prefix/env_nested_delimiterで柔軟に読み込み、複雑な型は JSON でパースする。SecretStrでシークレットを自動マスクし、コンテナではsecrets_dirでファイルから読む。- 優先順位は 6 段階(CLI>init>env>.env>secrets>default)。
settings_customise_sourcesで並べ替え・拡張できる。 - JSON/TOML/YAML とクラウド Secret Manager を設定ソースとして合成し、シークレットを直接・安全に読む。
- 設定は
lru_cacheでシングルトン化し、テストでは直接注入で上書きする。
「動くコード」と「10 年運用できるコード」の差は、設定とシークレットをどこで・どう堰き止めるかに表れます。pydantic-settings は、その境界を型で表現する最良の道具です。
公式の一次情報として、以下を本記事の観点で再読することをお勧めします。
本番運用に耐える設定・シークレット管理のご相談
筆者は、環境分野のサーバーレス決済プラットフォームをはじめ、シークレットと設定の取り扱いが事業の信頼性に直結するシステムを設計・運用してきました。環境変数の散在・シークレットの平文露出・設定ミスによる本番障害——これらは「設定を型付きモデルに集約し、境界で検証する」だけで構造的に防げます。pydantic-settings を用いた 12-factor 準拠の設定管理・クラウド Secret Manager 連携・環境別設定の整理を、生成 AI を活用して高速かつ高品質に実装します。お気軽にご相談ください。