Load Pydantic Settings from Docker Secrets

Docker and Swarm mount secrets as files under /run/secrets, not as environment variables — which is deliberately safer, because files on a tmpfs are not exposed by docker inspect the way env vars are. pydantic-settings reads this layout natively through secrets_dir, so you can wire mounted secrets into a validated model with one line. This extends the pydantic-settings fundamentals and the secrets management patterns.

Problem 1: passing secrets as environment variables

# ANTI-PATTERN — secret in an env var, visible to docker inspect
import os
db_password = os.environ["DB_PASSWORD"]

Anyone with docker inspect access to the container reads the value, and it is trivially leaked into a log line.

Problem 1: passing secrets as environment variables The core point of Problem 1: passing secrets as environment variables passing secrets as environment va…import os db_password = os.environ["DB_PASSWORD"] Anyone with docker inspect access…
The key point of this section at a glance.

Problem 2: reading the secret file by hand

# ANTI-PATTERN — manual file read, no validation, no masking
with open("/run/secrets/db_password") as fh:
    db_password = fh.read()   # trailing newline, plain str, unvalidated

Hand-reading each file misses validation, leaves a trailing newline, and stores the credential as a plain str that can leak.

Key APIs in Problem 2: reading the secret file by hand The main identifiers used in Problem 2: reading the secret file by hand openread
The APIs and identifiers this section uses.

Secure implementation

Point secrets_dir at the mount and type the field as SecretStr; pydantic-settings reads and masks it.

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


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        secrets_dir="/run/secrets",   # Docker/Swarm mounts secrets here
        extra="forbid",
    )
    db_password: SecretStr            # read from /run/secrets/db_password, masked
    api_key: SecretStr                # read from /run/secrets/api_key


settings = Settings()
# compose.yaml
services:
  app:
    build: .
    secrets:
      - db_password
      - api_key
secrets:
  db_password:
    file: ./secrets/db_password.txt   # or external: true for Swarm-managed
  api_key:
    file: ./secrets/api_key.txt
Key APIs in Secure implementation The main identifiers used in Secure implementation secrets_dirSecretStrBaseSettingsSettingsConfigDictSettingsDocker
The APIs and identifiers this section uses.

Gotchas & version-specific behaviour

  • pydantic-settings strips the trailing newline that Docker secret files often carry — a manual open().read() would not.
  • Field names must match the secret file names (respecting case_sensitive); db_password reads /run/secrets/db_password.
  • secrets_dir is a lower-priority source than environment variables, so an explicit env var of the same name still wins.
  • The secrets mount is a tmpfs — it never lands in the image and is gone when the container stops.
  • Combine with a manager: in Swarm, external: true secrets can be rotated without rebuilding the image.
Gotchas & version-specific behaviour Key points from Gotchas & version-specific behaviour pydantic-settings stripsthe …a manual open().read() would not.Field names must match these…secrets_dir is alower-priori…The secrets mount is atmpfsit never lands in the image and isgone when the co…Combine with a managerin Swarm, external: true secretscan be rotated wit…
The essentials of Gotchas & version-specific behaviour.

Production parity checklist

  • Mount credentials as Docker/Swarm secrets, not environment variables.
  • Type every secret field as SecretStr and set extra="forbid".
  • Keep the local ./secrets/*.txt files gitignored; ship a .example alongside.
  • Validate the model at startup so a missing secret fails the container immediately.
  • For managed rotation, use Swarm external secrets or a secret manager.
Production parity checklist Key points from Production parity checklist Mount credentials asDocker/S…Type every secret field asSe…Keep the local./secrets/*.tx…Validate the model atstartup…For managed rotation, useSwa…
Every item to tick off for Production parity checklist.

Frequently asked questions

How does pydantic-settings read Docker secrets?

Set secrets_dir in SettingsConfigDict pointing at /run/secrets. pydantic-settings reads each field from a same-named file in that directory, so a secret mounted at /run/secrets/db_password populates the db_password field.

Why are Docker secrets safer than environment variables for credentials?

Environment variables are visible via docker inspect and easy to leak into logs. Docker secrets are mounted as in-memory tmpfs files readable only by the container, and never appear in the image or docker inspect output.

What precedence do file secrets have in pydantic-settings?

By default init arguments win, then environment variables, then the .env file, then the secrets_dir files. So a file secret is a lower-priority source than an explicit environment variable of the same name.

Key takeaways

Mount credentials as Docker secrets and read them with secrets_dir into SecretStr fields; the value stays on a tmpfs, out of docker inspect, and masked in every log line.