Chapter 24: Secrets & Configuration
Learning Objectives
- Understand what environment variables are and why they exist.
- Learn how to manage secrets safely using
.envfiles. - Implement configuration management using
os.environandpython-dotenv. - Adopt the 12-factor app principle for configuration.
Prerequisites
File I/O (Ch. 11), Git (Ch. 14), Deployment (Ch. 20)
Why Does This Exist?
Developers often need to connect to databases, third-party APIs, and cloud services. These require credentials (passwords, API keys, tokens).
Hardcoding these credentials in source code is a terrible idea. When code is pushed to GitHub or shared, these secrets are leaked, leading to real data breaches and massive financial losses.
Furthermore, credentials change between environments (development, staging, production). You don't want to rewrite code just to point to a different database.
Environment variables solve this by decoupling configuration from code.
History
In 2011, Heroku published the 12-Factor App methodology, standardizing best practices for modern web apps. Principle III states: "Store config in the environment."
Despite this, secret leaks remain one of the top security vulnerabilities. GitGuardian reports millions of secrets are leaked on GitHub every year because developers accidentally commit .env files or hardcode API keys.
Mental Model
Think of environment variables as sticky notes placed on the outside of a house.
The house (your code) has no idea what city it's built in. The tenant (your environment) writes "City = New York" on a sticky note and slaps it on the house.
When the house runs, it reads the sticky note to configure itself.
Internal Working
Every process running on an Operating System (OS) has an "environment" - a dictionary of key-value strings.
When a process spawns a child process, the child inherits a copy of this environment.
A .env file is just a text file. Tools like python-dotenv parse this file, read the key-value pairs, and inject them into the running process's environment dynamically before the app starts.
Syntax
Using python-dotenv and os module in Python:
1import os
2from dotenv import load_dotenv
3
4# Load variables from .env file
5load_dotenv()
6
7# Safely get an environment variable (returns None if not found)
8api_key = os.getenv("API_KEY")
9
10# Accessing directly (raises KeyError if not found)
11db_url = os.environ["DATABASE_URL"]Example .env file format:
1API_KEY=sk_test_123456789
2DATABASE_URL=postgres://user:pass@localhost:5432/db
3DEBUG_MODE=TrueVisual Explanation
Tiny Example
1import os
2
3db_url = os.getenv("DATABASE_URL", "sqlite:///local.db")
4print(f"Connecting to: {db_url}")Walkthrough
- We import the
osmodule to interact with the operating system. os.getenvlooks for "DATABASE_URL".- If it's not set in the environment, it gracefully falls back to the second argument, "sqlite:///local.db", which is safe for local development.
Common Mistakes
Committing .env to Git
This is the cardinal sin of secrets management. If you commit .env, your secrets are now in your commit history forever.
The Fix: ALWAYS add .env to your .gitignore file immediately upon creating the project.
- Using env vars for non-secret config: Complex logic configurations (like a list of supported languages) belong in a
config.pyor.jsonfile, not an env var. - Never rotating secrets: Passwords and API keys should be rotated periodically in case they were silently compromised.
Debugging
If your app says "Invalid API Key", check if the env var is actually loaded.
Print the variable (temporarily, in development only) or print its length to verify it's not None or an empty string.
Mini Project
Time: 20 min
Create a .env file with APP_NAME, API_KEY, and DEBUG_MODE variables. Write a Python script that loads them with python-dotenv and prints a config summary. Make sure .env is in .gitignore.
💡 See One Approach
import os
from dotenv import load_dotenv
load_dotenv()
app_name = os.getenv("APP_NAME", "Default App")
api_key = os.getenv("API_KEY")
debug = os.getenv("DEBUG_MODE", "False") == "True"
print(f"--- Config Summary ---")
print(f"App: {app_name}")
print(f"Debug Active: {debug}")
print(f"API Key Set: {api_key is not None}")Bigger Project
Time: 1 hr
Build a multi-environment config system. Create .env.development and .env.production files with different values. Write a config.py module that loads the correct file based on an ENVIRONMENT env var. Write a script that uses this config to show different behavior in each environment.
💡 See One Approach
import os
from dotenv import load_dotenv
env = os.getenv("ENVIRONMENT", "development")
if env == "production":
load_dotenv(".env.production")
else:
load_dotenv(".env.development")
print(f"Running in {env} mode.")
print(f"DB URL: {os.getenv('DATABASE_URL')}")Production Usage
In real-world production systems (like AWS or Kubernetes), you don't use .env files. Instead, you use secure services:
- AWS Secrets Manager / HashiCorp Vault: Centralized, encrypted vaults that securely deliver secrets to your app at runtime.
- Vercel / Railway: They provide UI dashboards where you paste your environment variables, and they inject them automatically during deployment.
Best Practices
- Never commit secrets. Add
.envto.gitignoreimmediately. - Keep a
.env.examplefile committed as a template with dummy values so other developers know what variables they need to set. - Use different keys per environment (never use your prod database password in development).
Interview Questions
🟢 Easy: What is an environment variable?
🔍 Reveal Answer
🟡 Medium: Why should API keys not be stored in source code?
🔍 Reveal Answer
🔴 Hard: What is the difference between a secret and a configuration value, and how would you manage each in a microservices architecture?
🔍 Reveal Answer
Revision Sheet
- Secrets vs Config: Secrets = passwords, keys. Config = settings.
- The Rule: Code should be identical across environments; only the environment variables change.
- .env files: Local dev tools to simulate environment variables. NEVER COMMIT THEM.
Connections
- This ties directly into Deployment (Ch. 20) where you must provide these variables to your cloud host.