{"id":47697157,"url":"https://github.com/labrynx/envctl","last_synced_at":"2026-04-05T20:01:46.841Z","repository":{"id":347531135,"uuid":"1194106323","full_name":"labrynx/envctl","owner":"labrynx","description":"Your .env files, local-first and under your control.","archived":false,"fork":false,"pushed_at":"2026-04-02T20:44:40.000Z","size":689,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-03T03:11:00.796Z","etag":null,"topics":["cli","config-management","configuration","developer-tools","devops","dotenv","env","environment","environment-management","local-first","python","symlink","typer"],"latest_commit_sha":null,"homepage":"https://labrynx.github.io/envctl/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/labrynx.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-27T23:36:27.000Z","updated_at":"2026-04-02T20:44:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/labrynx/envctl","commit_stats":null,"previous_names":["alessbarb/envctl","labrynx/envctl"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/labrynx/envctl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/labrynx%2Fenvctl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/labrynx%2Fenvctl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/labrynx%2Fenvctl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/labrynx%2Fenvctl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/labrynx","download_url":"https://codeload.github.com/labrynx/envctl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/labrynx%2Fenvctl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31448216,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T15:22:31.103Z","status":"ssl_error","status_checked_at":"2026-04-05T15:22:00.205Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","config-management","configuration","developer-tools","devops","dotenv","env","environment","environment-management","local-first","python","symlink","typer"],"created_at":"2026-04-02T16:32:25.867Z","updated_at":"2026-04-05T20:01:46.834Z","avatar_url":"https://github.com/labrynx.png","language":"Python","readme":"# envctl\n\n**Your `.env.local` files drift between machines, hide missing variables, and break when you least expect it. envctl fixes that.**\n\n[![CI](https://github.com/labrynx/envctl/actions/workflows/ci.yml/badge.svg)](https://github.com/labrynx/envctl/actions/workflows/ci.yml)\n[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/labrynx/envctl/blob/main/LICENSE)\n\n---\n\n## Why this exists\n\nMost projects handle environment variables in a messy way:\n\n- `.env.local` files are undocumented\n- values get copied between machines\n- something works locally until it suddenly does not\n- CI and local setups behave differently\n- nobody is fully sure which variables are required\n\nIt works — until it breaks.\n\n`envctl` brings structure to this without turning environment setup into a second project.\n\n---\n\n## What is envctl?\n\n`envctl` is a local-first environment control plane built around a **contract-first model**.\n\nIt separates three things that usually get mixed together:\n\n- **what the project needs** → defined in `.envctl.schema.yaml` and committed to the repository\n- **what you have locally** → stored in a private local vault, outside git\n- **what actually runs** → a validated environment resolved when needed\n\nThat gives you:\n\n- clear, documented variables\n- no secrets in git\n- fewer setup mistakes\n- more predictable local and team workflows\n- explicit validation before execution\n\n---\n\n## Install\n\n```bash\npip install envctl\n````\n\nOr from source:\n\n```bash\ngit clone https://github.com/labrynx/envctl\ncd envctl\npip install -e .\n```\n\n---\n\n## Quickstart\n\n```bash\nenvctl config init\nenvctl init\nenvctl fill\nenvctl check\nenvctl run -- python app.py\n```\n\nWhat this does:\n\n* `config init` creates your local envctl config\n* `init` connects the current repository to envctl\n* `fill` asks only for missing values\n* `check` validates the environment before you run anything\n* `run` injects a clean resolved environment into the child process\n\n---\n\n## Why not just `.env.local`?\n\nBecause it does not scale cleanly.\n\n|                                | `.env.local` | direnv       | Doppler / Infisical | **envctl** |\n| ------------------------------ | ------------ | ------------ | ------------------- | ---------- |\n| Documents variables            | ❌            | ❌            | Partial             | ✅          |\n| Validates values               | ❌            | ❌            | ❌                   | ✅          |\n| Keeps secrets out of git       | ⚠️           | ✅            | ✅ cloud             | ✅ local    |\n| Supports multiple environments | manual files | manual files | ✅                   | ✅ profiles |\n| Works without cloud            | ✅            | ✅            | ❌                   | ✅          |\n\n`envctl` is **not** a cloud secrets manager.\n\nIt is a way to make environment handling explicit, predictable, and local-first.\n\n\u003e your repository defines what is needed, your machine provides the values, and envctl resolves the final environment.\n\n---\n\n## A typical workflow\n\n```bash\n# one developer adds a new requirement\nenvctl add API_KEY sk-example\ngit add .envctl.schema.yaml\ngit commit -m \"require API_KEY\"\n\n# another developer pulls the change\nenvctl check\nenvctl fill\nenvctl run -- python app.py\n```\n\nThe contract is shared in git.\nThe values stay local.\nThe runtime environment is rebuilt consistently when needed.\n\n---\n\n## How it works\n\nAt a high level:\n\n* **contract** → defines which variables exist and how they should look\n* **vault** → stores the real local values\n* **profile** → selects which local value set to use (`local`, `dev`, `staging`, ...)\n* **resolution** → builds the final validated environment\n* **projection** → applies it through `run`, `sync`, or `export`\n\nThink of it like this:\n\n\u003e the repository defines the rules, your machine provides the values, and envctl builds the environment you actually run.\n\n---\n\n## Profiles\n\nInstead of juggling multiple `.env` files:\n\n```bash\nenvctl --profile dev fill\nenvctl --profile staging check\nenvctl --profile staging run -- python app.py\n```\n\nEach profile is explicit and independent.\nNo hidden inheritance, no magic fallback between profiles.\n\n---\n\n## Docker note\n\n```bash\nenvctl run -- docker run ...\n```\n\n`envctl` injects variables into the **Docker client process**.\n\nTo pass them into the container, you still need one of these:\n\n* `-e`\n* `--env`\n* `--env-file`\n\nA common pattern is:\n\n```bash\ndocker run --env-file \u003c(envctl export --format dotenv) ...\n```\n\n---\n\n## CI mode\n\n```bash\nENVCTL_RUNTIME_MODE=ci envctl check\n```\n\nIn CI:\n\n* validation still works\n* mutating commands are blocked\n\nThat keeps automation predictable and avoids accidental local-style writes in CI environments.\n\n---\n\n## Common commands\n\n```bash\nenvctl check\nenvctl inspect\nenvctl explain DATABASE_URL\nenvctl status\nenvctl doctor\n\nenvctl add DATABASE_URL \u003cvalue\u003e\nenvctl set PORT 4000\nenvctl unset PORT\n\nenvctl run -- \u003ccommand\u003e\nenvctl sync\nenvctl export\n\nenvctl profile list\nenvctl profile create staging\n\nenvctl vault check\nenvctl vault show\nenvctl vault encrypt\nenvctl vault decrypt\n```\n\n---\n\n## When envctl is a good fit\n\nenvctl is a strong fit if:\n\n* `.env.local` files drift between machines\n* onboarding is fragile\n* CI and local environments do not behave the same way\n* you work with multiple environments\n* you want a local-first workflow without depending on a hosted service\n\n---\n\n## When envctl is not the right tool\n\nenvctl may be unnecessary if:\n\n* you only have one static `.env` file\n* the project is very small and has no real setup complexity\n* you already rely fully on a centralized secrets platform and do not want local-first handling\n\n---\n\n## Security model\n\n* the contract contains **no secrets**\n* secrets stay on your machine\n* sensitive values are masked in normal output\n* vault files use restrictive permissions\n* optional encryption at rest is available for vault files\n\n### Vault encryption at rest\n\nIf you enable encryption, envctl stores vault files in an encrypted, self-identifying format instead of plaintext.\n\nEnable it in your config:\n\n```json\n{\n  \"encryption\": {\n    \"enabled\": true\n  }\n}\n```\n\nThen migrate existing vault files once:\n\n```bash\nenvctl vault encrypt\n```\n\nThis creates a local key file at:\n\n```text\n~/.envctl/vault/master.key\n```\n\nThat key is stored with restrictive permissions.\n\nAfter encryption is enabled:\n\n* `vault edit` works transparently\n* `vault check` reports whether the file is plaintext, encrypted, using the wrong key, or corrupted\n* decrypt failures are explicit instead of looking like generic parse errors\n\nTo migrate back to plaintext:\n\n```bash\nenvctl vault decrypt\n```\n\nThen disable encryption in config.\n\n### Important limitation\n\nEncryption at rest helps protect vault files on disk.\n\nIt does **not** protect against a fully compromised machine or a compromised user session.\n\n\u003e envctl assumes a trusted machine.\n\u003e If your machine is compromised, your secrets are compromised too.\n\nBack up your `master.key` carefully.\nIf you lose it, encrypted vault data cannot be recovered.\n\n---\n\n## Documentation\n\n* [Quickstart](https://github.com/labrynx/envctl/blob/main/docs/getting-started/quickstart.md)\n* [Mental model](https://github.com/labrynx/envctl/blob/main/docs/getting-started/mental-model.md)\n* [Commands reference](https://github.com/labrynx/envctl/blob/main/docs/reference/commands.md)\n* [Profiles reference](https://github.com/labrynx/envctl/blob/main/docs/reference/profiles.md)\n* [Vault reference](https://github.com/labrynx/envctl/blob/main/docs/reference/vault.md)\n* [Encryption reference](https://github.com/labrynx/envctl/blob/main/docs/reference/encryption.md)\n* [Config reference](https://github.com/labrynx/envctl/blob/main/docs/reference/config.md)\n* [CI workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/ci.md)\n* [Team workflow](https://github.com/labrynx/envctl/blob/main/docs/workflows/team.md)\n* [Security](https://github.com/labrynx/envctl/blob/main/docs/reference/security.md)\n* [Internal architecture](https://github.com/labrynx/envctl/blob/main/docs/internals/architecture.md)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flabrynx%2Fenvctl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flabrynx%2Fenvctl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flabrynx%2Fenvctl/lists"}