https://github.com/labrynx/envctl
Your .env files, local-first and under your control.
https://github.com/labrynx/envctl
cli config-management configuration developer-tools devops dotenv env environment environment-management local-first python symlink typer
Last synced: 6 days ago
JSON representation
Your .env files, local-first and under your control.
- Host: GitHub
- URL: https://github.com/labrynx/envctl
- Owner: labrynx
- License: mit
- Created: 2026-03-27T23:36:27.000Z (15 days ago)
- Default Branch: main
- Last Pushed: 2026-04-02T20:44:40.000Z (9 days ago)
- Last Synced: 2026-04-03T03:11:00.796Z (9 days ago)
- Topics: cli, config-management, configuration, developer-tools, devops, dotenv, env, environment, environment-management, local-first, python, symlink, typer
- Language: Python
- Homepage: https://labrynx.github.io/envctl/
- Size: 673 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# envctl
**Your `.env.local` files drift between machines, hide missing variables, and break when you least expect it. envctl fixes that.**
[](https://github.com/labrynx/envctl/actions/workflows/ci.yml)
[](https://www.python.org/downloads/)
[](https://github.com/labrynx/envctl/blob/main/LICENSE)
---
## Why this exists
Most projects handle environment variables in a messy way:
- `.env.local` files are undocumented
- values get copied between machines
- something works locally until it suddenly does not
- CI and local setups behave differently
- nobody is fully sure which variables are required
It works — until it breaks.
`envctl` brings structure to this without turning environment setup into a second project.
---
## What is envctl?
`envctl` is a local-first environment control plane built around a **contract-first model**.
It separates three things that usually get mixed together:
- **what the project needs** → defined in `.envctl.schema.yaml` and committed to the repository
- **what you have locally** → stored in a private local vault, outside git
- **what actually runs** → a validated environment resolved when needed
That gives you:
- clear, documented variables
- no secrets in git
- fewer setup mistakes
- more predictable local and team workflows
- explicit validation before execution
---
## Install
```bash
pip install envctl
````
Or from source:
```bash
git clone https://github.com/labrynx/envctl
cd envctl
pip install -e .
```
---
## Quickstart
```bash
envctl config init
envctl init
envctl fill
envctl check
envctl run -- python app.py
```
What this does:
* `config init` creates your local envctl config
* `init` connects the current repository to envctl
* `fill` asks only for missing values
* `check` validates the environment before you run anything
* `run` injects a clean resolved environment into the child process
---
## Why not just `.env.local`?
Because it does not scale cleanly.
| | `.env.local` | direnv | Doppler / Infisical | **envctl** |
| ------------------------------ | ------------ | ------------ | ------------------- | ---------- |
| Documents variables | ❌ | ❌ | Partial | ✅ |
| Validates values | ❌ | ❌ | ❌ | ✅ |
| Keeps secrets out of git | ⚠️ | ✅ | ✅ cloud | ✅ local |
| Supports multiple environments | manual files | manual files | ✅ | ✅ profiles |
| Works without cloud | ✅ | ✅ | ❌ | ✅ |
`envctl` is **not** a cloud secrets manager.
It is a way to make environment handling explicit, predictable, and local-first.
> your repository defines what is needed, your machine provides the values, and envctl resolves the final environment.
---
## A typical workflow
```bash
# one developer adds a new requirement
envctl add API_KEY sk-example
git add .envctl.schema.yaml
git commit -m "require API_KEY"
# another developer pulls the change
envctl check
envctl fill
envctl run -- python app.py
```
The contract is shared in git.
The values stay local.
The runtime environment is rebuilt consistently when needed.
---
## How it works
At a high level:
* **contract** → defines which variables exist and how they should look
* **vault** → stores the real local values
* **profile** → selects which local value set to use (`local`, `dev`, `staging`, ...)
* **resolution** → builds the final validated environment
* **projection** → applies it through `run`, `sync`, or `export`
Think of it like this:
> the repository defines the rules, your machine provides the values, and envctl builds the environment you actually run.
---
## Profiles
Instead of juggling multiple `.env` files:
```bash
envctl --profile dev fill
envctl --profile staging check
envctl --profile staging run -- python app.py
```
Each profile is explicit and independent.
No hidden inheritance, no magic fallback between profiles.
---
## Docker note
```bash
envctl run -- docker run ...
```
`envctl` injects variables into the **Docker client process**.
To pass them into the container, you still need one of these:
* `-e`
* `--env`
* `--env-file`
A common pattern is:
```bash
docker run --env-file <(envctl export --format dotenv) ...
```
---
## CI mode
```bash
ENVCTL_RUNTIME_MODE=ci envctl check
```
In CI:
* validation still works
* mutating commands are blocked
That keeps automation predictable and avoids accidental local-style writes in CI environments.
---
## Common commands
```bash
envctl check
envctl inspect
envctl explain DATABASE_URL
envctl status
envctl doctor
envctl add DATABASE_URL
envctl set PORT 4000
envctl unset PORT
envctl run --
envctl sync
envctl export
envctl profile list
envctl profile create staging
envctl vault check
envctl vault show
envctl vault encrypt
envctl vault decrypt
```
---
## When envctl is a good fit
envctl is a strong fit if:
* `.env.local` files drift between machines
* onboarding is fragile
* CI and local environments do not behave the same way
* you work with multiple environments
* you want a local-first workflow without depending on a hosted service
---
## When envctl is not the right tool
envctl may be unnecessary if:
* you only have one static `.env` file
* the project is very small and has no real setup complexity
* you already rely fully on a centralized secrets platform and do not want local-first handling
---
## Security model
* the contract contains **no secrets**
* secrets stay on your machine
* sensitive values are masked in normal output
* vault files use restrictive permissions
* optional encryption at rest is available for vault files
### Vault encryption at rest
If you enable encryption, envctl stores vault files in an encrypted, self-identifying format instead of plaintext.
Enable it in your config:
```json
{
"encryption": {
"enabled": true
}
}
```
Then migrate existing vault files once:
```bash
envctl vault encrypt
```
This creates a local key file at:
```text
~/.envctl/vault/master.key
```
That key is stored with restrictive permissions.
After encryption is enabled:
* `vault edit` works transparently
* `vault check` reports whether the file is plaintext, encrypted, using the wrong key, or corrupted
* decrypt failures are explicit instead of looking like generic parse errors
To migrate back to plaintext:
```bash
envctl vault decrypt
```
Then disable encryption in config.
### Important limitation
Encryption at rest helps protect vault files on disk.
It does **not** protect against a fully compromised machine or a compromised user session.
> envctl assumes a trusted machine.
> If your machine is compromised, your secrets are compromised too.
Back up your `master.key` carefully.
If you lose it, encrypted vault data cannot be recovered.
---
## Documentation
* [Quickstart](https://github.com/labrynx/envctl/blob/main/docs/getting-started/quickstart.md)
* [Mental model](https://github.com/labrynx/envctl/blob/main/docs/getting-started/mental-model.md)
* [Commands reference](https://github.com/labrynx/envctl/blob/main/docs/reference/commands.md)
* [Profiles reference](https://github.com/labrynx/envctl/blob/main/docs/reference/profiles.md)
* [Vault reference](https://github.com/labrynx/envctl/blob/main/docs/reference/vault.md)
* [Encryption reference](https://github.com/labrynx/envctl/blob/main/docs/reference/encryption.md)
* [Config reference](https://github.com/labrynx/envctl/blob/main/docs/reference/config.md)
* [CI workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/ci.md)
* [Team workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/team.md)
* [Security](https://github.com/labrynx/envctl/blob/main/docs/reference/security.md)
* [Internal architecture](https://github.com/labrynx/envctl/blob/main/docs/internals/architecture.md)