Catch environment drift
before it ships.
A consistency checker that reconciles the environment variables your
code uses against the ones your .env files
define — and flags every mismatch.
one tool · six languages
npm install -g @arunskg/envdoctor
One tool, six languages
Published as a standalone native port for each ecosystem — no Node required, no wrappers. Pick yours.
Node reference implementation
View on npm →npm install -g @arunskg/envdoctorprocess.env.X · import.meta.env.X
Every native port is at full feature parity with the Node reference — all ten detectors (missing, unused, duplicates, public-prefix secret leaks, weak secrets, typos, environment diffs, type mismatches, schema validation), Docker Compose / Kubernetes / GitHub Actions scanning, the scan/diff/sync/init/fix subcommands, and a --json mode — with byte-identical output across languages.
Python
View on PyPI →pip install arun-envdoctoros.getenv · os.environ[...] · os.environ.get
The PyPI package is named arun-envdoctor (the bare name is blocked), but the installed command and importable package are both envdoctor.
go install github.com/arun-skg/envdoctor/go/cmd/envdoctor@latestos.Getenv · os.LookupEnv
composer require --dev arun-skg/envdoctorgetenv · $_ENV · $_SERVER
<dependency> <groupId>io.github.arun-skg</groupId> <artifactId>envdoctor</artifactId> <version>0.1.0</version> </dependency>
System.getenv("X")
Perl pending CPAN release
cpanm App::Envdoctor$ENV{X}
The Perl port is implemented and awaiting its first CPAN release.
Every port shares the same CLI:
envdoctor scan --dir .
and exits 1 on errors.
See exactly what drifted
One command reconciles every source, then prints a readable audit. Values are never shown.
What it checks
A suite of detectors runs over one normalized model of every place a variable can appear.
Missing / undefined
A variable is referenced in source code, Compose, Kubernetes, or Actions but never defined in any .env file.
Unused variables
Defined in a .env file but never referenced anywhere in your code or config — dead entries that drift out of sync.
Duplicates
The same key defined twice in one file, or across files sharing one environment label — a classic source of “which value wins?”.
ErrorType mismatch
Incompatible inferred types across environments, or a value that fails its own inferred type — e.g. PORT expected integer, found string.
Public-prefix leak
A secret-looking variable uses a public framework prefix (NEXT_PUBLIC_*, VITE_*) and would ship straight into a client bundle.
Local-first & private
Everything runs on your machine. No network calls, no telemetry, and variable values are never printed or written to any artifact.
By designGetting started
Give envdoctor a directory, and it reconciles your code against your environment files.
NODE_ENV=development NODE_ENV=production # duplicate DATABASE_URL=postgres://localhost/app PORT=three-thousand # not an integer DEBUG_MODE=true # never used
const db = process.env.DATABASE_URL;
const port = process.env.PORT;
const flag = process.env.NEW_FEATURE_FLAG;
// ^ not defined
// in any .env file
Bootstrap your project
envdoctor init creates a config, a generated .env.example, and ENVIRONMENT.md docs — never overwriting existing files.
Scan for issues
envdoctor scan --dir . runs every detector and prints the audit above. Exits 0 when clean, 1 when errors are found.
Tighten for CI
envdoctor scan --strict promotes warnings to errors too, so unused vars and other warnings also fail the build.
Drop it into your pipeline
Because it exits non-zero on errors, a single line in CI blocks any merge that introduces environment drift — no plugin, no service, no config server.
# fail the build on any drift
- name: Check environment consistency
run: envdoctor scan --strict
Why local-first
Environment variables are where secrets live. A checker that reads them should never be a new place they can leak.
No network
Everything runs on your machine. envdoctor makes zero outbound requests — nothing to review in a proxy log, nothing to allowlist.
No telemetry
No usage pings, no analytics, no phone-home. What you scan stays entirely with you.
Values never printed
Reports reference variables by name and location only. Secret values are never printed to the terminal or written into generated files.
CI-friendly by default
A clean exit code contract — 0 for pass, 1 for errors — means it drops into any pipeline without a wrapper.