python-dotenv
environment variables
Python
dotenv library
configuration management

What is the use of python-dotenv?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

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.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.