What does EnvDoctor check?

EnvDoctor is my open-source Python command-line tool. check compares keys in two configuration files; sync adds local keys missing from the template; audit compares environment-variable references in source code with a selected .env file. These are three different checks.

Installation requires Python 3.12 or newer. This guide uses CLI version 0.2.0. The examples were executed on 6 October 2026 against the repository source in a separate local directory. All values are demonstration data, not results from a production deployment.

Terminal
python -m pip install envdoctor-cli==0.2.0
python -m envdoctor --version
# envdoctor 0.2.0

Create a deliberate mismatch between two files

Create the following .env and .env.example files separately in an empty demonstration directory. LOG_LEVEL exists locally but not in the template. The template requires REDIS_URL, which is missing locally. API_TOKEN is a dummy value.

The files have different roles: .env holds local values, while .env.example documents the required keys. Do not commit real secrets. The local addresses below demonstrate key checks only; EnvDoctor does not connect to these services.

ENV
# .env
DATABASE_URL=postgresql://localhost:5432/demo
API_TOKEN=demo_token_not_a_real_secret
LOG_LEVEL=info

# .env.example
DATABASE_URL=postgresql://localhost:5432/demo
API_TOKEN=
REDIS_URL=redis://localhost:6379/0

check: understand missing keys and strict mode

Run python -m envdoctor --format json check --strict inside the demonstration directory. --format is a global option and must appear before the check subcommand. The command produced the following JSON and exited with code 1.

missing_in_env identifies REDIS_URL, required by the template but absent locally. missing_in_example identifies LOG_LEVEL, present locally but undocumented in the template. Default check fails for required keys missing locally; --strict also fails for keys missing from the template and empty local values.

JSON
{
  "valid": false,
  "strict": true,
  "env_file": ".env",
  "example_file": ".env.example",
  "total_env_keys": 3,
  "total_example_keys": 3,
  "missing_in_env": [
    "REDIS_URL"
  ],
  "missing_in_example": [
    "LOG_LEVEL"
  ],
  "empty_in_env": []
}

sync --dry-run: preview before writing

python -m envdoctor --format json sync --dry-run reported that only LOG_LEVEL would be added and exited with code 0. --dry-run previews additions without changing .env.example.

The direction matters: sync works from .env to .env.example. It does not add REDIS_URL from the template to the local file. Synchronizing keys does not discover missing connection values.

JSON
{
  "source_env": ".env",
  "target_example": ".env.example",
  "dry_run": true,
  "added_count": 1,
  "added_keys": [
    "LOG_LEVEL"
  ]
}

sync: update the template, then check again

After the preview, python -m envdoctor --format json sync exited with code 0 and appended the lines below to the template. It generated your_log_level_here rather than copying LOG_LEVEL=info.

sync does not rewrite or remove existing template keys. Repeating the strict check still returned code 1: LOG_LEVEL was documented, but REDIS_URL remained absent from .env. sync also preserves existing template content and comments; it is not a tool that sanitizes the entire file.

ENV
# .env.example dosyasına eklenen satırlar
# Synchronized by EnvDoctor
LOG_LEVEL=your_log_level_here

Add the missing local configuration

For this demonstration, REDIS_URL=redis://localhost:6379/0 was added to .env. In your own project, obtain the appropriate value from an authorized configuration source. Running python -m envdoctor --format json check --strict then produced the following output and exit code 0.

valid: true means the file keys passed this check. It does not prove database availability, token validity or successful application startup. Those require separate connection and application tests.

JSON
{
  "valid": true,
  "strict": true,
  "env_file": ".env",
  "example_file": ".env.example",
  "total_env_keys": 4,
  "total_example_keys": 4,
  "missing_in_env": [],
  "missing_in_example": [],
  "empty_in_env": []
}

A small Python input for audit

Create src/app.py with the content below. SENTRY_DSN is referenced by the code but absent from .env. LOG_LEVEL is present in .env but not referenced by this small source file.

You do not need to execute the file as an application. audit scans source text without connecting to services through those variables. This example shows that matching configuration files can still leave a missing key referenced by code.

Python
import os

DATABASE_URL = os.getenv("DATABASE_URL")
API_TOKEN = os.getenv("API_TOKEN")
REDIS_URL = os.getenv("REDIS_URL")
SENTRY_DSN = os.getenv("SENTRY_DSN")

audit: compare code with the selected .env file

python -m envdoctor --format json audit ./src found four references and exited with code 1 because SENTRY_DSN was missing. The summary fields are shown below. The full JSON also includes each reference's file path, line number and source snippet.

In this version, audit compares against .env by default; it does not automatically use .env.example. To audit against the template, use python -m envdoctor --format json audit ./src --env .env.example. --env selects the comparison file.

LOG_LEVEL in stale_in_env means no reference was found in the scanned scope. Another service, deployment script or dynamic lookup may use it. Do not delete keys automatically based on this report.

JSON
{
  "scanned_paths": [
    "src"
  ],
  "total_usages": 4,
  "referenced_keys_count": 4,
  "known_keys_count": 4,
  "missing_from_env": [
    "SENTRY_DSN"
  ],
  "stale_in_env": [
    "LOG_LEVEL"
  ]
}

Exit codes: success, mismatch and usage errors

In these examples, 0 means the check passed or sync completed, 1 means check/audit found a mismatch, and 2 indicates a missing file or invalid command usage. Running check against a nonexistent missing.env returned code 2 and a file-not-found message.

In PowerShell, read $LASTEXITCODE immediately after the command. In CI, check the exit code instead of relying only on descriptive output. Review reports before sharing them: audit includes snippets from source-code lines.

PowerShell
python -m envdoctor --format json check --strict
$LASTEXITCODE

python -m envdoctor --format json audit ./src
$LASTEXITCODE

Understand the limitations of regex scanning

The previous version of this article incorrectly described AST analysis. The inspected auditor.py scans lines with regular expressions. It has patterns for direct Python os.getenv/os.environ lookups, JavaScript/TypeScript process.env, Go os.Getenv and Rust env::var. A file extension being scanned does not mean all lookup forms in that language are supported.

In the example below, the direct os.getenv("API_TOKEN") lookup is recognized, while a variable key and a separately imported getenv are not recognized the same way. A clean audit report therefore does not guarantee that no configuration keys are missing. Review dynamic lookups and files outside the scan scope separately.

This guide does not claim to eliminate production outages. It shows what each check can find through a reproducible example. Source code and the project description are linked below. The content was updated on 6 October 2026; its original publication date has been retained.

Python
import os
from os import getenv

key = "API_TOKEN"
direct = os.getenv("API_TOKEN")
dynamic = os.getenv(key)
aliased = getenv("API_TOKEN")

Source: EnvDoctor project case study

Source: Inspected EnvDoctor source code

Source: EnvDoctor 0.2.0 Python package

Source: Related guide: debugging with hypotheses