How should you manage secrets and configuration in a Python application?

6 minintermediatesecuritysecretsconfiguration

Quick Answer

Never hardcode secrets (API keys, database passwords, tokens) in source code or commit them to version control — load them from **environment variables** (via `os.environ`, `python-dotenv` for local development) or a dedicated **secrets manager** (AWS Secrets Manager, HashiCorp Vault, environment injection from your deployment platform) at runtime, and keep non-secret configuration separate (a `.env`/config file that's safe to commit, or environment-specific settings modules) from actual credentials.

Detailed Answer

The mistake: hardcoded secrets in source

# DON'T -- committed to git, visible in history forever, even if later "removed"
DATABASE_PASSWORD = "hunter2"
API_KEY = "sk-live-abc123..."

Once a secret is committed to version control, it's in the repository's history permanently (removing it from the latest commit doesn't remove it from history) — anyone with read access to the repo, now or in the future, can find it. This is one of the most common real-world causes of security incidents.

Loading from environment variables

import os

DATABASE_PASSWORD = os.environ["DATABASE_PASSWORD"]   # raises KeyError if missing -- fail loudly
API_KEY = os.environ.get("API_KEY")                     # or provide a fallback if optional

Environment variables keep secrets out of the codebase entirely — they're injected at deploy/runtime by the hosting platform, CI secrets store, or orchestration system (Kubernetes secrets, systemd environment files), and never touch the repository.

Local development: .env files (never committed)

# .env  (in .gitignore -- never committed!)
DATABASE_PASSWORD=local-dev-password
API_KEY=sk-test-...
from dotenv import load_dotenv
load_dotenv()   # reads .env into os.environ, for local dev convenience

import os
password = os.environ["DATABASE_PASSWORD"]

python-dotenv loads a local .env file into the process environment, giving the same os.environ access pattern locally as in production — critically, .env must be listed in .gitignore, and a .env.example (with placeholder, non-real values) is committed instead to document what variables are needed.

Dedicated secrets managers for production

import boto3

client = boto3.client("secretsmanager")
secret = client.get_secret_value(SecretId="prod/db-password")["SecretString"]

For production systems, a dedicated secrets manager (AWS Secrets Manager, HashiCorp Vault, Google Secret Manager) adds capabilities plain environment variables don't offer: access auditing (who fetched which secret, when), automatic rotation, and fine-grained access control per service — worth the added complexity for anything beyond small applications.

Separating secrets from non-secret configuration

# settings.py -- safe to commit; no actual secrets here
import os

DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
DATABASE_HOST = os.environ.get("DATABASE_HOST", "localhost")
DATABASE_PASSWORD = os.environ["DATABASE_PASSWORD"]   # the actual secret, injected at runtime

Non-sensitive configuration (feature flags, hostnames, timeouts) can reasonably live in a committed settings file with sensible defaults; only the genuinely sensitive values need to come exclusively from the environment/secrets manager with no committed default at all.

A useful checklist

  • Add .env, *.pem, credentials.json, etc. to .gitignore from day one.
  • Use pre-commit secret-scanning hooks (detect-secrets, gitleaks) to catch accidental commits before they happen.
  • Rotate any secret that was ever accidentally committed — removing it from the latest commit is not sufficient; treat it as compromised.

Interview-ready summary: Secrets belong in environment variables or a dedicated secrets manager, injected at runtime — never hardcoded in source or committed to version control, since git history is effectively permanent. Use .env files (gitignored) for local development convenience, and treat any secret that was ever committed as compromised and due for rotation.