pyguides

Working with Environment Variables in Python

Environment variables are key-value pairs that live outside your Python code. They let you configure your application without changing source code. This is essential for different environments—development, testing, production—where you need different settings.

Why Environment Variables Matter

When you deploy an application, you rarely want hardcoded values. Database passwords, API keys, and feature flags should live outside your code. Environment variables solve this problem elegantly by storing configuration in the operating system’s process environment, where any language or tool can read them without parsing a config file format.

They are:

  • Secure — Secrets stay out of your source code
  • Flexible — Change behavior without redeploying code
  • Portable — Work across operating systems
  • Standard — Used by virtually every programming language and deployment tool

Reading Environment Variables

Python provides the os module for working with environment variables:

import os

# Read an environment variable
db_host = os.environ.get("DB_HOST")
print(db_host)  # None if not set

# Provide a default value
debug_mode = os.environ.get("DEBUG", "false")
print(debug_mode)  # "false" if DEBUG is not set

The get() method returns None if the variable does not exist. Providing a default avoids crashes and lets your code handle missing variables gracefully.

Setting Environment Variables

You can also set environment variables from within Python:

import os

os.environ["MY_APP_VERSION"] = "1.2.3"
print(os.environ.get("MY_APP_VERSION"))  # 1.2.3

These changes affect the current process and any child processes it spawns. They do not modify the system’s permanent environment.

Working with os.environ

The os.environ object behaves like a dictionary:

import os

# Check if a variable exists
if "API_KEY" in os.environ:
    api_key = os.environ["API_KEY"]

# List all environment variables
for key, value in os.environ.items():
    print(f"{key}: {value}")

# Delete a variable
del os.environ["TEMP_VAR"]
# Or use:
os.environ.pop("TEMP_VAR", None)

Be careful when accessing keys directly with os.environ[key]—it raises a KeyError if the variable does not exist. Use get() for safer access.

The os Module vs os.environ

Both os.environ and os.environ.get() work, but there is a subtle difference. os.environ is a dict-like object, while os.environ.get() is a method on that object:

import os

# These are equivalent:
value1 = os.environ.get("MY_VAR")
value2 = os.environ.get("MY_VAR")

# Both return None when not found

Use whichever feels more natural in your code. In practice, os.environ.get("KEY", default) is the recommended pattern because it never raises a KeyError and lets you provide a fallback value in a single readable expression.

Real-World Examples

Database Configuration

import os

DB_HOST = os.environ.get("DB_HOST", "localhost")
DB_PORT = os.environ.get("DB_PORT", "5432")
DB_NAME = os.environ.get("DB_NAME", "mydb")
DB_USER = os.environ.get("DB_USER", "admin")
DB_PASSWORD = os.environ.get("DB_PASSWORD", "")

# Connect using these values
connection_string = f"********************************************************/{DB_NAME}"

Reading database credentials from environment variables keeps sensitive information out of your source code and lets you connect to different database instances in development, staging, and production without changing a single line of application code. Always provide reasonable defaults for local development while requiring explicit values in production.

Feature Flags

import os

def is_feature_enabled(feature_name):
    """Check if a feature flag is enabled."""
    return os.environ.get(f"FEATURE_{feature_name.upper()}", "false").lower() == "true"

if is_feature_enabled("new_dashboard"):
    show_new_dashboard()
else:
    show_old_dashboard()

Feature flags powered by environment variables let you toggle functionality without redeploying code. The naming convention of prefixing flags with FEATURE_ keeps them organised and makes it obvious which variables control application behaviour versus which hold configuration data.

Application Mode

import os

APP_ENV = os.environ.get("APP_ENV", "development")
DEBUG = APP_ENV == "development"

if DEBUG:
    print("Running in development mode")
else:
    print("Running in production mode")

Switching behaviour based on APP_ENV is a common pattern across web frameworks like Django and Flask. By convention, values like development, staging, and production let you adjust logging verbosity, enable debug toolbars, or connect to different services without hardcoding environment-specific logic throughout your application.

Best Practices

Keep these tips in mind when working with environment variables:

Use descriptive names. Prefix your variables with your app name to avoid collisions: MYAPP_DATABASE_URL instead of DATABASE_URL.

Provide sensible defaults. Your application should work with minimal configuration, even if some features are disabled.

Validate early. Check for required variables at startup and fail fast with clear error messages:

import os

required_vars = ["DATABASE_URL", "API_KEY"]
missing = [v for v in required_vars if not os.environ.get(v)]

if missing:
    raise RuntimeError(f"Missing required environment variables: {missing}")

Validating required variables at startup catches misconfiguration immediately rather than letting the application fail later with a cryptic error deep in the execution path. This pattern is especially important in containerised deployments where environment variables are the primary configuration mechanism and a missing variable can mean the difference between a working service and a silent failure.

Use a .env file for local development. The python-dotenv package loads variables from a .env file:

# pip install python-dotenv
from dotenv import load_dotenv
load_dotenv()  # Loads .env file in current directory

This keeps local development simple without polluting your shell environment.

See Also