Python Dotenv Configuration — Core Concepts
Why this topic matters
Every application needs configuration that changes between environments: database URLs, API keys, feature flags, debug settings. Hard-coding these values creates security risks and deployment headaches. The Twelve-Factor App methodology recommends storing config in environment variables, and python-dotenv bridges the gap between that ideal and developer convenience.
How it works
Python-dotenv reads key-value pairs from a .env file and loads them into os.environ. The file format is straightforward:
DATABASE_URL=postgresql://user:pass@localhost/mydb
DEBUG=true
API_KEY=sk-abc123
SECRET_KEY=super-secret-value
In your Python code, a single call at startup makes these values available:
from dotenv import load_dotenv
import os
load_dotenv()
db_url = os.getenv("DATABASE_URL")
debug = os.getenv("DEBUG", "false").lower() == "true"
The library searches for .env in the current directory or parent directories, similar to how Git finds its .git folder.
Key concepts
Override behavior
By default, python-dotenv does not overwrite environment variables that already exist. This means system-level or CI-set variables take precedence over .env file values. You can change this with load_dotenv(override=True), but the default behavior is intentional — it lets deployment platforms inject the real values while developers use local defaults.
The .env.example pattern
Teams typically commit a .env.example file that lists every expected variable with placeholder values:
DATABASE_URL=postgresql://user:password@localhost/dbname
DEBUG=false
API_KEY=your-key-here
The actual .env file goes into .gitignore. New developers copy the example, fill in their values, and start working. This pattern documents required configuration without exposing secrets.
Variable interpolation
Python-dotenv supports referencing other variables within the file:
BASE_DIR=/opt/app
LOG_DIR=${BASE_DIR}/logs
DATA_DIR=${BASE_DIR}/data
This reduces repetition and keeps related paths consistent.
Multiple environments
Some teams maintain separate files: .env.development, .env.testing, .env.production. The application loads the right one based on context:
env = os.getenv("APP_ENV", "development")
load_dotenv(f".env.{env}")
This approach works for small projects but can become unwieldy at scale — that’s where tools like Hydra or Dynaconf step in.
Common misconception
“.env files are secure.” They’re more secure than hard-coding secrets in source code, but a .env file on disk is still plain text. Anyone with file system access can read it. For production systems, use a proper secrets manager (AWS Secrets Manager, HashiCorp Vault) and inject values as real environment variables. The .env file is primarily a development convenience.
One thing to remember
Python-dotenv is the simplest reliable bridge between “secrets shouldn’t be in code” and “I need to run this locally.” Use it for development, but graduate to proper secrets management for production.
See Also
- Python Black Formatter Understand Black Formatter through a practical analogy so your Python decisions become faster and clearer.
- Python Bumpversion Release Change your software's version number in every file at once with a single command — no more find-and-replace mistakes.
- Python Changelog Automation Let your git commits write the changelog so you never forget what changed in a release.
- Python Ci Cd Python Understand CI CD Python through a practical analogy so your Python decisions become faster and clearer.
- Python Cicd Pipelines Use Python CI/CD pipelines to remove setup chaos so Python projects stay predictable for every teammate.