The contract for your .env — validate it, catch team drift, and never commit a secret. 100% local.
Project description
envcontract
The contract for your .env. Validate it, catch team drift, and never commit a secret — 100% local, your values never leave your machine.
Table of contents
- What is this?
- The problem it solves
- Install
- 60-second quick start
- The four commands
- The schema file explained
- Using it in CI
- Using it as a pre-commit hook
- How it works & the privacy promise
- FAQ
- Troubleshooting
- Contributing
- License
What is this?
Most apps read their settings — database URLs, ports, API keys — from a file called .env.
That file is handy but fragile: it holds secrets, and everyone on the team keeps their own copy.
envcontract is a tiny command-line tool that treats your .env like it has a contract:
a committed file (.env.schema) that lists which variables are required and what each should look like —
without storing any secret values. It then helps you:
- generate that contract automatically,
- check any
.envagainst it, - spot when your
.envhas fallen out of sync with the team, and - block secrets from being committed to git by mistake.
It runs entirely on your computer. No account, no server, no telemetry.
The problem it solves
If you've worked on a team, you've hit these:
- "Works on my machine." A teammate adds a new variable but forgets to tell anyone. Everyone else's app breaks with a confusing error.
- Silent typos. Someone sets
PORT=eightyinstead ofPORT=8080, and the app crashes deep in startup with no clear reason. - Leaked secrets. Someone accidentally commits their real
.envto GitHub, exposing live credentials. This is a genuine security incident and happens constantly. - Stale
.env.example. The old habit of keeping a.env.examplefile drifts out of date and was never a real, enforceable spec.
envcontract turns the informal "ask a teammate what goes in your .env" into a checked, committed contract.
Install
Requires Python 3.10 or newer.
pip install envcontract
Or, to keep it isolated from your other Python packages (recommended for command-line tools):
pipx install envcontract
Verify it installed:
envcontract --help
60-second quick start
From inside a project that already has a .env file:
# 1. Create the contract from your existing .env (secret VALUES are stripped out)
envcontract init
# 2. Commit the contract so your whole team shares one source of truth
git add .env.schema && git commit -m "Add env contract"
# 3. Anytime, check that a .env is valid
envcontract check
# 4. See what your local .env is missing compared to the contract
envcontract diff
That's the whole core loop. Everything below is detail.
The four commands
envcontract init
Looks at your .env and writes a .env.schema file. It infers a type for each variable,
flags likely secrets (names containing KEY, TOKEN, SECRET, PASSWORD, etc.), and removes every value.
$ envcontract init
+ Wrote .env.schema with 6 variable(s). No values were copied.
Given this .env:
DATABASE_URL=postgres://localhost/app
PORT=8080
STRIPE_SECRET_KEY=sk_live_abc123
it produces this safe-to-commit .env.schema:
version: 1
variables:
DATABASE_URL:
type: url
required: true
PORT:
type: int
required: true
STRIPE_SECRET_KEY:
type: string
required: true
secret: true
Options: --env <path> (default .env), --schema <path> (default .env.schema), --force (overwrite an existing schema).
envcontract check
Compares a .env against the schema and reports problems: missing required variables, wrong types, failed patterns, out-of-range numbers, and values not declared in the schema.
$ envcontract check
┏━━━┳━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃ Variable ┃ Line ┃ Issue ┃
┡━━━╇━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ ✗ │ DATABASE_URL │ 1 │ expected a URL (scheme://...) │
│ ✗ │ PORT │ 2 │ expected an integer, got 'eighty' │
│ ✗ │ DEBUG │ - │ required but missing │
└───┴──────────────┴──────┴────────────────────────────────────────┘
3 error(s)
It exits with code 1 when there are errors and 0 when everything is valid, so it works as a gate in scripts and CI. Add --json for machine-readable output.
envcontract diff
Shows the difference between your local .env and the committed schema — the fastest way to answer "what changed that I don't have yet?"
$ envcontract diff
Missing from your .env (declared in schema):
- REDIS_URL
envcontract guard
Scans files for real values of secret variables and is meant to run as a pre-commit hook. It blocks the commit if you're about to check in a secret — and it never prints the secret value itself.
$ envcontract guard .env
X envcontract: blocked commit - real secret values detected:
- STRIPE_SECRET_KEY (.env:4)
Remove these values (or move them to a git-ignored .env) before committing.
The schema file explained
.env.schema is a small YAML file. Each variable can declare these rules:
| Field | Meaning | Example |
|---|---|---|
type |
One of string, int, float, bool, url, email, enum |
type: int |
required |
Whether the variable must be present (default true) |
required: false |
default |
A default value (its absence is then not an error) | default: "3000" |
secret |
Marks the value as sensitive; guarded against commits, never printed | secret: true |
pattern |
A regular expression the value must match | pattern: "^sk_(test|live)_" |
min / max |
For numbers: value range. For strings: length range | min: 1 / max: 65535 |
values |
The allowed values for an enum |
values: [debug, info, warn, error] |
description |
A human note about the variable | description: Postgres URL |
Full example:
version: 1
variables:
DATABASE_URL:
type: url
required: true
secret: true
description: Postgres connection string.
PORT:
type: int
default: "3000"
min: 1
max: 65535
LOG_LEVEL:
type: enum
values: [debug, info, warn, error]
default: info
STRIPE_KEY:
type: string
required: true
secret: true
pattern: "^sk_(test|live)_[A-Za-z0-9]+$"
You can hand-edit this file anytime to tighten the rules. A copy of this example ships as .env.schema.example.
Using it in CI
Make your pipeline fail if someone's environment config is invalid. GitHub Actions example:
name: env
on: [push, pull_request]
jobs:
env-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install envcontract
- run: envcontract check --json
Using it as a pre-commit hook
Option A — with the pre-commit framework
Add to your project's .pre-commit-config.yaml:
repos:
- repo: https://github.com/hamzamansoorch/envcontract
rev: v0.1.0
hooks:
- id: envcontract-guard
Then run pre-commit install once. Now every commit is checked automatically.
Option B — plain git hook (no extra tools)
From your project root:
cat > .git/hooks/pre-commit <<'HOOK'
#!/bin/sh
staged=$(git diff --cached --name-only | grep -E '(^|/)\.env($|\.)' || true)
[ -z "$staged" ] && exit 0
envcontract guard $staged
HOOK
chmod +x .git/hooks/pre-commit
Now if you try to git commit a file containing a real secret, the commit is blocked.
Tip: keep your real
.envin.gitignoreso it's never committed, and only commit.env.schema.
How it works & the privacy promise
envcontract only reads files on your machine and prints to your terminal. It makes zero network calls and collects no telemetry — there's nothing to phone home to. This is enforced by an automated test that fails the build if any network connection is attempted. Values for variables marked secret are never printed in any output, masked or otherwise.
This is intentional and is the core promise of the tool: a thing that touches your secrets must never send them anywhere.
FAQ
Does this store or upload my secrets?
No. It never reads values into anything that leaves your machine, and .env.schema contains no values at all.
Is this a secrets manager like Vault or Doppler? No. It doesn't store, encrypt, or distribute secrets. It validates structure and guards commits. Use it alongside whatever secret storage you already have.
Is it a generic secret scanner like gitleaks?
No. guard is narrow on purpose — it checks the variables your schema marks secret, plus obvious secret-looking names. For full repo-wide scanning, keep using gitleaks; envcontract complements it.
Does it work with any language/framework?
Yes. It reads plain .env files, so it works for Node, Python, Go, Ruby, PHP — anything that uses .env.
Will it change my .env?
Never. It only reads your .env. The only file it writes is .env.schema (via init).
Troubleshooting
envcontract: command not found— your Python scripts folder isn't on PATH. Trypython -m envcontract.cli --help, or reinstall withpipx install envcontract.schema file not found— runenvcontract initfirst to create.env.schema, or pass--schema <path>.env file not found— run the command from your project folder, or pass--env <path>.
Contributing
Contributions are welcome! Please keep the two core principles intact: zero network and never print a secret value. See CONTRIBUTING.md for dev setup and guidelines. Bug reports and feature ideas: open an issue.
License
MIT — see LICENSE. Use it freely.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file envcontract-0.1.1.tar.gz.
File metadata
- Download URL: envcontract-0.1.1.tar.gz
- Upload date:
- Size: 20.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b3448c4a204d4e2a5b6c4a1e6f52f3ed4ca9944a8eae8bd1964eb3eeccaf8e88
|
|
| MD5 |
e18ae0e002429f7fae4b5e3004aeed89
|
|
| BLAKE2b-256 |
2673426a4cf2dc5be4c2e261056ff5cd3f4a9ce2cc7808b4f6a71ed1c2bad15b
|
File details
Details for the file envcontract-0.1.1-py3-none-any.whl.
File metadata
- Download URL: envcontract-0.1.1-py3-none-any.whl
- Upload date:
- Size: 17.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b40524c46146f4979d063f612bd25101fb2ae2b7e7ca6c6ced5e90f045e978f
|
|
| MD5 |
1e8f6596f17795a4289eccdf1eca725b
|
|
| BLAKE2b-256 |
a3fbfc34198d9afb98eb2dbce68c2f791cda3bf12ff60c6e66d31800faf790b6
|