---
title: "pydantic-settingsの使い方：BaseSettingsで.env・環境変数・シークレットを型安全に読む"
url: "https://www.issoh.co.jp/tech/details/19769/"
published: 2026-11-14
updated: 2026-11-14
categories: ["開発"]
publisher: "株式会社一創"
---

# pydantic-settingsの使い方：BaseSettingsで.env・環境変数・シークレットを型安全に読む

pydantic-settingsは、Pydanticのモデルと同じ書き方で設定クラスを定義し、環境変数と`.env`ファイルから値を読み込んで型を検証するライブラリです。`BaseSettings`を継承したクラスに`port: int`と書けば、環境変数の文字列`"8000"`は整数に変換され、`"abc"`なら起動の時点で`ValidationError`になります。この記事でコードを示す範囲は、最新版2.16.3（2026年11月時点）でDB接続情報とAPIキーを持つ設定クラスを作り、FastAPIへ`Depends`で注入するまでの手順です。共用の`.env`で起きる「Extra inputs are not permitted」の対処や、`SecretStr`でパスワードをログに出さない書き方も扱います。

## まとめ：pydantic-settingsで設定を読むときの結論

設定値が5つを超え、環境ごとに値が変わるアプリなら、`os.environ`を散らさず`BaseSettings`のクラス1つへ集めます。型と必須項目が起動時に検証され、環境変数の付け忘れをデプロイの段階で止められます。

読み込みの優先順位は「初期化引数＞環境変数＞`.env`＞secretsのファイル＞既定値」です。`.env`を他のツールと共用するなら`env_prefix`を付け、`dotenv_filtering="match_prefix"`で自分の変数だけを読ませます。パスワードとAPIキーは`SecretStr`で受け、本番ではファイルかクラウドの保管庫から渡します。

FastAPIでは設定クラスを`Depends`で注入し、`settings_cached()`か`lru_cache`で読み込みを1回に絞ります。

## pydantic-settings 2.16の導入と環境変数を読む最小コード

まずはインストールと、環境変数を1つ読むだけの最小の設定クラスで動きを確かめます。

### pydantic-settingsの導入とPython 3.11以上の動作要件

[PyPIのpydantic-settings](https://pypi.org/project/pydantic-settings/)では、2026年11月14日時点の最新が2.16.3（2026年11月13日公開）です。2.16.0からPython 3.11以上が必要になり、3.10の環境では2.15系までしか入りません。依存として`pydantic`2.7.0以上と`python-dotenv`1.2.2以上が一緒に入るため、`.env`を読むために別のパッケージを足す必要はありません。

```
# 仮想環境を作り、pydantic-settings を入れる
python3 -m venv .venv
source .venv/bin/activate
pip install "pydantic-settings==2.16.3"

# 入った版を確かめる
python -c "import pydantic_settings; print(pydantic_settings.__version__)"
```

pydantic本体のv1時代は`from pydantic import BaseSettings`で使えましたが、v2で別パッケージに分かれました。古い記事のコードをそのまま貼ると`PydanticImportError`になります。Pydanticそのものの位置付けとv1との違いは、[Pydanticの読み方・BaseModelの使い方とV1との違い](https://www.issoh.co.jp/tech/details/4838/)で整理しています。

### BaseSettingsを継承した設定クラスと型変換・必須項目の検証

次のファイルを`config.py`として置きます。

```
# config.py
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    app_name: str = "sample-api"
    port: int = 8000
    debug: bool = False
    admin_email: str  # 既定値なし＝必須


settings = Settings()
print(settings.model_dump())
```

```
# 必須の admin_email が無いと起動時に止まる
python config.py
# pydantic_core._pydantic_core.ValidationError: 1 validation error for Settings
# admin_email
#   Field required ...

# 環境変数で渡すと、文字列が型に合わせて変換される
ADMIN_EMAIL=ops@example.com PORT=9000 DEBUG=true python config.py
# {'app_name': 'sample-api', 'port': 9000, 'debug': True, 'admin_email': 'ops@example.com'}
```

環境変数名はフィールド名と同じで、既定では大文字と小文字を区別しません。`PORT=abc`と渡せば`int`に変換できず、`ValidationError`で止まる仕組みです。既定値も検証されるため、既定値の型の誤りも起動時に見つかります。

## .envと環境変数を読む優先順位と複数ファイル・共用.envの扱い方

同じ項目が複数の場所に書かれていたとき、どれが勝つかを先に押さえておくと、「設定したのに反映されない」の大半は自分で切り分けられます。

### 初期化引数・環境変数・.env・secrets・既定値の優先順位

[公式のSettings Management](https://pydantic.dev/docs/validation/latest/concepts/pydantic%5Fsettings/)が定める優先順位は、上ほど強い次の6段です。

1. CLI引数（`cli_parse_args`を有効にしたときだけ）
2. `Settings(port=9000)`のような初期化引数
3. 環境変数
4. `.env`ファイルの値
5. `secrets_dir`に置いたファイルの値
6. クラスに書いた既定値

実務で効いてくるのは3と4の関係です。シェルに`export DEBUG=false`が残っていると、`.env`に`DEBUG=true`と書いても`false`のまま動きます。`.env`を書き換えても反映されないときは、まず`env | grep -i debug`で同名の環境変数が無いかを見ます。

### env\_fileに.envと.env.prodを渡したときの後勝ちの仕組み

`.env`を読むには`model_config`に`env_file`を書きます。タプルで複数渡すと前から順に読み、同じキーは後のファイルの値で上書きされる仕組みです。

```
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        # 共通の .env を読んだあと、手元だけの .env.local で上書きする
        env_file=(".env", ".env.local"),
        env_file_encoding="utf-8",
    )

    port: int = 8000
    debug: bool = False
```

無いファイルはエラーにならず読み飛ばされるため、`.env.local`は開発者の手元にだけ置けば済みます。本番で`.env`を読ませたくない場合は、`Settings(_env_file=None)`のように初期化時に`None`を渡します。相対パスは起動したディレクトリから探すため、2.16.0では親ディレクトリまでさかのぼる`env_file_depth`（既定は0）が加わりました。`.env`そのものの記法や、Gitに載せない運用は[dotenvの書き方と読み込み方の解説](https://www.issoh.co.jp/tech/details/3573/)に譲ります。

### 共用の.envで発生する余分な入力エラーの原因と設定による対処

Docker Composeと同じ`.env`を使っていると、次のエラーで起動しないことがあります。

```
pydantic_core._pydantic_core.ValidationError: 1 validation error for Settings
postgres_password
  Extra inputs are not permitted [type=extra_forbidden, input_value='local-pass', input_type=str]
```

原因は2つの既定値の組み合わせです。`BaseSettings`の`extra`は`"forbid"`で、2.16.3のソースでは、`.env`のエントリはフィールドに当たらないものも含めて全部モデルに渡されます。Compose用の`POSTGRES_PASSWORD`がフィールドに無いため、余計な入力として弾かれる仕組みです。環境変数の側はフィールドに当たらないものを無視するので、同じ値を`export`したときは起きません。

対処は3通りあり、おすすめは上から順です。

| 設定                        | .envの余計なキー | 自分の変数の打ち間違い |
| ------------------------- | ---------- | ----------- |
| env\_prefix＋match\_prefix | 無視する       | エラーで気付ける    |
| only\_existing指定          | 無視する       | 黙って無視される    |
| extra=”ignore”            | 無視する       | 黙って無視される    |

`env_prefix="APP_"`と`dotenv_filtering="match_prefix"`を組み合わせると、`APP_`で始まるキーだけがモデルに渡ります。`APP_DEBGU=true`のような打ち間違いは余計な入力として止まり、`POSTGRES_PASSWORD`は無視されます。`extra="ignore"`は手軽ですが、打ち間違えた変数名が既定値のまま黙って動くため、本番で`debug`が効いていないことに気付けません。`extra`の各値の挙動は[ConfigDictの設定一覧](https://www.issoh.co.jp/tech/details/7583/)で比較しています。

## env\_prefixとenv\_nested\_delimiterによるネスト設定

設定が増えてくると、DBやメールなどの単位でまとめたくなります。ここではアプリ全体の設定クラスを、記事の後半まで使う形で組み立てます。

### APP\_のprefixとDATABASE\_\_HOSTでサブモデルへ値を入れる設定

```
# config.py
from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict


class DatabaseSettings(BaseModel):
    host: str = "localhost"
    port: int = 5432
    name: str = "app"
    user: str = "app"
    password: SecretStr


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_prefix="APP_",
        env_nested_delimiter="__",
        env_file=(".env", ".env.local"),
        env_file_encoding="utf-8",
        dotenv_filtering="match_prefix",
    )

    debug: bool = False
    allowed_origins: list[str] = ["http://localhost:3000"]
    database: DatabaseSettings
    openai_api_key: SecretStr
```

```
# .env（Docker Compose と共用。APP_ で始まらない行は読まれない）
POSTGRES_PASSWORD=local-pass
APP_DEBUG=true
APP_ALLOWED_ORIGINS=["https://example.com","https://admin.example.com"]
APP_DATABASE__HOST=db
APP_DATABASE__PASSWORD=local-pass
APP_OPENAI_API_KEY=sk-local-dummy
```

`env_nested_delimiter="__"`を指定すると、`APP_DATABASE__HOST`は`database.host`に入ります。区切りを1文字の`_`にすると、`openai_api_key`のようにフィールド名自体に含まれる`_`まで分割されてしまうため、2文字の`__`にするのが定石です。

`env_prefix`は既定でフィールド名から作る変数名にだけ付き、`validation_alias`で別名を付けたフィールドには付きません。別名にも付けたいときは`env_prefix_target="all"`を指定します。

`alias`・`validation_alias`・`serialization_alias` の3種の違いは「[Pydantic Fieldの使い方：default\_factory・alias・制約の引数一覧とOpenAPIへの反映](https://www.issoh.co.jp/tech/details/19771/)」で解説しています。

### listとdictはJSON文字列で渡すルールと空文字の扱いの違い

上の`APP_ALLOWED_ORIGINS`のように、`list`・`dict`・サブモデルの型を持つフィールドは、環境変数の値をJSONとして解釈します。`APP_ALLOWED_ORIGINS=https://example.com,https://admin.example.com`とカンマ区切りで書くとJSONとして読めず、`SettingsError`で起動に失敗します。カンマ区切りで渡したい事情があるなら、フィールドを`str`で受けて`field_validator`で分割する方が素直です。

空文字の扱いも、設定を読み込む際の落とし穴です。`APP_PORT=`のように値が空の行は、既定（`env_ignore_empty=False`）では空文字として渡され、`int`のフィールドなら`ValidationError`になります。テンプレートの`.env.example`を値を空のまま複製する運用なら、`env_ignore_empty=True`にして空の行を「未設定」として扱わせます。

## SecretStrとsecrets\_dirでパスワード・APIキーをログに出さない実装

設定クラスをそのまま`print`やログに出すと、パスワードまで平文で残ります。ここでは表示を伏せる型と、ファイルから渡す仕組みを組み合わせます。

### SecretStrで表示を伏せget\_secret\_valueで取り出す使い方

先ほどの設定クラスで`password`と`openai_api_key`を`SecretStr`にしたのは、このためです。[PydanticのTypesのAPI](https://pydantic.dev/docs/validation/latest/api/pydantic/types/)に記載のとおり、`SecretStr`は`str()`と`repr()`で値を`**********`に置き換えます。

```
from config import Settings

settings = Settings()
print(settings.openai_api_key)
# **********
print(settings.database)
# host='db' port=5432 name='app' user='app' password=SecretStr('**********')

# 実際に使う場所でだけ元の値を取り出す
api_key = settings.openai_api_key.get_secret_value()
```

伏せられるのは表示とJSON出力までで、`get_secret_value()`で取り出した後は普通の`str`に戻ります。取り出すのはSDKのクライアントを作る1か所に限り、変数に入れて持ち回らないようにします。

### Docker secretsのファイルをsecrets\_dirで読み込む設定と注意

本番では、パスワードを環境変数に置かずファイルで渡す構成もよく使います。環境変数は`docker inspect`や子プロセスから見えるためです。[Docker Composeのsecrets](https://docs.docker.com/compose/how-tos/use-secrets/)は、指定した値をコンテナ内の`/run/secrets/`以下にファイルとして置きます。

```
# compose.yaml
services:
  api:
    build: .
    secrets:
      - app_openai_api_key
secrets:
  app_openai_api_key:
    file: ./secrets/openai_api_key.txt
```

設定クラスの`model_config`に`secrets_dir="/run/secrets"`を足すと、ファイル名をキー、中身を値として読みます。ファイル名には`env_prefix`が付くため、上の例で使うファイル名は`app_openai_api_key`です。ディレクトリが無い環境では警告が出るだけで止まらないため、開発機と本番で同じクラスを使えます。

2.16.3（2026年11月13日）では、`secrets_dir`の外を指すシンボリックリンクを追わないよう修正されました（[v2.16.3のリリースノート](https://github.com/pydantic/pydantic-settings/releases/tag/v2.16.3)）。ネストしたsecretsを読む`NestedSecretsSettingsSource`は2.14.2で同種の問題が直っています。`secrets_dir`を使うなら2.16.3以上に上げておきます。

### AWS Secrets Managerなどクラウドの保管庫から読む拡張の選び方

pydantic-settingsには、クラウドの保管庫から値を読むソースも同梱されています。使うには、PyPIに登録されたextraを付けてインストールします。次の表のGCPはGoogle Cloudの略で、対象の保管庫はGoogle Cloud Secret Managerです。

| 保管庫                     | インストールするextra       |
| ----------------------- | ------------------- |
| AWS Secrets Manager     | aws-secrets-manager |
| AWS SSM Parameter Store | aws-systems-manager |
| Azure Key Vault         | azure-key-vault     |
| GCP Secret Manager      | gcp-secret-manager  |

これらは既定の読み込み順に入っていないため、`settings_customise_sources`を上書きし、返すタプルに追加します（先頭ほど優先）。起動時に1回読んだ値は変わらないので、保管庫側でパスワードを自動ローテーションするなら再起動を伴う運用にします。料金とローテーションの設定は[AWS Secrets Managerの料金とローテーション設定](https://www.issoh.co.jp/tech/details/15888/)で扱っています。

## FastAPIで設定クラスをDependsから注入しテストで差し替える構成

[FastAPI公式のSettings and Environment Variables](https://fastapi.tiangolo.com/advanced/settings/)は、設定をモジュールの変数に置かず、依存関数から渡す形を勧めています。FastAPI 0.162.7の`fastapi[standard]`にはpydantic-settingsも含まれますが、指定は2.0.0以上と緩いため、ここまでの機能を使うなら`pydantic-settings>=2.16.3`を別に固定します。

### lru\_cacheとsettings\_cachedで設定の読み込みを1回にする書き方

`Settings()`は呼ぶたびに環境変数と`.env`、secretsのファイルを読み直して検証します。リクエストごとに作るとディスクの読み込みが毎回走るため、1回だけ作って使い回します。公式の例は`@lru_cache`を付けた関数ですが、2.16.0で同じ役割の`settings_cached()`が`BaseSettings`に加わりました（[v2.16.0のリリースノート](https://github.com/pydantic/pydantic-settings/releases/tag/v2.16.0)）。

```
# main.py
from typing import Annotated

from fastapi import Depends, FastAPI

from config import Settings

app = FastAPI()


def get_settings() -> Settings:
    # 初回だけ読み込み、以降は同じインスタンスを返す（2.16.0以上）
    return Settings.settings_cached()


SettingsDep = Annotated[Settings, Depends(get_settings)]


@app.get("/info")
def info(settings: SettingsDep):
    return {"debug": settings.debug, "db_host": settings.database.host}
```

`settings_cached()`はクラスごとにキャッシュを持ち、`settings_clear_cache()`を呼ぶと次の呼び出しで読み直します。2.15系以前の環境で同じ動きにする方法は、`get_settings`に`@lru_cache`を付けて`return Settings()`と書く形です。`Depends`そのものの仕組みと、`Annotated`で型エイリアスにする書き方は[FastAPIのDependsの使い方](https://www.issoh.co.jp/tech/details/19720/)で詳しく扱っています。

キャッシュはワーカーのプロセスごとに1つです。`--workers 4`なら4回読み込まれ、どこかで値を書き換えても他のワーカーには伝わりません。DBの接続プールのように、設定から作ってアプリ全体で共有する資源は[FastAPIのlifespanでの起動処理](https://www.issoh.co.jp/tech/details/19739/)で作ります。

### dependency\_overridesでテスト用の設定へ差し替える手順

エンドポイントが`get_settings`経由で設定を受け取っていれば、テストでは依存関数ごと差し替えられます。

```
# test_main.py
from fastapi.testclient import TestClient

from config import DatabaseSettings, Settings
from main import app, get_settings


def get_test_settings() -> Settings:
    # .env を読まず、テストに必要な値だけを初期化引数で渡す
    return Settings(
        _env_file=None,
        debug=True,
        database=DatabaseSettings(host="test-db", password="test"),
        openai_api_key="sk-test",
    )


def test_info():
    app.dependency_overrides[get_settings] = get_test_settings
    try:
        client = TestClient(app)
        res = client.get("/info")
        assert res.json() == {"debug": True, "db_host": "test-db"}
    finally:
        app.dependency_overrides.clear()
```

`_env_file=None`は、手元の`.env`でテスト結果が変わるのを防ぐためです。初期化引数は環境変数より優先されるので、CIのランナーに`APP_DEBUG`が残っていても、テストで渡した値が勝ちます。

## pydantic-settingsを採用する条件と使わずに済ませる場面の判断

便利なライブラリですが、すべての設定読み込みに入れる必要はありません。判断の軸は「設定の数」と「型の誤りが起きたときの損害」です。

### pydantic-settingsを使わずos.environで足りる場面と移行の目安

読む値が1〜2個の使い捨てスクリプトや、AWS Lambdaの小さな関数なら、`os.environ["TABLE_NAME"]`で十分です。依存の追加と、2.16.0のようなPython対応版の引き上げに追従する手間の方が重くなります。

次のどれかに当てはまったら、`BaseSettings`へ移す時期です。

- 設定値が5つを超え、`int`や`bool`への変換をあちこちで手書きしている
- 開発・ステージング・本番で`.env`や環境変数を切り替えている
- APIキーやDBのパスワードを扱い、ログへの漏れを防ぎたい
- FastAPIで、テストのたびに設定を差し替えたい

最優先は1つ目です。`os.environ.get("DEBUG") == "true"`のような比較が3か所に散ると、どこか1か所だけ`"True"`や`"1"`を受け付けない、という不一致が必ず起きます。既存のFlaskやDjangoのAPIをFastAPIへ移しながら、設定の持ち方やシークレットの渡し方も整理したい場合は、[API開発・システム連携の受託開発](https://www.issoh.co.jp/service/system/api/)でご相談を受け付けています。

### 実行中の設定変更やフロントエンドとの設定共有で失敗するパターン

pydantic-settingsが向かないのは、実行中に設定を変えたい場面です。機能フラグのように運用中に切り替える値はDBや機能フラグのサービスに置き、pydantic-settingsにはその接続先だけを持たせます。

もう1つは、フロントエンドや他言語のサービスと同じ設定ファイルを共有する場面です。`env_nested_delimiter`の`__`やJSONで書く`list`は、Node.jsのdotenvにとってはただの文字列です。共有するのは接続先のURLのような単純な値に留め、型付きの構造はPython側の設定クラスで組み立てます。言語をまたいだ環境変数の受け渡しは[環境変数の設定方法とDocker・CIでの受け渡し](https://www.issoh.co.jp/tech/details/17616/)で整理しています。

## よくある質問

pydantic-settingsの導入時に出てくることの多い質問に、2026年11月時点の版（pydantic-settings 2.16.3、pydantic 2.14.3）で答えます。

### from pydantic import BaseSettings でエラーになるのはなぜですか？

Pydantic v2で`BaseSettings`が本体から外れ、pydantic-settingsという別パッケージに移ったためです。pydantic 2.14.3で古い書き方をすると、`PydanticImportError`で「BaseSettings has been moved to the pydantic-settings package」と表示されます。インストールには`pip install pydantic-settings`を使い、インポート文の変更先は`from pydantic_settings import BaseSettings`です。v1の`class Config`で書いた設定は、`model_config = SettingsConfigDict(...)`に移します。

### 環境変数名の大文字と小文字は区別されますか？

既定（`case_sensitive=False`）では区別しません。`PORT`でも`port`でも同じフィールドに入ります。2.15.0からは、この設定が初期化引数と設定ファイル（JSON・TOML・YAML）にも効くようになりました。区別したい場合は`case_sensitive=True`にしますが、Windowsの環境変数には効きません。

### pydantic-settingsとpython-dotenvはどう使い分けますか？

python-dotenvは`.env`の中身を環境変数へ流し込むだけで、型の変換も必須項目の確認もしません。pydantic-settingsは内部でpython-dotenvを使って`.env`を読み、その上で型を検証します。Pythonのアプリなら、`load_dotenv()`を呼ぶ代わりに`env_file`を指定すれば足り、両方を呼ぶ必要はありません。

### .envを書き換えたら実行中のアプリに反映されますか？

反映されません。設定クラスのインスタンスが持ち続けるのは、そのインスタンスを作った時点で読み込んだ値です。`settings_cached()`を使っているなら`Settings.settings_clear_cache()`を呼んだ後の呼び出しで読み直しますが、ワーカーが複数あれば各プロセスで呼ぶ必要があります。開発中は`fastapi dev`や`--reload`の自動再起動に任せ、本番では値の変更をデプロイか再起動とセットにします。

### 既定値にも型のバリデーションは掛かりますか？

はい、既定値も型のバリデーションの対象です。`BaseModel`と違い、`BaseSettings`は`validate_default=True`が既定で、`port: int = "abc"`のような誤りは起動時にエラーになります。止めたい場合は、`model_config`に`validate_default=False`を書くか、フィールドごとに`Field(validate_default=False)`を指定します。

## 関連記事

- [Pydanticとは？読み方・BaseModelの使い方とV1との違い【2.14対応】](https://www.issoh.co.jp/tech/details/4838/)：BaseSettingsの土台になるPydanticの入門です。
- [Pydantic v2のConfigDict設定一覧｜model\_configの書き方とv1からの移行【2.14対応】](https://www.issoh.co.jp/tech/details/7583/)：extraやstrictなど設定項目ごとの挙動です。
- [dotenvとは？.envの書き方と読み込み方、dotenv 18・Node.js・Python・Railsの使い方](https://www.issoh.co.jp/tech/details/3573/)：.envの記法と言語ごとの読み込み方です。
- [FastAPIのDependsの使い方｜依存性注入・yield・テストの差し替えをコードで解説](https://www.issoh.co.jp/tech/details/19720/)：設定の注入に使う依存性注入の仕組みです。
- [FastAPIのlifespanの使い方｜on\_eventからの移行とDB接続・モデル読込をコードで解説](https://www.issoh.co.jp/tech/details/19739/)：設定から作る接続プールの置き場所です。

---

出典: [pydantic-settingsの使い方：BaseSettingsで.env・環境変数・シークレットを型安全に読む](<https://www.issoh.co.jp/tech/details/19769/>)（株式会社一創）
