# How to Manage Environment Variables in a Python Project


API keys and database credentials change between machines and environments; hardcoding them leaks secrets into version control. A `.env` file keeps these values outside the code, where each environment supplies its own.

## Prerequisites

- [uv installed](https://pydevtools.com/handbook/how-to/how-to-install-uv.md)
- A uv project (run `uv init` to create one)

## Create a `.env` file

Add a `.env` file to the project root with one `KEY=value` pair per line:

```text {filename=".env"}
DATABASE_URL=postgres://localhost:5432/mydb
SECRET_KEY=change-me-in-production
API_KEY=sk-test-abc123
```

## Add `.env` to `.gitignore`

> [!WARNING]
> Add this line **before the first commit** that contains the `.env` file. Removing a file after it has been pushed does not erase it from Git history.

Open `.gitignore` and add:

```text
.env
```

Projects created with `uv init` ship a `.gitignore`, but it does not include `.env` by default. For full `.gitignore` setup, see [how to put your Python project on GitHub](https://pydevtools.com/handbook/how-to/how-to-put-your-python-project-on-github.md).

## Load variables with `uv run --env-file`

[uv](https://pydevtools.com/handbook/reference/uv.md)'s `--env-file` flag injects variables into the process environment before Python starts. `uv run` executes the command inside the project's {{< term "virtual-environment" "virtual environment" >}}, ensuring all dependencies are installed first. Without `--env-file`, the script raises `KeyError: 'DATABASE_URL'` because no environment variable is set.

```bash
uv run --env-file .env python main.py
```

```python {filename="main.py"}
import os

database_url = os.environ["DATABASE_URL"]  # raises KeyError if missing
api_key = os.getenv("API_KEY", "fallback")  # returns "fallback" if missing
```

Pass `--env-file` multiple times to layer files (later files override earlier ones). Set `UV_ENV_FILE=.env` in the shell to avoid typing the flag on every invocation. If the shell and the `.env` file both define the same variable, the shell value wins.

## Load variables with python-dotenv

When the project needs variables available at import time without passing a CLI flag, install python-dotenv:

```bash
uv add python-dotenv
```

Call `load_dotenv()` at the top of the entry point:

```python {filename="main.py"}
from dotenv import load_dotenv
import os

load_dotenv()  # reads .env; does not override variables already set in the shell

database_url = os.environ["DATABASE_URL"]
```

## Document required variables with `.env.example`

Create a `.env.example` that lists every variable the project expects, with placeholder values:

```text {filename=".env.example"}
DATABASE_URL=postgres://user:password@localhost:5432/dbname
SECRET_KEY=change-me
API_KEY=your-api-key-here
```

Commit `.env.example` to version control. New contributors copy it to `.env` and fill in real values. Keep it updated when adding or removing variables.

## Keep secrets away from AI coding agents

AI coding assistants read project files by default, including `.env`. Add `.env` to the agent's ignore file (`.claudeignore` for Claude Code, `.cursorignore` for Cursor) to prevent it from reading secrets:

```text {filename=".claudeignore"}
.env
```

## Learn More

- [uv run CLI reference](https://docs.astral.sh/uv/reference/cli/#uv-run) documents `--env-file`, `--no-env-file`, and `UV_ENV_FILE`.
- [python-dotenv documentation](https://saurabh-kumar.com/python-dotenv/) covers interpolation, encoding, and stream parsing.
- [How to put your Python project on GitHub](https://pydevtools.com/handbook/how-to/how-to-put-your-python-project-on-github.md) covers `.gitignore` setup for new projects.
- [direnv](https://pydevtools.com/handbook/reference/direnv.md) loads `.envrc` files automatically when entering a directory instead of requiring manual `--env-file` flags.
