Reading environment variables in Python is a basic skill that becomes critical as soon as your project uses API keys, database passwords, access tokens, deployment settings, or different configurations for development and production. Hardcoding secrets directly inside Python files is risky because those files often end up in Git repositories, shared folders, backups, screenshots, logs, or deployment artifacts. Environment variables let you keep sensitive configuration outside the codebase while still making it available to the application at runtime.
This English version is adapted for developers who want a practical, production-minded workflow. You will learn what environment variables are, how to read them with Python’s built-in os module, when to use os.environ versus os.getenv(), how to load .env files with python-dotenv, how to validate values, how to avoid leaking secrets, and how to prepare your app for deployment. If you are still building your Python foundation, start with this Python beginner guide and this overview of the Python os module.
What Are Environment Variables?
Environment variables are key-value pairs provided by the operating system to running processes. A Python script can read these values while it runs. Instead of writing a database URL directly into settings.py or app.py, you store it as something like DATABASE_URL outside the source code. Your application reads that value when it starts.
This separation matters because the same code can run in different environments without edits. On your laptop, DATABASE_URL may point to a local SQLite or PostgreSQL database. In production, the same variable can point to a managed cloud database. The code stays the same. Only the environment changes. This is one of the core ideas in the Twelve-Factor App configuration principle, a widely used reference for app configuration.
Why You Should Not Hardcode Secrets
Hardcoded secrets are one of the easiest security mistakes to make. A developer may write an API key into a Python file “just for testing” and later commit it to Git. Once a secret is pushed to a public repository, it should be considered compromised. Attackers constantly scan public repositories for keys, tokens, and credentials. Even private repositories are not a perfect place for secrets because many people, tools, and integrations may have access.
Environment variables reduce that risk by keeping secrets out of the code. They also make deployment cleaner. A Flask API, FastAPI backend, automation script, or data pipeline can read credentials from the runtime environment instead of requiring a separate version of the code for each server. If your project calls third-party APIs, this is especially important. For a practical API context, read this guide on how to secure a Flask API with JWT.
Reading Variables with os.environ
Python’s built-in os module exposes environment variables through os.environ. It behaves like a mapping of names to values. You can access a variable by key, but if the key does not exist, Python raises a KeyError. That behavior is useful when a variable is truly required and your application should stop immediately if it is missing.
import os
api_key = os.environ["API_KEY"]
print("API key loaded")This code is strict. If API_KEY is not defined, the program fails immediately. That may be exactly what you want for required settings such as a production secret key, payment token, or database URL. A fast failure is better than an application starting with missing configuration and failing later in an unpredictable place.
Reading Variables Safely with os.getenv
For optional settings, os.getenv() is usually more convenient. It returns the environment variable if it exists and returns None or a default value if it does not. This avoids a KeyError and lets you define sensible defaults for local development.
import os
debug = os.getenv("DEBUG", "false")
port = os.getenv("PORT", "8000")
print(debug)
print(port)The second argument is the fallback value. In this example, DEBUG becomes "false" if it is missing, and PORT becomes "8000". Notice that both values are strings. Environment variables always arrive as text, even when they represent numbers, booleans, URLs, or JSON. Understanding strings in Python helps avoid many mistakes in this area.
Converting Strings to the Right Type
Because environment variables are strings, you must convert them when your application expects another type. A port number should become an integer. A feature flag should become a boolean. A timeout may become a float. If conversion fails, handle the error clearly instead of letting a confusing bug appear later.
import os
port = int(os.getenv("PORT", "8000"))
timeout = float(os.getenv("REQUEST_TIMEOUT", "10.0"))
debug = os.getenv("DEBUG", "false").lower() in {"1", "true", "yes", "on"}
print(port, timeout, debug)Be strict with important values. If an environment variable controls billing, authentication, database access, or security behavior, validate it explicitly. A typo such as TRU instead of true should not silently become False if that changes how the application behaves. For error handling patterns, review this guide to try and except in Python.
Using .env Files During Development
Setting environment variables manually in the terminal works, but it becomes annoying when a project has many settings. During local development, a common solution is a .env file. This file stores environment-style key-value pairs in your project folder. Your Python app can load it at startup so you do not need to export variables manually every time.
DEBUG=true
PORT=8000
DATABASE_URL=postgresql://user:password@localhost:5432/app
SECRET_KEY=replace-this-locallyThe .env file should be treated as private. Add it to .gitignore so it does not enter version control. If your team needs an example file, create .env.example with fake values and comments. The real .env file stays local. This habit is especially important when working with Git and GitHub in Python projects.
Loading .env Files with python-dotenv
The Python standard library does not automatically read .env files. For that, use python-dotenv, a small library that loads variables from a .env file into the process environment. Install it with pip inside a virtual environment. If you need a setup refresher, follow this guide on creating a Python virtual environment with venv.
pip install python-dotenvThen load the file at the beginning of your application:
import os
from dotenv import load_dotenv
load_dotenv()
database_url = os.getenv("DATABASE_URL")
secret_key = os.getenv("SECRET_KEY")This keeps local development simple while preserving the production pattern. Your code still reads from environment variables. The only difference is that, during local development, python-dotenv fills those variables from the local file.
Required vs Optional Configuration
Not every configuration value has the same importance. Some settings are required. The application should not start without them. Others are optional and can safely use defaults. Separating required and optional values makes your configuration easier to reason about.
import os
def require_env(name: str) -> str:
value = os.getenv(name)
if not value:
raise RuntimeError(f"Missing required environment variable: {name}")
return value
DATABASE_URL = require_env("DATABASE_URL")
SECRET_KEY = require_env("SECRET_KEY")
PORT = int(os.getenv("PORT", "8000"))This pattern makes failures explicit. If a required value is missing, the error message tells you exactly which variable needs to be configured. Type hints also make the helper function clearer. If you want to go deeper, this guide to Python type hints explains how annotations improve readability and tooling.
A Practical Configuration Module
For small scripts, reading variables directly in the main file is fine. For larger projects, create a configuration module. A dedicated module centralizes environment parsing, validation, default values, and error messages. That makes the rest of the project cleaner because other files import settings instead of reading environment variables everywhere.
# config.py
import os
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()
def require_env(name: str) -> str:
value = os.getenv(name)
if not value:
raise RuntimeError(f"Missing required environment variable: {name}")
return value
@dataclass(frozen=True)
class Settings:
database_url: str
secret_key: str
debug: bool
port: int
settings = Settings(
database_url=require_env("DATABASE_URL"),
secret_key=require_env("SECRET_KEY"),
debug=os.getenv("DEBUG", "false").lower() in {"1", "true", "yes"},
port=int(os.getenv("PORT", "8000")),
)Now other files can import settings and use a clean object instead of repeatedly calling os.getenv(). If you are organizing a growing project, this guide on Python modules and packages will help you structure files more professionally.
Environment Variables in Flask and FastAPI
Web applications often depend heavily on environment variables. A Flask app may need SECRET_KEY, database credentials, JWT settings, mail server credentials, and third-party API tokens. A FastAPI app may need the same, plus CORS origins, OpenAPI settings, or background worker configuration. Keeping these values outside the codebase makes deployment safer and more flexible.
For Flask, load settings before creating extensions that depend on them. For FastAPI, centralize settings in a module and import them into routers, services, or dependencies. If you are building APIs, read this Flask tutorial and this guide on how to build APIs with FastAPI. Both frameworks benefit from a clean configuration strategy.
Deployment: What Changes in Production?
In production, you usually should not upload a local .env file. Most hosting platforms provide a secure interface for environment variables or secrets. Docker can receive environment variables from the runtime. Kubernetes can use Secrets and ConfigMaps. Cloud providers offer secret managers. CI/CD systems often have protected variables for deployment pipelines.
The code still reads values with os.getenv() or a configuration library. The source of the values changes. Locally, they may come from .env. In production, they come from the platform. This keeps your application portable. If you deploy Python with containers, this guide on how to run Python with Docker is a useful next step.
Security Checklist
Never commit real secrets to Git. Add .env to .gitignore. Use fake values in .env.example. Rotate credentials if they were exposed. Avoid printing secrets in logs. Do not include secrets in error messages. Restrict who can view production environment variables. Use different secrets for development, staging, and production. Replace long-lived credentials with scoped, rotated credentials when possible.
Also remember that environment variables are not magic encryption. They reduce the risk of exposing secrets in source code, but they must still be protected on the machine or platform where the app runs. The official Python os.environ documentation explains how Python exposes the process environment, but security depends on your full deployment setup.
Common Mistakes to Avoid
The first mistake is assuming that os.getenv("DEBUG") returns a boolean. It returns a string or None. The second mistake is using os.environ["KEY"] for optional values and crashing the app unnecessarily. The third mistake is using defaults for required secrets, which can hide missing configuration. The fourth mistake is committing .env to a repository. The fifth mistake is logging configuration values without filtering secrets.
Another mistake is scattering environment reads across many files. If every module calls os.getenv(), it becomes hard to know which settings the application needs. Centralize configuration instead. That makes onboarding easier and reduces hidden dependencies between modules.
Final Checklist
Use environment variables for secrets and deployment-specific settings. Use os.environ when a missing required key should crash immediately. Use os.getenv() when a value is optional or has a safe default. Convert strings to the correct type. Validate required values at startup. Use python-dotenv for local development. Never commit real .env files. Keep production secrets in your hosting platform or secret manager.
Reading environment variables safely is a small habit that protects real projects. It keeps code portable, reduces credential leaks, makes deployments cleaner, and gives your application a professional configuration layer. Once you start using this pattern, it becomes a default part of every serious Python project.




