303 — Loading .env Files and Environment Variables

Beginner

Install python-dotenv and use load_dotenv() to load .env files into the process environment. Understand env var precedence, handle missing .env files safely, and learn why .env files must never be committed to source control — with a .gitignore pattern and a .env.example template to enforce that boundary.

Learning Objectives

1
Install python-dotenv and create a .env file with API keys and config values
2
Load a .env file into the process environment with load_dotenv()
3
Read loaded values with os.getenv() and os.environ
4
Understand env var precedence: existing env vars are NOT overridden by load_dotenv() by default
5
Use override=True to force a .env value to replace an existing env var
6
Confirm that load_dotenv() returns False when the .env file is missing and never raises an exception
7
Apply the .gitignore + .env.example pattern so secrets never reach source control
8
Build env_loader.py: a reusable config loader that validates required env vars and fails fast when any are missing
Step 1

Set up the lesson 303 workspace, install python-dotenv, and create a .env file

Create the lesson-303 directory, activate the virtual environment, install python-dotenv, and write a `.env` file that holds an API key, a base URL, and a debug flag. Then verify that load_dotenv() makes those values available to os.getenv().

Commands to Run

mkdir -p ~/devops-python/lesson-303
cd ~/devops-python/lesson-303
source ~/devops-python/lesson-101/devops-env/bin/activate
pip install "python-dotenv>=1,<2"
pip show python-dotenv | grep -E 'Name|Version'

Files for This Step

.envPaste this into the file
# Deployment secrets — DO NOT COMMIT THIS FILE
API_KEY=secret-key-abc123
API_BASE_URL=https://api.example.internal
DEBUG=true
MAX_RETRIES=3
verify_load.pyPaste this into the file
import os
from dotenv import load_dotenv
from pathlib import Path

# Load the nearest .env file using python-dotenv's default search.
# For a fixed location, pass dotenv_path=Path(__file__).resolve().parent / '.env'.
loaded = load_dotenv()
print(f'load_dotenv() returned: {loaded}')
print()
print(f'API_KEY:      {os.getenv("API_KEY")}')
print(f'API_BASE_URL: {os.getenv("API_BASE_URL")}')
print(f'DEBUG:        {os.getenv("DEBUG")}')
print(f'MAX_RETRIES:  {os.getenv("MAX_RETRIES")}')

After Saving

python3 verify_load.py

What This Does

python-dotenv is not part of the Python standard library, so it must be installed with pip.

The version pin python-dotenv>=1,<2 locks to the v1.x stable series.

The .env file is a plain text file where each line is a KEY=value pair.

Lines starting with # are comments and are ignored. load_dotenv() uses find_dotenv() when you do not pass dotenv_path=; in a normal script that means it searches for the nearest .env file by walking upward from the calling script's directory.

It parses each KEY=value line and calls os.environ.__setitem__() for any key not already present in the process environment.

After load_dotenv() returns, the values are available through the standard os.getenv() and os.environ interfaces — no dotenv-specific API is needed to read them.

Expected Outcome

pip show prints Name: python-dotenv and Version: 1.x. verify_load.py prints load_dotenv() returned: True, then API_KEY: secret-key-abc123, API_BASE_URL: https://api.example.internal, DEBUG: true, and MAX_RETRIES: 3.

Pro Tips

  • 1
    All values in a .env file are strings — including `true`, `false`, `3`, and quoted numbers. `os.getenv('DEBUG')` returns the string `'true'`, not Python `True`. Convert explicitly: `debug = os.getenv('DEBUG', 'false').lower() == 'true'`.
  • 2
    python-dotenv is compatible with any Python project that reads env vars via `os.environ` or `os.getenv`. It is not framework-specific — the same load_dotenv() call works in standalone scripts, Flask apps, FastAPI services, and Airflow DAGs.
Was this step helpful?

All Steps (0 / 8 completed)