Skip to main content

Local environment control plane for contract-driven development workflows

Project description

envctl

Your .env.local files are undocumented, unvalidated, and drift between machines. envctl fixes that.

CI Python 3.10+ License: MIT


What is this?

Most projects handle .env files like this:

  • variables are not documented
  • values get copied between machines
  • something works locally… but breaks somewhere else

envctl gives you a simple structure to fix that.

It separates three things that usually get mixed together:

  • what the project needs → defined in .envctl.schema.yaml (committed to the repo)
  • what you have locally → stored in a private vault (never in git)
  • what actually runs → a validated environment, built on demand

So you get:

  • no secrets in git
  • no undocumented variables
  • no copy-pasting .env files

Install

pip install envctl

Or from source:

git clone https://github.com/labrynx/envctl
cd envctl
pip install -e .

Quickstart

envctl config init      # create your local config
envctl init             # initialize this repository
envctl fill             # set missing values (interactive)
envctl check            # validate against the contract
envctl run -- python app.py  # run with env injected

Why not just .env.local?

Because it doesn’t scale well.

.env.local direnv Doppler / Infisical envctl
Documents what variables exist Partial ✅ contract
Type validation
Values stay off git ⚠️ easy to slip ✅ cloud ✅ local vault
Multiple environments manual files manual files ✅ profiles
No cloud account required
Works in CI without mutation ENVCTL_RUNTIME_MODE=ci

envctl is not a secrets manager.

It’s a local control plane for your project’s environment:

the contract says what’s needed, your machine provides values, and envctl makes them work together.


How it works

There are five pieces, but the idea is simple:

  • contract → defines what variables exist and their rules
  • vault → stores your real values locally
  • profile → selects a set of values (local, dev, staging, …)
  • resolution → combines everything in a deterministic way
  • projection → makes it usable (run, sync, export)

Think of it like this:

the repo defines the rules, your machine provides the data, and envctl builds the final environment.

Resolution now includes placeholder expansion as part of the runtime model, so check, inspect, run, sync, and export all see the same final value.


Example contract

# .envctl.schema.yaml — commit this
version: 1
variables:
  DATABASE_URL:
    type: url
    required: true
    sensitive: true
    description: Primary database connection URL
  PORT:
    type: int
    required: true
    default: 3000
    sensitive: false
  DEBUG:
    type: bool
    required: false
    default: false
    sensitive: false
  TEST_JSON:
    type: string
    format: json
    required: false
    sensitive: false

This file describes what exists. It never contains real values.


Variable expansion

envctl supports explicit placeholder expansion with ${VAR} during resolution.

That means the expansion happens before projection, so the effective expanded value is what:

  • inspect shows
  • check validates
  • run injects
  • sync writes
  • export prints

Example:

INFRA_NEO4J_USER=neo4j
INFRA_NEO4J_PASSWORD=super-secret
INFRA_NEO4J_AUTH=${INFRA_NEO4J_USER}/${INFRA_NEO4J_PASSWORD}

INFRA_NEO4J_AUTH resolves to the final runtime value, not the literal expression.

Rules:

  • only ${VAR} is supported in v1
  • $VAR stays literal
  • if VAR is a declared envctl key, envctl resolves that key first
  • otherwise envctl falls back to the current process environment
  • ${HOME} works when HOME exists in the current process environment
  • malformed placeholders or unresolved references make resolution invalid

Compatibility notes:

  • before this feature, ${HOME} stayed literal
  • now ${HOME} is expanded during resolution
  • ${...} literal escaping is not supported in v1

Profiles

Instead of juggling multiple .env files:

# set up dev once
envctl --profile dev fill

# validate staging
envctl --profile staging check

# run with staging values
envctl --profile staging run -- python app.py

Profile selection priority:

  1. --profile
  2. ENVCTL_PROFILE
  3. config default
  4. local

Each profile is independent. No hidden inheritance.


Team workflow

The idea is simple:

  • the contract is shared
  • the values are local
# developer A
envctl add API_KEY sk-abc123
git add .envctl.schema.yaml
git commit -m "require API_KEY"

# developer B
git pull
envctl check   # shows what's missing
envctl fill    # only asks for missing values

No more guessing what goes into .env.


CI workflow

ENVCTL_RUNTIME_MODE=ci envctl check

In CI mode:

  • validation works
  • mutations are blocked (add, set, fill, etc.)

You can also combine it with profiles:

ENVCTL_PROFILE=ci ENVCTL_RUNTIME_MODE=ci envctl check

Common commands

# validation and visibility
envctl check
envctl inspect
envctl explain DATABASE_URL
envctl status
envctl doctor

# values
envctl add DATABASE_URL <value>
envctl add TEST_JSON '{"key":"value"}' --type string --format json
envctl set PORT 4000
envctl unset PORT
envctl remove PORT

# run / output
envctl run -- <command>
envctl sync
envctl export

# profiles
envctl profile list
envctl profile create staging
envctl profile copy local staging
envctl profile remove staging --yes

# vault
envctl vault show
envctl vault check
envctl vault path
envctl vault prune

# project identity
envctl project bind <id>
envctl project rebind
envctl project repair

Machine-readable output

All read commands support --json:

envctl --json check
envctl --json status
envctl --json inspect
envctl --json doctor

Structured string validation

If a variable is a string but carries structured content, declare that semantic format in the contract:

variables:
  TEST_JSON:
    type: string
    format: json

Supported format values for type: string:

  • json
  • url
  • csv

When format is declared, check, inspect, and runtime resolution validate payload semantics, not only raw string presence.


Design principles

  • Contract-first: the repo defines requirements
  • Deterministic: same inputs → same result
  • Explicit: nothing happens automatically
  • Local-first: no required cloud
  • Generated files are disposable
  • Profiles are value namespaces, not variants
  • CI mode is policy, not a profile

Security model

  • The contract contains no secrets
  • Secrets stay on your machine
  • .env.local is optional and disposable
  • Sensitive values are masked in output
  • Read-only commands never change state
  • Vault files use restrictive permissions (0600)

Important:

envctl assumes a trusted machine. If your machine is compromised, your secrets are compromised.

It’s not a replacement for a team-wide secrets manager.


Documentation

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

envctl-2.3.3.tar.gz (65.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

envctl-2.3.3-py3-none-any.whl (102.9 kB view details)

Uploaded Python 3

File details

Details for the file envctl-2.3.3.tar.gz.

File metadata

  • Download URL: envctl-2.3.3.tar.gz
  • Upload date:
  • Size: 65.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for envctl-2.3.3.tar.gz
Algorithm Hash digest
SHA256 a595d0ab57702cbfbfa71417bbad83abf4719a985b9c0842585881306d70f073
MD5 e0631a92c9dd6582bbcc2118f5307858
BLAKE2b-256 f1d096738485fa6186013d011f2b41345f5c8ff1fba0af9a557d9e32e20da7f4

See more details on using hashes here.

File details

Details for the file envctl-2.3.3-py3-none-any.whl.

File metadata

  • Download URL: envctl-2.3.3-py3-none-any.whl
  • Upload date:
  • Size: 102.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for envctl-2.3.3-py3-none-any.whl
Algorithm Hash digest
SHA256 3155be13b6a3369f2f91ea99b5b9a8f986529b1e2e1bf95bb69ee22556a5d302
MD5 057742802971513f1122f3ddca3cca95
BLAKE2b-256 dea7cfe36101b384a6ee5666e3f6be7942d1b10c55796fc7d40de5d5beeaca15

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page