Managing Environment Variables in Python
Learn how to manage environment variables in Python using `os.environ`, `python-dotenv`, and validation libraries like `pydantic-settings`. Keep secrets out of your code and handle config safely across development and production.
Managing Environment Variables in Python: What You Need to Know
If you've ever written a Python app that needs to connect to a database or an API, you've probably run into the question of where to store your secret keys, passwords, or configuration settings. Hardcoding these values in your code might work for a small test, but it quickly becomes a security nightmare. That's where environment variables step in.
Python handles environment variables with a few straightforward tools that are easy to understand and use. Let's break down how it works, and what you need to keep in mind.
The Basics: os.environ
Python's os module gives you access to environment variables through a dictionary-like object called os.environ. Here's the simplest way to get a value:
import os
database_url = os.environ.get('DATABASE_URL')
If the variable doesn't exist, .get() returns None by default, which is safer than throwing an error. You can also provide a fallback:
port = os.environ.get('PORT', '5000')
This approach is great for scripts and small projects. But for serious work, you'll want a more structured solution.
Why Not Just Use os.environ Everywhere?
The os.environ dictionary works, but it has a few quirks. All the values are strings, so you have to convert them manually. Boolean values like 'True' or 'false' are just strings, not actual booleans. Environment variables can also be missing or misspelled, and your code might not catch the error until runtime.
This is why most professional Python developers reach for more robust tools.
The Modern Way: python-dotenv
The python-dotenv library is a game changer for managing configuration in development. It lets you keep your environment variables in a .env file that sits in your project root, but never gets committed to version control.
First, install it:
pip install python-dotenv
Then create a .env file:
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
API_KEY=sk-1234abcd5678
DEBUG=true
In your code, you load these variables with:
from dotenv import load_dotenv
import os
load_dotenv()
database_url = os.getenv('DATABASE_URL')
api_key = os.getenv('API_KEY')
The beauty of this is that load_dotenv() only runs in development. In production, you set the environment variables directly on your server, and the os.getenv() calls still work without any code changes. No magic, just practical separation of config from code.
A Real‑World Example at PythonSkillset
Here at PythonSkillset, we run a small dashboard that tracks API usage for our readers. When we deployed it, we needed to keep our API keys and database credentials separate from the code. The .env file made local development simple, and when we moved to a cloud server, we just set those same variables in the server's environment settings. The Python code never changed, and no secrets leaked into our repository.
# config.py
import os
from dotenv import load_dotenv
load_dotenv()
class Settings:
DATABASE_URL = os.getenv('DATABASE_URL')
API_KEY = os.getenv('API_KEY')
DEBUG = os.getenv('DEBUG', 'false').lower() == 'true'
That single config.py module became the central place where all environment variables were loaded. Any part of the app could import Settings.DATABASE_URL and know the value was correct.
Validating Your Environment Variables
One problem remains: if a required environment variable is missing, your app might fail halfway through. To catch this early, use a validation library like pydantic-settings or environs.
With pydantic-settings, you define a settings class that automatically parses and validates environment variables:
pip install pydantic-settings
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
api_key: str
debug: bool = False
class Config:
env_file = '.env'
settings = Settings()
If database_url is missing, this raises a clear error the moment your app starts. No more wondering why your database connection silently fails half an hour later.
Common Pitfalls to Avoid
- Don't hardcode default values for secrets. A default like
'password123'in production is dangerous. Only provide defaults for non‑sensitive settings like port numbers or log levels. - Don't commit your
.envfile. Add it to.gitignoreimmediately. Use a.env.examplefile instead, with dummy values. - Watch out for trailing spaces. An environment variable like
SECRET_KEY= mykey(with a space after the=) will include that space. It's a subtle bug that can drive you crazy. - Remember that environment variables are case‑sensitive on Linux.
Database_Urlanddatabase_urlare different.
When to Use What
| Situation | Tool |
|---|---|
| Quick script or one‑off job | os.environ |
| Flask/Django/FastAPI project | python-dotenv + os.getenv |
| Large application with many settings | pydantic-settings or environs |
| Docker containers | Set env vars in docker-compose.yml or Dockerfile |
| Production server | System env vars (set by your deployment tool) |
Final Thoughts
Managing environment variables in Python is not complicated once you understand the basics. Start with os.environ for small tasks, add python-dotenv for clean development setups, and move to validation libraries when your application grows. The key takeaway is simple: keep your configuration outside your code. Your future self—and anyone else working on your project—will thank you.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.