envgap
Find gaps between .env, .env.example, shell variables, and Python code.
envgap is a diagnostic CLI for Python projects that use .env files, .env.example, shell variables, and os.environ / os.getenv in code. It does not load your config. It shows the gaps between what your app expects, what your project documents, and what your environment actually provides.
envgap detects Pydantic BaseSettings fields, aliases, and env prefixes for FastAPI-style projects.
It also flags Docker Compose environment keys that are missing from .env.example.
Why Developers Try It
- Catch missing env vars before CI, Docker, or another developer's machine fails.
- Spot stale
.env.examplefiles that no longer match real code. - Find typo-shaped drift like
DB_URLvsDATABASE_URL. - Detect placeholder secrets such as
changeme,todo, andyour-key-here. - Add a lightweight config check to CI without adopting a new settings framework.
envgap is intentionally not a .env loader. It is the tool you run when you want the project to explain why config works in one place and breaks somewhere else.
Contents
- 30-Second Demo
- When It Helps
- Why
- Install
- Quick Start
- What It Checks Today
- Exit Codes
- CI
- pre-commit
- Example Diagnosis
- Why Not Just python-dotenv?
- Current Scope
- Roadmap
- Contributing
- Development
30-Second Demo
Given a project with:
# .env
DB_URL=postgres://localhost/app
OPENAI_API_KEY=changeme
# app.py
import os
DATABASE_URL = os.environ["DATABASE_URL"]
Run:
$ DATABASE_URL=postgres://shell/app envgap check examples/basic
envgap check
===============
Checked:
shell environment: available (1/4 expected key(s) found)
.env: found (3 key(s))
.env.example: found (3 key(s))
Python code: 3 env usage(s)
Diagnosis:
DATABASE_URL
! Missing value: DATABASE_URL is missing from .env (app.py:3)
It is present in your shell environment, so local commands may work while CI or Docker still fails.
Suggested fix: Add DATABASE_URL=... to .env or document how CI/Docker should provide it.
DB_URL
~ Possible typo: DB_URL may be a typo for DATABASE_URL (.env:1)
Suggested fix: Rename DB_URL to DATABASE_URL if they represent the same setting.
When It Helps
Use envgap when a Python project has config spread across:
- local
.env - documented
.env.example - shell exports
- Python code
- CI or Docker conventions
It is especially useful for Python backend projects, FastAPI/Django/Flask apps, AI/data apps with API keys, and open-source projects where .env.example must stay useful for new contributors.
Why
Environment config bugs are boring until they eat an afternoon.
Common examples:
- The app expects
DATABASE_URL, but.envcontainsDB_URL. .env.examplesays a key exists, but local.envnever got it.- A secret is still set to
changeme. - A variable works locally only because it is exported in your shell.
- CI, Docker, or another developer's machine fails because the real required variables are not documented.
envgap is for that moment when you want the project to explain itself.
Install
With pip:
pip install envgap
With uv:
uv tool install envgap
With pipx:
pipx install envgap
From a local checkout:
pip install -e ".[dev]"
Quick Start
Run a check in the current project:
envgap check
Try the included broken example:
envgap check examples/basic
Try a FastAPI-style settings example:
envgap check examples/fastapi
Try a Docker Compose drift example:
envgap check examples/docker-compose
Show machine-readable output:
envgap check --json
Ignore shell variables for deterministic CI checks:
envgap check --no-shell
Fail on warnings as well as errors:
envgap check --strict
--ci is supported as a CI-friendly alias for --strict:
envgap check --ci
Use custom dotenv filenames:
envgap check --env-file .env.local --example-file .env.example
What It Checks Today
envgap check currently inspects:
- current shell environment
.env.env.example- Python files using common environment variable APIs
- Pydantic
BaseSettingsfields used by FastAPI-style settings modules - Docker Compose files named
compose.yml,compose.yaml,docker-compose.yml, ordocker-compose.yaml
It detects:
- missing keys
- undocumented extra keys
- duplicate keys
- empty values
- placeholder values like
your-key-here,changeme,todo, andreplace-me - likely typo pairs like
DB_URLvsDATABASE_URL - required env vars used in Python code but missing from
.env.example - required Pydantic settings fields missing from
.envor.env.example - Docker Compose environment keys missing from
.env.example - missing
.env - missing
.env.example
It scans Python code for:
os.environ["DATABASE_URL"]
os.getenv("DATABASE_URL")
os.getenv("DATABASE_URL", "sqlite:///local.db")
os.environ.get("DATABASE_URL", "sqlite:///local.db")
It also scans Pydantic Settings classes:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
openai_api_key: str
debug: bool = False
Required vs optional behavior:
os.environ["KEY"]is requiredos.getenv("KEY")is requiredos.getenv("KEY", default)is optionalos.environ.get("KEY", default)is optionalBaseSettingsfields without defaults are requiredBaseSettingsfields with defaults are optionalField(alias=...)andField(validation_alias=...)use the configured env name- simple
env_prefixsettings are applied to field names
It scans common Docker Compose environment patterns:
services:
web:
env_file:
- .env.docker
environment:
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: redis://redis:6379/0
Exit Codes
| Command | Exit code behavior |
|---|---|
envgap check |
exits 1 when errors are present |
envgap check --strict |
exits 1 when errors or warnings are present |
envgap check --ci |
same as --strict |
envgap check --json |
same pass/fail behavior, JSON output |
envgap check --no-shell |
ignores current shell variables when diagnosing missing keys |
Warnings do not fail a normal check unless --strict or --ci is used.
CI
Minimal GitHub Actions step:
- name: Check environment config drift
run: |
pip install envgap
envgap check --strict
Full workflow:
name: envgap
on: [push, pull_request]
jobs:
envgap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install envgap
- run: envgap check --ci
pre-commit
Use envgap as a pre-commit hook to catch config drift before a change lands:
repos:
- repo: https://github.com/Pinak-Datta/envgap
rev: v0.2.2
hooks:
- id: envgap
Install pre-commit if you do not already have it:
pipx install pre-commit
Run it manually:
pre-commit run envgap --all-files
Example Diagnosis
Given:
# .env
DB_URL=postgres://localhost/app
OPENAI_API_KEY=changeme
# .env.example
DATABASE_URL=
OPENAI_API_KEY=your-key-here
# app.py
import os
DATABASE_URL = os.environ["DATABASE_URL"]
DEBUG = os.getenv("DEBUG", "false")
envgap can report:
DATABASE_URLis required in code but missing from.envDB_URLmay be a typo forDATABASE_URLOPENAI_API_KEYstill looks like a placeholderDEBUGis optional because it has a default
Why Not Just python-dotenv?
python-dotenv loads environment variables.
envgap explains whether the environment variables your app expects match the variables your project defines and documents.
The useful question is not only:
Did
.envload?
It is:
What does my app expect, where should it come from, and why is it missing or wrong here?
How envgap Differs from Other Tools
| Tool | Purpose | How envgap differs |
|---|---|---|
| python-dotenv | Loads .env files |
envgap diagnoses drift between code, .env, .env.example, and shell variables. |
| pydantic-settings | Validates application settings | envgap diagnoses configuration drift instead of validating settings. |
| django-environ | Reads environment configuration for Django | envgap is framework-independent and diagnoses configuration drift. |
| Secret scanners | Detect leaked secrets | envgap finds missing or inconsistent configuration rather than exposed secrets. |
Current Scope
This is intentionally a small diagnostic tool, not a config framework.
In scope now:
.env.env.example- shell environment
- Python
os.environ/os.getenvscanning - Pydantic
BaseSettingsfield, alias, and env prefix detection - Docker Compose
environmentand safe project-localenv_filedetection - terminal and JSON reports
- CI-friendly exit codes
Not in scope yet:
- loading or mutating your environment
- validating every framework-specific settings edge case
- full Docker Compose YAML validation or interpolation precedence modeling
- GitHub Actions secrets parsing
- dynamic Pydantic settings config and nested settings
Roadmap
- dynamic Pydantic settings config and nested settings
- Django settings helper detection
- deeper Docker Compose precedence explanations
- GitHub Actions env/secrets detection
- precedence explanations for shell vs
.envvs framework defaults - GitHub Actions annotations
- richer JSON schema for editor and CI integrations
See the full roadmap.
Contributing
Real-world config examples are the most useful contribution right now.
New contributors can start with good first issues, help wanted issues, or false positives from their own Python projects.
Good first contributions:
- report a false positive with a tiny redacted example
- add a missing framework pattern
- improve docs for CI, FastAPI, Django, or Docker users
- add tests for typo detection edge cases
See CONTRIBUTING.md and SUPPORT.md.
Development
python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"
pytest
Run the example locally:
envgap check examples/basic
Run the FastAPI-style example:
envgap check examples/fastapi
Run the Docker Compose example:
envgap check examples/docker-compose
Run the shell-aware example:
DATABASE_URL=postgres://shell/app envgap check examples/basic
License
MIT
Metadata
Release files for envgap 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| envgap-0.3.0.tar.gz | 96.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| envgap-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 119.0 kB
Release files / envgap-0.3.0.tar.gz
| Download URL | envgap-0.3.0.tar.gz |
|---|---|
| Size | 96.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ea141849fb5e34d665a186018ce2f04e0346cfdb48b5fbec76b31883b8a399c3
|
|
BLAKE2b-256 checksum How to use checksums |
2c44466bca0931993499a04df27285442570cecbbbd8ab966b66829d365bbf7b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.0
|
Release files / envgap-0.3.0-py3-none-any.whl
| Download URL | envgap-0.3.0-py3-none-any.whl |
|---|---|
| Size | 22.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
10dcfcea0f2dee83129c83f544746bd4d175f8a97b4711e806ef4296dbb875d1
|
|
BLAKE2b-256 checksum How to use checksums |
7c822d861d444796f173c3af4da2e5f15a3bb56eca77583978e97bce2c54a852
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.0
|