メインコンテンツへスキップ
友田 陽大
Pydantic・型安全バリデーション
Python
Pydantic
pydantic-settings
設定管理
セキュリティ
型安全

pydantic-settings 実践ガイド:型安全な設定管理とシークレット保護で12-factorを実現する

pydantic-settings公式ドキュメントに忠実に、BaseSettingsの型付き設定モデル、.envとenv_nested_delimiter、SecretStr/secrets_dirによるシークレット保護、設定の優先順位、settings_customise_sourcesによるJSON/TOML/クラウド(AWS/Azure/GCP)連携、CLIまで、本番運用に耐える設定管理を実コードで解説します。

公開日
読了時間
11分
著者
友田 陽大
シェア

導入:os.environ["..."] は本番設定の負債である

設定を os.environ["DATABASE_URL"] で都度読むコードは、一見シンプルですが、本番で必ず破綻します。理由は 4 つ。

  1. 型が str 固定os.environ["MAX_CONNECTIONS"] は文字列。int(...) 変換を読むたびに書き、忘れればバグる。
  2. 存在保証がない:環境変数を設定し忘れても、その値を実際に使う瞬間まで誰も気づかない。本番の深夜に KeyError で落ちる。
  3. デフォルトが散在するos.environ.get("DEBUG", "false") が各所にバラまかれ、どれが正なのか分からなくなる(DRY 違反)。
  4. シークレットがログに漏れる:設定オブジェクトを 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 させる

BaseSettingspydantic 本体ではなく別パッケージです(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 / SettingsConfigDictfrom pydantic_settings import ...、一方 SecretStr / Field / AliasChoicesfrom 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. シークレットを守る:SecretStrsecrets_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 有効時)一時的な上書き・運用コマンド
2Settings(...) への初期化引数テストでの明示的な注入
3環境変数本番・ステージングの設定
4.env ファイルローカル開発
5secrets_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
AWSAWSSecretsManagerSettingsSourcepydantic-settings[aws-secrets-manager]
AzureAzureKeyVaultSettingsSourcepydantic-settings[azure-key-vault]
GCPGoogleSecretManagerSettingsSourcepydantic-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・シークレット保護の三点で本番品質に引き上げます。本記事の要点を再掲します。

  1. BaseSettings(別パッケージ) で設定を型付きモデルに集約し、起動時に Fail Fast させる。
  2. .env / env_prefix / env_nested_delimiter で柔軟に読み込み、複雑な型は JSON でパースする。
  3. SecretStr でシークレットを自動マスクし、コンテナでは secrets_dir でファイルから読む。
  4. 優先順位は 6 段階(CLI>init>env>.env>secrets>default)。settings_customise_sources で並べ替え・拡張できる。
  5. JSON/TOML/YAML とクラウド Secret Manager を設定ソースとして合成し、シークレットを直接・安全に読む。
  6. 設定は lru_cache でシングルトン化し、テストでは直接注入で上書きする。

「動くコード」と「10 年運用できるコード」の差は、設定とシークレットをどこで・どう堰き止めるかに表れます。pydantic-settings は、その境界を型で表現する最良の道具です。

公式の一次情報として、以下を本記事の観点で再読することをお勧めします。


本番運用に耐える設定・シークレット管理のご相談

筆者は、環境分野のサーバーレス決済プラットフォームをはじめ、シークレットと設定の取り扱いが事業の信頼性に直結するシステムを設計・運用してきました。環境変数の散在・シークレットの平文露出・設定ミスによる本番障害——これらは「設定を型付きモデルに集約し、境界で検証する」だけで構造的に防げます。pydantic-settings を用いた 12-factor 準拠の設定管理・クラウド Secret Manager 連携・環境別設定の整理を、生成 AI を活用して高速かつ高品質に実装します。お気軽にご相談ください。

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事で解説した技術の適用事例

環境分野のサーバーレス決済プラットフォーム(フルスタック開発・決済信頼性レイヤーを主導)

ケーススタディを見る