Store secrets in environment variables
Learn how to store secrets in environment variables to avoid hardcoding credentials in your code. This lesson covers the core concept, step-by-step implementation, best practices, and common pitfalls, with a hands-on exercise to reinforce learning.
Focus: store secrets in environment variables
Every developer has committed the sin: pushing an API key to GitHub, hardcoding a database password, or pasting a secret into a config file that ends up in a public repo. The pain is real — leaked credentials cost companies millions, get you blamed in incident reviews, and are a leading cause of data breaches. But there's a simple, battle-tested solution that starts with one habit: store secrets in environment variables instead of in your source code. In this lesson, you'll learn how to do this properly in Python, avoid the most common pitfalls, and prepare for the next step in your secure development journey.
The problem this lesson solves
Hardcoded secrets are a ticking time bomb. When you embed an API key or database password directly in your Python file, you create several serious risks:
- Accidental exposure: One
git pushand your secret is forever in the history — even if you delete it later, it's already public. - Difficult rotation: If a secret leaks, you have to change it in every file that hardcodes it.
- No separation of environments: Your local dev DB password is different from production, but hardcoded values force you into error-prone copy-paste.
- Audit failure: Security scanners (like GitGuardian or trufflehog) will flag any repo with hardcoded secrets, damaging your team's trust.
Environment variables solve these problems by letting you inject secrets at runtime, outside your codebase. The secret lives in the process's environment — not in your files. This lesson shows you how to implement this pattern in Python, step by step.
Core concept / mental model
Think of your application as a stage actor. The script (your code) should never contain the actor's personal diary (secrets). Instead, the stage manager (the operating system) hands the actor their props right before the performance. In programming terms, environment variables are the props — set by the OS or a configuration service — and your code reads them when it starts.
A useful analogy: a configuration file is like a sticky note on your desk — anyone walking by can read it. An environment variable is like a sealed envelope given to you just before you need it; only the process that has the key can open it.
In Python, you access these variables through the os.environ dictionary or the safer os.getenv() function. The os.getenv() method returns None by default if the variable is missing, which lets you fail gracefully or with a clear error.
Here's the mental model: never put secrets in code or version-controlled files; always inject them at runtime.
How it works step by step
Setting and reading environment variables is straightforward, but doing it securely requires a few deliberate steps.
1. Set the environment variable in your shell
Before running your Python script, you set the variable in your terminal. This is temporary for that shell session.
# For Linux/macOS
export DATABASE_PASSWORD='s3cr3t!'
# For Windows (Command Prompt)
set DATABASE_PASSWORD=s3cr3t!
To make it permanent, you'd add the export line to your shell profile (e.g., .bashrc), but for development, a local .env file is more practical (see hands-on).
2. Read the variable in Python
Use os.getenv() to fetch the value. This is your secure access point.
import os
db_password = os.getenv('DATABASE_PASSWORD')
if db_password is None:
raise RuntimeError('DATABASE_PASSWORD is not set!')
print('Connecting to database with password from env var')
# Do NOT print the password itself!
3. Keep .env files out of version control
For local development, you'll often use a .env file, but you must add it to .gitignore so you never commit it. The python-dotenv library loads it into the environment for you.
4. Use a secret manager in production
In production, you won't manually set environment variables. Instead, use a secrets manager like AWS Secrets Manager, Vault, or environment injection from your platform (Heroku, Docker, Kubernetes) — the principle remains the same: your code never sees a hardcoded secret.
Hands-on walkthrough
Let's put it into practice with a complete example. We'll build a tiny script that reads a database password from an environment variable and uses it to connect (simulated). We'll also use python-dotenv for local development.
Step 1: Set up a local environment file
Create a .env file in your project directory (never commit it).
# .env (local only)
DATABASE_PASSWORD=local_dev_pw_42
API_SECRET_KEY=sk_test_abc123
Step 2: Install python-dotenv
pip install python-dotenv
Step 3: Write the Python script
# app.py
import os
from dotenv import load_dotenv
# Load .env file into environment (only for local dev)
load_dotenv()
def get_secret(name: str) -> str:
"""Fetch a secret from environment, fail fast if missing."""
value = os.getenv(name)
if not value:
raise RuntimeError(f'Missing required environment variable: {name}')
return value
# In production, DATABASE_PASSWORD is injected by the platform
db_password = get_secret('DATABASE_PASSWORD')
api_key = get_secret('API_SECRET_KEY')
print('Connected to database (password loaded from env)')
print('API key loaded (but never printed!)')
# Use the secrets in API calls, not in logs
Run it:
python app.py
Expected output:
Connected to database (password loaded from env)
API key loaded (but never printed!)
Step 4: Protect yourself with .gitignore
Add .env to your .gitignore file:
.env
Verify that the file is ignored:
git status
Now you can safely commit your code without leaking secrets.
Step 5: Simulate production behavior
In a deployment, you might not have a .env file. If you run the script without the variable set, your get_secret function raises an error instead of failing silently — good for debugging.
Compare options / when to choose what
Environment variables are the baseline, but not the only solution. Here's how they compare with alternatives.
| Method | Pros | Cons | Best for |
|---|---|---|---|
| Hardcoding | Dead simple | Leaks immediately, no rotation | Never — avoid |
| Config files (e.g., .ini, YAML) | Structured | Easy to commit accidentally, not encrypted at rest | Legacy apps with no better option |
| Environment variables | Simple, ubiquitous, built-in, not in code | If process is compromised, env is visible; no built-in encryption | Most applications, especially 12-factor apps |
| Secret managers (Vault, AWS Secrets Manager) | Centralized, audited, encrypted, auto-rotation | More setup and cost; requires network access | Production systems, microservices, compliance-heavy environments |
When to choose what:
- For small projects and local dev: environment variables (with
.envfor convenience) are perfect. - For production with a single server: environment variables injected by your host (e.g., Heroku config vars) are solid.
- For complex systems with dynamic secrets, multiple services, or strict compliance: invest in a secret manager — but your code still reads them via environment variables or API calls, so the habit you learn here still applies.
Pro tip: Even with secret managers, the API key you get is often injected as an environment variable into your process — so this lesson is foundational.
Troubleshooting & edge cases
You'll hit some common issues. Here's how to fix them.
Issue 1: Environment variable is None or empty
If os.getenv() returns None, either the variable is not set or your .env file isn't loaded. Check:
- Did you call
load_dotenv()before reading? Order matters. - Use a debug print (temporarily) to see what
os.environcontains.
Issue 2: .env file is not loaded in production
Don't call load_dotenv() in production if your environment already has the variables. It will silently fail to find .env and your script still works, but it's wasted. Better to conditionally load based on ENVIRONMENT variable.
Issue 3: Special characters in secrets
If your password contains $, !, or spaces, shell quoting trips you up. Always use single quotes in export 'VAR=value' or better, use the .env file which handles special chars well with python-dotenv.
Issue 4: Committing .env by accident
If you already committed .env to git, remove it from tracking:
git rm --cached .env
Then add it to .gitignore. Note: the secret is already in history — rotate it immediately.
Issue 5: Env var set but not visible in Python
If you set the variable in one terminal, it won't appear in another terminal. Each shell session has its own environment. Set it in the same shell where you run python app.py.
Edge case: Variable name conventions
Use uppercase with underscores (DATABASE_PASSWORD) — it's a community standard and prevents confusion with code variables.
What you learned & what's next
You now understand the core concept of storing secrets in environment variables: the problem it solves (leaked secrets), the mental model (runtime injection), and the practical steps to implement it in Python. You can read secrets with os.getenv(), guard against missing values, and protect .env files with .gitignore. You also know when to level up to a secret manager.
Your learning objectives met:
- You can explain why environment variables are a security best practice.
- You completed a hands-on exercise that reads secrets from the environment and fails safely if missing.
Next in the Secure development track, you'll learn how to handle secrets in CI/CD pipelines — ensuring that your build and test processes also avoid hardcoding credentials, and how to integrate secret scanning into your workflow to catch leaks before they happen.
Practice recap
Take the script from the hands-on walkthrough and modify it to connect to a real database (or a mock). Create a .env file with a fake password, run the script, then try running it without the variable set. Observe how the error message helps you debug. As a bonus, set the environment variable manually in your shell and confirm the script works without the .env file — this simulates a production deployment.
Common mistakes
- Committing the .env file to GitHub — always add it to .gitignore and verify with
git statusbefore pushing. - Printing the secret in logs or error messages — never output the actual value of a secret variable.
- Using
os.environindexing (os.environ['KEY']) which raises KeyError — preferos.getenv()for safer handling. - Setting the environment variable in one shell and expecting it in another — each terminal session has its own environment.
- Hardcoding secrets as defaults in
os.getenv('KEY', 'default_secret')— that defeats the purpose; fail fast instead.
Variations
- Use
python-dotenvwith a.envfile for local development, but never commit it. - For production, use a secrets manager like AWS Secrets Manager or HashiCorp Vault, and inject the secret as an environment variable when the app starts.
- On Windows, use
set VAR=valuein Command Prompt or$env:VAR='value'in PowerShell instead ofexport.
Real-world use cases
- A Django web app reads its SECRET_KEY and database credentials from environment variables set in a CI/CD pipeline, ensuring zero secrets in version control.
- A Python script running as a cron job pulls its SMTP password from an environment variable injected by systemd, avoiding a config file in the repo.
- A microservice in Kubernetes reads its API key from a Secret object mounted as an environment variable, enabling rotation without redeploying code.
Key takeaways
- The problem this lesson solves: hardcoded secrets lead to leaks, rotation nightmares, and security audit failures.
- The mental model: secrets are injected at runtime via the process environment, never hardcoded in source.
- Use
os.getenv()to read secrets in Python, and always fail fast if a required variable is missing. - Keep
.envfiles out of version control with.gitignore— this protects your local dev credentials. - For production, choose a secret manager for complex systems, but the principle of environment injection remains identical.
- Troubleshoot common pitfalls: .env not loaded, special characters in secrets, and shell session environment isolation.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.