python-dotenv
environment variables
Python
dotenv library
configuration management

What is the use of python-dotenv?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

python-dotenv exists to make local configuration behave more like deployment-time environment configuration. Many Python applications expect settings in os.environ, but typing those values into your shell for every run is tedious and error-prone. A .env file plus python-dotenv gives you a simple development-time bridge without hardcoding credentials into source files.

Core Sections

What python-dotenv Actually Does

The library reads key-value pairs from a .env file and loads them into the current process environment.

Typical file:

env
DATABASE_URL=postgresql://localhost:5432/appdb
DEBUG=true
API_KEY=local-dev-key

Basic usage:

python
1from dotenv import load_dotenv
2import os
3
4load_dotenv()
5
6database_url = os.getenv("DATABASE_URL")
7debug = os.getenv("DEBUG", "false").lower() == "true"
8
9print(database_url)
10print(debug)

That lets your application keep reading configuration from os.environ whether values came from a real deployment environment or from a local file.

Why This Is Useful in Development

Without python-dotenv, developers often end up doing one of these:

  • exporting variables manually in every shell
  • hardcoding credentials in Python files
  • keeping machine-specific config in ad hoc scripts

All three approaches are harder to maintain than a clear .env file loaded at startup.

Load Specific Files or Read Without Mutating os.environ

You can target a specific file:

python
from dotenv import load_dotenv

load_dotenv(".env.local")

Or read values into a dictionary without modifying the process environment:

python
1from dotenv import dotenv_values
2
3config = dotenv_values(".env.shared")
4print(config["SERVICE_NAME"])

dotenv_values() is useful for tooling, tests, and config inspection code where you do not want side effects.

Typical Project Pattern

A practical project layout is:

  1. commit .env.example with safe placeholders
  2. ignore real .env in version control
  3. call load_dotenv() only for local development
  4. rely on real environment variables in staging and production

Example startup logic:

python
1from dotenv import load_dotenv
2import os
3
4if os.getenv("ENVIRONMENT", "development") == "development":
5    load_dotenv()
6
7settings = {
8    "db_url": os.environ["DATABASE_URL"],
9    "log_level": os.getenv("LOG_LEVEL", "INFO"),
10}
11
12print(settings)

This prevents local convenience behavior from overriding production configuration unexpectedly.

Existing Environment Variables Usually Win

By default, load_dotenv() does not overwrite values that are already present in the environment. That is usually the correct behavior because deployment systems, containers, and CI should have higher priority than a local file.

If you explicitly want file values to win:

python
1from dotenv import load_dotenv
2import os
3
4os.environ["DEBUG"] = "false"
5load_dotenv(override=True)
6
7print(os.environ["DEBUG"])

Use override=True carefully. It can hide configuration errors if overused.

Framework Examples

This pattern is common in:

  • Flask and FastAPI startup modules
  • Django local settings bootstrap
  • CLI tools
  • test harnesses

The library is not tied to any one framework. It is just a small configuration-loading utility.

What python-dotenv Is Not

It is not:

  • encrypted secret storage
  • a production secret manager
  • a replacement for Kubernetes secrets, cloud secret stores, or CI secret injection

It is a local developer convenience layer. That distinction matters because plain text .env files are easy to misuse if treated like secure vaults.

Good Configuration Hygiene

A good config module does three things:

  1. load environment once, early
  2. parse strings into proper types
  3. centralize validation

For example:

python
1import os
2from dotenv import load_dotenv
3
4load_dotenv()
5
6PORT = int(os.getenv("PORT", "8000"))
7DEBUG = os.getenv("DEBUG", "false").lower() == "true"
8DATABASE_URL = os.environ["DATABASE_URL"]

This is better than scattering os.getenv calls across the whole codebase.

Common Pitfalls

  • Treating .env as a secure production secret store.
  • Committing the real .env file to source control.
  • Calling load_dotenv() too late, after other modules already read settings.
  • Forgetting that every loaded value starts as a string.
  • Using override=True casually and masking real environment configuration.

Summary

  • 'python-dotenv loads local config from .env files into environment variables.'
  • It is primarily a development and testing convenience tool.
  • Use load_dotenv() to populate os.environ and dotenv_values() for side-effect-free reads.
  • Keep real .env files out of Git and treat them as plain text, not secure storage.
  • Centralize parsing and validation so loaded values become reliable application settings.

Course illustration
Course illustration

All Rights Reserved.