Put secrets outside your code. FastAPI's docs recommend environment variables read through a pydantic Settings class, with an optional .env file for development. In containers, pydantic settings can also read each secret from a file in a secrets directory such as /run/secrets. Never commit .env files or bake them into images.
What the FastAPI docs say
The FastAPI settings page opens by noting that applications often need external configuration such as secret keys, database credentials and email service credentials, many of which are sensitive. For that reason it is common to provide them in environment variables read by the application. An environment variable lives outside the Python code, in the operating system.
It then shows a dotenv file, commonly named .env, which pydantic can read through an external library, and tells you to configure env_file inside the Settings class. It also shows creating the settings once with lru_cache, so the file is not re-read on every request.
Three sources compared
| Source | Good for | Watch out for |
|---|---|---|
| Environment variables | Production, CI, containers | Visible to processes and sometimes in crash dumps or debug pages |
| .env file | Local development | Committed to git, copied into images, shared in chat |
| Secrets directory | Docker or orchestrator mounted secrets | File permissions, missing mounts |
Pydantic Settings documents the third option. In its Docker secrets section, you define the Settings class with a secrets directory such as /run/secrets, and each file in it provides a field value. The docs say a missing secrets directory is ignored with a warning, which is worth knowing: a misconfigured mount may not fail loudly. Check with a startup test that required secrets are present.
Typing secrets
Pydantic offers SecretStr, which its own secrets examples use for fields like keys and passwords. Declaring those fields with that type signals intent and helps avoid printing the value by accident. Read the real value only at the point of use.
from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", secrets_dir="/run/secrets")
database_password: SecretStr
Priority and surprises
Pydantic Settings supports multiple sources and lets you change their priority with settings_customise_sources, where the first item has the highest priority. That is useful, and also a source of surprises: a stray environment variable on a developer machine can override a file value. Know the order your app uses.
Keep secrets out of the wrong places
- Add .env to .gitignore and to .dockerignore, so it never enters the repository or the image build context.
- Commit an example file with placeholder values.
- Scan the repository history for committed secrets. Removing a file later does not remove it from history, so rotate anything that was committed.
- Do not log settings objects. Avoid printing the whole configuration at startup.
- Use your platform's secret manager and inject values at deploy time. Pydantic Settings documents sources for several cloud secret managers.
- Use different secrets per environment, and rotate on a schedule.
Test the failure mode
Write a startup check that fails fast when a required secret is missing or still equals a placeholder. A service that boots with an empty signing key is worse than one that refuses to boot.
Try it yourself
Click through it at your own pace, or open it full screen. Look at how hardcoded credentials in a codebase are reported.
Quick check
Is a .env file safe in production?
It is a development convenience. In production prefer your platform's secret injection and keep the file out of images and repositories.
What if the secrets directory is missing?
Pydantic Settings ignores a missing directory and shows a warning, so add a startup check that required values exist.
Safeguard's SAST and dependency scanning can flag committed secrets and vulnerable packages. Run a free scan.
Sources
Last reviewed: 19 October 2026.