Local environment control plane for contract-driven development workflows
Project description
Your .env.local works... until it doesn't.
Different machines behave differently.
Onboarding breaks.
CI fails in ways you can't reproduce.
envctl stops that drift.
envctl keeps environments consistent.
What envctl does
envctl keeps environments consistent.
It gives you:
- shared requirements in the repo
- local values outside Git
- explicit runtime behavior instead of guesswork
It is not a secret manager. It is not a dotenv loader. It is not a shell trick.
It is a local-first way to stop .env drift across development, teammates, and CI.
The problem
You have probably seen some version of this already:
- it works on your machine, but not on your teammate's
- CI fails because a variable is missing or shaped differently
- onboarding depends on tribal knowledge
.env.localgets copied around until nobody trusts it- nobody can clearly say which variables are actually required
That is not just a secret-storage problem.
It is an environment-consistency problem.
How envctl fixes it
envctl makes environments explicit instead of implicit.
It separates the environment into clear responsibilities:
- contract: what the project requires
- vault: what each machine stores locally
- profiles: which local value set is active
- resolution: what is actually true at runtime
- projection: how that resolved environment is handed to tools
That means:
- the repo defines shared requirements
- each machine keeps real values local
- the runtime environment is explicit and checkable
No hidden source of truth.
No guessing which value won.
Quickstart
Install the CLI:
python3 -m pip install envctl
Then the shortest useful flow is:
envctl config init
envctl init
envctl fill
envctl check
envctl run -- python app.py
What happens:
config initcreates your user-levelenvctlconfiginitprepares the repository forenvctland attempts to install managed Git hooksfillasks only for missing required valuescheckvalidates the resolved environmentrunexecutes with the resolved environment injected directly
If another tool really needs a file on disk, use sync.
Otherwise, run is usually the cleanest path.
Local Git protection
envctl can keep its own secret guard wired into Git without becoming a generic hooks manager.
The managed workflow is:
envctl hooks status
envctl hooks install
envctl hooks repair
envctl hooks remove
Those commands manage only envctl's own pre-commit and pre-push wrappers, both of which run envctl guard secrets.
Why it is different
envctl is not mainly competing with cloud secret tools or dotenv loaders.
Its primary job is different:
- cloud secret tools focus on secret distribution
- dotenv loaders and shell tooling focus on injection
envctlfocuses on environment coherence
That is why the core value is not “where do secrets live?”.
The core value is:
- what does this project require?
- what does this machine actually have?
- what environment will the app really receive?
A typical workflow
# add a new shared requirement
envctl add API_KEY sk-example
git add .envctl.yaml
git commit -m "require API_KEY"
# another developer pulls
envctl check
envctl fill
envctl run -- python app.py
The contract changes in Git. Real values stay local. The runtime environment stays explicit.
When envctl is a good fit
- your
.env.localkeeps drifting - onboarding is fragile
- local and CI behavior diverge too easily
- one machine needs multiple local contexts
- you want a local-first workflow without turning generated files into the source of truth
When it is probably overkill
- you have one tiny project with a static env file
- onboarding is trivial and unlikely to change
- the team already solves environment consistency elsewhere and does not need another layer
Security
- no secrets in the contract
- local values stay on the machine
- sensitive output is masked
- encryption at rest is optional
- managed Git hooks can run
guard secretsautomatically before commit and push
envctl assumes the local machine is trusted. It is designed to keep environment handling explicit and safer, not to replace a full remote secrets platform.
Documentation
- Docs home
- Getting started
- Quickstart
- Concepts
- Commands reference
- Configuration reference
- Observability reference
- Distribution reference
- Troubleshooting
- Compatibility
Development
The repository uses uv for dependency management and reproducible environments.
uv sync --dev
make validate
This ensures that local development and CI use the same locked dependency graph defined in uv.lock.
If you are editing documentation locally:
uv sync --extra docs
make docs-check
The validation flow includes linting, formatting, type checking, security checks, tests with coverage, and architectural constraints.
See CONTRIBUTING.md for details.
If you have ever said:
"it works on my machine"
then envctl is probably solving a problem you already have.
Project details
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 envctl-2.5.1.tar.gz.
File metadata
- Download URL: envctl-2.5.1.tar.gz
- Upload date:
- Size: 4.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
377cd5d796f93ed9259efa848e570844fee06b34d5ca5c87986936ea668fbc8e
|
|
| MD5 |
dc5d9128ec994689150775c78d24f61b
|
|
| BLAKE2b-256 |
6b7e3859a1b67db2b499e911de5b16279baef0ac4bcd46d67a64f0a797835e8f
|
Provenance
The following attestation bundles were made for envctl-2.5.1.tar.gz:
Publisher:
release.yml on labrynx/envctl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
envctl-2.5.1.tar.gz -
Subject digest:
377cd5d796f93ed9259efa848e570844fee06b34d5ca5c87986936ea668fbc8e - Sigstore transparency entry: 1294011677
- Sigstore integration time:
-
Permalink:
labrynx/envctl@9147b85cbc9132bfd1f82d9b1ae6486188ca8904 -
Branch / Tag:
refs/tags/v2.5.1 - Owner: https://github.com/labrynx
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9147b85cbc9132bfd1f82d9b1ae6486188ca8904 -
Trigger Event:
push
-
Statement type:
File details
Details for the file envctl-2.5.1-py3-none-any.whl.
File metadata
- Download URL: envctl-2.5.1-py3-none-any.whl
- Upload date:
- Size: 195.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
12ad96ba37408174fa6b93423f5730fb3bc9acd398ae558eaa8a48f3279b7dc7
|
|
| MD5 |
a0e36dcdfc6e02303d2b99b8171e85b5
|
|
| BLAKE2b-256 |
3652bc048820ef083c5453ce96bc38f3b9146d8cde073c4c1dd939085da70028
|
Provenance
The following attestation bundles were made for envctl-2.5.1-py3-none-any.whl:
Publisher:
release.yml on labrynx/envctl
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
envctl-2.5.1-py3-none-any.whl -
Subject digest:
12ad96ba37408174fa6b93423f5730fb3bc9acd398ae558eaa8a48f3279b7dc7 - Sigstore transparency entry: 1294011750
- Sigstore integration time:
-
Permalink:
labrynx/envctl@9147b85cbc9132bfd1f82d9b1ae6486188ca8904 -
Branch / Tag:
refs/tags/v2.5.1 - Owner: https://github.com/labrynx
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9147b85cbc9132bfd1f82d9b1ae6486188ca8904 -
Trigger Event:
push
-
Statement type: