How to Create and Use .env Files in Python
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=True
These 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-dotenv
If you're using a virtual environment (recommended), activate it before running the install command so the package is scoped to your project.
Step 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=True
- No spaces around the
=sign (some parsers tolerate them, but it's safest to omit them). - Use quotes around values that contain spaces:
APP_NAME="My Cool App". - Lines starting with
#are treated as comments:# This is a comment. - Never commit this file to version control.
Step 3: Add .env to .gitignore
To prevent accidentally pushing secrets to a repository, add .env to your .gitignore file:
# Environment variables
.env
It'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 variables from .env into the environment
load_dotenv()
# Access variables
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: True
How 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()
# Falls back to 5432 if PORT is not defined in .env
port = os.getenv("PORT", "5432")
print(f"Port: {port}")
Output (when PORT is not in .env):
Port: 5432
Using 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/mydb
This approach is useful when you want to keep .env values isolated from the system environment and avoid potential naming conflicts with existing environment variables.
Loading 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 from a custom path
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
# ❌ .env values are NOT loaded: os.getenv returns None
api_key = os.getenv("API_KEY")
print(api_key)
Output:
None
Fix: 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_here
Some 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_here
Mistake 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.
If you've already committed a .env file, removing it from the repository is not enough: the secrets remain in the Git history. You'll need to rotate all exposed credentials and consider tools like git filter-repo to purge the file from history.
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=True
import os
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
# Access variables with fallback defaults
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")
# Display the configuration
print(f"Application: {app_name} v{app_version}")
print(f"Debug Mode: {debug}")
# Verify that secret key is loaded
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.