Back to Blog
Python

Python Dotenv Secrets Management Basics

python dotenv secrets management basics: Learn how to use python-dotenv to load environment variables from .env files, manage secrets safely, and avoid common configur...

python-dotenvenvironment variablessecrets managementconfiguration.env filessecurity
Illustration of a Python application loading environment variables from a .env file into its runtime configuration.

python dotenv secrets management basics requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When a Python application needs API keys, database credentials, or other secrets, hardcoding them in source code is a common mistake. The python-dotenv library provides a straightforward way to load these values from a .env file into environment variables, keeping secrets out of the repository. This article covers the basics of python dotenv secrets management: installing the library, loading .env files, understanding variable precedence, and applying security-conscious practices.

Why Use .env Files for Secrets

A .env file is a plain text file that stores key-value pairs. It is typically placed in the project root and read by the application at startup. The main advantage is that configuration is separated from code. You can commit the code but exclude the .env file, so each developer or deployment environment can have its own secrets without modifying source files.

A .env file looks like this:

DATABASE_URL=postgresql://user:pass@localhost/db
API_KEY=sk-1234
DEBUG=true

The format is simple: each line is a KEY=VALUE pair. Lines starting with # are comments. Values can be quoted if they contain spaces or special characters.

Installing python-dotenv

Install the library with pip:

pip install python-dotenv

For a project, add it to your requirements.txt or use pipenv or poetry as appropriate. The library is pure Python and works with Python 3.7 and later.

Loading Variables with load_dotenv()

The most common usage is to call load_dotenv() at the top of your entry point, such as main.py or app.py. This function reads the .env file and sets the variables as environment variables, but it does not override existing environment variables by default.

from dotenv import load_dotenv load_dotenv()

After this call, you can access the variables using os.getenv or os.environ:

import os database_url = os.getenv("DATABASE_URL") api_key = os.getenv("API_KEY")

By default, load_dotenv() looks for a file named .env in the current working directory. You can specify a different path with the dotenv_path parameter:

load_dotenv(dotenv_path="/path/to/.env")

You can also pass override=True to force the .env values to overwrite existing environment variables, but this is rarely needed and can cause surprising behavior.

Variable Precedence and Overriding

Understanding precedence is critical for predictable behavior. The default rule is: existing environment variables take precedence over values in .env. This means if DATABASE_URL is already set in the shell, load_dotenv() will not change it. This is useful because it allows you to override .env values in production without modifying the file.

If you need to force the .env values to take precedence, use override=True:

load_dotenv(override=True)

Use this with caution. It can mask real environment variables and make debugging harder.

Handling Missing Keys and Defaults

When a key is not present in the environment or .env, os.getenv returns None. To provide a fallback, pass a default:

timeout = int(os.getenv("TIMEOUT", "30"))

For more complex validation, consider using a configuration library like pydantic or dynaconf, but for simple cases, os.getenv with defaults is sufficient.

Security Considerations for Secrets

The primary reason to use .env files is to keep secrets out of version control. You must add .env to your .gitignore file. Never commit .env to a repository, even if it contains dummy values, because it sets a bad precedent and can leak real credentials.

Other security practices:

  • Use different .env files for different environments (e.g., .env.dev, .env.prod) and load the appropriate one.
  • Rotate secrets regularly, especially if you suspect a leak.
  • Avoid putting secrets in code that is shared, such as frontend JavaScript.
  • Be aware that .env files are plain text; protect them with file permissions on shared systems.

For production, environment variables are often set directly by the deployment platform (e.g., Docker, Kubernetes, CI/CD systems). In that case, you may not need a .env file at all. python-dotenv is primarily a development convenience.

Using python-dotenv in Scripts and Frameworks

In a simple script, call load_dotenv() at the top. In a framework like Django, you typically call it in manage.py or settings.py before accessing any environment variables. For example, in manage.py:

from dotenv import load_dotenv if __name__ == "__main__": load_dotenv() # rest of the code

In a Flask app, you can call it at the top of app.py or in a config.py module.

For libraries or modules that are imported, it's generally better to let the application entry point call load_dotenv() rather than doing it inside a library, to avoid surprising side effects.

Common Pitfalls and Troubleshooting

  • Path issues: load_dotenv() looks in the current working directory. If you run your script from a different directory, it won't find the file. Use an absolute path or Path(__file__).resolve().parent to locate the .env relative to the script.

  • Spaces and special characters: Values with spaces should be quoted: KEY="value with spaces". The parser handles basic quoting, but avoid complex escaping.

  • Comments: Lines starting with # are ignored. Inline comments are not supported; treat the entire line as the value.

  • Variable expansion: python-dotenv does not expand variables like ${VAR} by default. If you need that, use load_dotenv(expand_vars=True) (available in newer versions).

  • Empty values: An empty value is treated as an empty string, not None. Use os.getenv("KEY") and check for empty string if needed.

When a .env file is not found, load_dotenv() does not raise an error; it simply does nothing. This can be confusing if you expect variables to be set. Always verify that the file path is correct and that the file is readable.

python dotenv secrets management basics: Practical Usage and | RYUSLOG DEV