Managing sensitive information like API keys, database credentials, and secret tokens directly in your source code is a serious security risk. .env files offer a clean, secure, and widely adopted solution: store your configuration in a simple text file and load it into your Python application at runtime.
This guide walks you through the entire process, from creating a .env file to reading its values in Python using the python-dotenv library, with practical examples, best practices, and common mistakes to avoid.
What Is a .env File?
A .env file is a plain text file that holds key-value pairs representing environment variables. Each line follows the format KEY=VALUE:
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
SECRET_KEY=supersecretkey123
DEBUG=TrueThese files are not meant to be committed to version control. Instead, they stay on each developer's machine (or deployment environment), keeping sensitive data out of the codebase entirely.
Why Use .env Files?
- Security: API keys and passwords stay out of your source code and Git history.
- Portability: different environments (development, staging, production) can use different
.envfiles without changing code. - Simplicity: configuration is centralized in one readable file.
Prerequisites
Before you begin, make sure you have:
- Python 3 installed (download here)
- pip available (it comes bundled with modern Python installations)
Step 1: Install the python-dotenv Library
The python-dotenv package reads .env files and loads the variables into your application's environment. Install it with pip:
pip install python-dotenvStep 2: Create the .env File
In the root directory of your project, create a file named .env (note the leading dot). Add your configuration as key-value pairs:
API_KEY=your_api_key_here
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
SECRET_KEY=supersecretvalue
DEBUG=TrueStep 3: Add .env to .gitignore
To prevent accidentally pushing secrets to a repository, add .env to your .gitignore file:
.envIt's also a good practice to provide a template file (.env.example) that documents the required variables without containing real values:
API_KEY=
DATABASE_URL=
SECRET_KEY=
DEBUG=This way, other developers know which variables they need to configure.
Step 4: Load and Access Variables in Python
Use load_dotenv() to load the .env file, then access individual values with os.getenv():
import os
from dotenv import load_dotenv
load_dotenv()
api_key = os.getenv("API_KEY")
database_url = os.getenv("DATABASE_URL")
debug = os.getenv("DEBUG")
print(f"API Key: {api_key}")
print(f"Database URL: {database_url}")
print(f"Debug Mode: {debug}")Output:
API Key: your_api_key_here
Database URL: postgresql://user:password@localhost:5432/mydb
Debug Mode: TrueHow It Works
load_dotenv()reads the.envfile and injects its key-value pairs into the process's environment variables.os.getenv("KEY")retrieves the value associated withKEYfrom the environment.- If a variable is not found,
os.getenv()returnsNoneby default.
Providing Default Values
If a variable might not exist in the .env file, you can supply a fallback value as the second argument to os.getenv():
import os
from dotenv import load_dotenv
load_dotenv()
port = os.getenv("PORT", "5432")
print(f"Port: {port}")Output (when PORT is not in .env):
Port: 5432Using dotenv_values() as an Alternative
If you prefer to load .env values into a dictionary instead of injecting them into the environment, use dotenv_values():
from dotenv import dotenv_values
config = dotenv_values(".env")
print(config["API_KEY"])
print(config["DATABASE_URL"])Output:
your_api_key_here
postgresql://user:password@localhost:5432/mydbLoading a Custom .env File Path
By default, load_dotenv() looks for a .env file in the current working directory. You can point it to a different file:
from dotenv import load_dotenv
load_dotenv(dotenv_path="/path/to/config/.env.production")This is especially handy when you maintain separate files per environment, such as .env.development, .env.staging, and .env.production.
Common Mistakes and How to Avoid Them
Mistake 1: Forgetting to Call load_dotenv()
import os
api_key = os.getenv("API_KEY")
print(api_key)Output:
NoneFix: Always call load_dotenv() before accessing the variables:
import os
from dotenv import load_dotenv
load_dotenv() # ✅ Load .env first
api_key = os.getenv("API_KEY")
print(api_key)Mistake 2: Adding Spaces Around the = Sign
API_KEY = your_api_key_hereSome parsers interpret the key as API_KEY (with a trailing space), causing os.getenv("API_KEY") to return None.
Fix: Remove spaces around =:
API_KEY=your_api_key_hereMistake 3: Committing .env to Version Control
Pushing your .env file to a public (or even private) repository exposes your secrets. Always add it to .gitignore before your first commit.
Complete Working Example
Here's a full end-to-end example putting everything together:
APP_NAME=MyPythonApp
APP_VERSION=1.0.0
SECRET_KEY=abc123xyz
DEBUG=Trueimport os
from dotenv import load_dotenv
load_dotenv()
app_name = os.getenv("APP_NAME", "DefaultApp")
app_version = os.getenv("APP_VERSION", "0.0.1")
secret_key = os.getenv("SECRET_KEY")
debug = os.getenv("DEBUG", "False")
print(f"Application: {app_name} v{app_version}")
print(f"Debug Mode: {debug}")
if secret_key:
print("Secret key loaded successfully.")
else:
print("WARNING: Secret key is missing!")Output:
Application: MyPythonApp v1.0.0
Debug Mode: True
Secret key loaded successfully.Conclusion
Using .env files in Python is a best practice for managing configuration and sensitive data. Combined with the python-dotenv library, the workflow is straightforward:
- Create a
.envfile with your key-value pairs. - Add
.envto.gitignoreto keep secrets out of version control. - Install
python-dotenvand callload_dotenv()in your code. - Access values with
os.getenv()or usedotenv_values()for a dictionary approach.
This pattern keeps your credentials secure, makes your application portable across environments.