envsentinel is a contract-driven CLI that prevents .env drift across local development, CI, and production deployments.
Teams lose time and reliability when environment variables are undocumented, silently renamed, or incorrectly typed. Typical failure modes:
- New teammate copies an old
.envand misses required variables. - CI passes while production crashes because a value is malformed.
.env.examplegoes stale and stops reflecting real requirements.
EnvSentinel treats environment configuration as a versioned contract:
- Validate one or more
.envfiles against a JSON schema contract. - Fail fast in CI with clear, actionable diagnostics.
- Generate
.env.exampledirectly from the contract to avoid drift. - Bootstrap a starter contract from an existing
.env.
envsentinel init: scaffold a contract from an existing.envenvsentinel check: validate.envfiles against that contract (--json,--junit,--env-glob,--env-dir,--exclude-env-glob)envsentinel example: generate.env.examplefrom the contract
From PyPI (after first release):
pipx install envsentinelOr:
python -m pip install envsentinelFrom source:
python -m pip install .For editable development:
python -m pip install -e .- Create a contract from your current
.env:
envsentinel init --from-env .env --schema envsentinel.json-
Refine
envsentinel.jsonrules (required, type, pattern, choices, bounds). -
Validate in local dev and CI:
envsentinel check --schema envsentinel.json --env .envCI-friendly JUnit report:
envsentinel check --schema envsentinel.json --env .env --junit reports/envsentinel.junit.xmlValidate a set of env files with glob patterns:
envsentinel check --schema envsentinel.json --env-glob ".env.*" --exclude-env-glob ".env.example"Recursively discover env files inside directories:
envsentinel check --schema envsentinel.json --env-dir services --env-dir apps- Regenerate
.env.examplefrom the contract:
envsentinel example --schema envsentinel.json --output .env.example --forceContract file is JSON:
{
"version": 1,
"allow_unknown": false,
"variables": {
"APP_ENV": {
"required": true,
"type": "string",
"choices": ["dev", "stage", "prod"],
"description": "Deployment environment"
},
"PORT": {
"required": true,
"type": "int",
"min": 1,
"max": 65535,
"default": 8000
},
"DATABASE_URL": {
"required": true,
"type": "url",
"sensitive": true
}
}
}Supported field semantics:
required(bool): if missing and no default, validation fails.type(string|int|float|bool|url|email): value type constraint.choices(list[str]): must match one of the listed values.pattern(regex): full-match regex constraint.min,max(number):- numeric bounds for
intandfloat - length bounds for
string(must be integer bounds forstring)
- numeric bounds for
default: used for generated example output and documentation.sensitive(bool): when true, defaults must be empty or null; generated starter contracts never persist raw secret defaults.allow_unknown(bool): whether keys outsidevariablesare allowed.
Parser guarantees:
- Duplicate keys in
.envare rejected with file/line diagnostics. - Invalid dotenv syntax fails fast with source line context.
Use the included GitHub Actions workflow (.github/workflows/ci.yml) as a baseline. In your project, add a validation step:
envsentinel check --schema envsentinel.json --env .envFor monorepos or environment matrices:
envsentinel check --schema envsentinel.json --env services/api/.env --env services/worker/.envOr discover files by pattern:
envsentinel check --schema envsentinel.json --env-glob "services/*/.env"Or recursively scan directories for .env and .env.* files:
envsentinel check --schema envsentinel.json --env-dir servicesEnvSentinel is most useful when it runs before bad configuration changes are committed.
- Install both tools in your project environment:
python -m pip install -e .
python -m pip install pre-commit- Copy the example config:
cp .pre-commit-config.example.yaml .pre-commit-config.yaml- Install git hooks:
pre-commit install- Validate immediately:
pre-commit run --all-filesThis repository ships a .pre-commit-hooks.yaml manifest. Once you publish tagged releases, consumers can reference it directly:
repos:
- repo: https://github.com/<your-org>/envsentinel
rev: v0.1.0
hooks:
- id: envsentinel-check
args: ["--schema", "envsentinel.json", "--env", ".env"]- Contract changes are reviewed in pull requests.
- CI runs
envsentinel checkagainst environment templates. .env.exampleis regenerated from contract and committed.- Deploy jobs validate runtime-loaded env files before rollout.
This catches configuration regressions before runtime.
Run tests:
python -m unittest discover -s tests -vInstall development tooling:
python -m pip install -e ".[dev]"This repository includes a PyPI publish workflow at .github/workflows/publish.yml.
- Create the project on PyPI.
- Configure a PyPI Trusted Publisher for this GitHub repository.
- Ensure your default branch CI is green.
- Bump version in
pyproject.toml. - Run local checks:
python -m pip install -e ".[dev]"
python -m unittest discover -s tests -v
python -m build
python -m twine check dist/*- Commit, tag, and push:
git tag v0.1.0
git push origin v0.1.0Pushing a v* tag triggers the publish workflow and uploads dist/* to PyPI.
- Zero external runtime dependencies: keeps install friction low.
- Typed rule model: schema validation catches contract mistakes early.
- Human + JSON output modes: supports both local debugging and machine parsing.
- Parser strictness: invalid dotenv syntax fails fast with file/line context.
- YAML contract support.
- Optional secret-manager backends for default resolution.
- Contract drift check against deployed environments.
MIT
See CONTRIBUTING.md.
Bug reports and PRs welcome — please open an issue first for large changes.