mkenv documentation
mkenv runs your command with the right environment: one typed JSONC document declares your variables, and secrets decrypt on your machine — never on the server.
Early access — commands and formats below are implemented; hosted sign-in is rolling out.
#Install
The CLI is built on Bun and published to npm under the @mkenv scope.
$ bun add -g @mkenv/cli
# package publishing is part of the launch rollout — until then, run from source (github.com/mkenv)
#Quickstart
Create mkenv.jsonc in your project root, then put mkenv in front of any command.
// mkenv.jsonc
{
"variables": {
"PORT": 3000,
"APP_NAME": "demo",
},
}
$ mkenv bun start
$ mkenv terraform plan
$ mkenv ./scripts/deploy.sh
mkenv resolves the document, injects the variables into the child process environment, runs your command, and exits with the command's exit code. If resolution fails for any reason, the command never runs.
#The env document
mkenv.jsonc is JSONC — JSON with comments and trailing commas — validated against a published JSON Schema before anything runs. mkenv finds it in the nearest ancestor directory, so it works from anywhere inside your project.
| field | meaning |
|---|---|
variables | Required. The environment variables to inject. Values may be any JSON value; non-strings serialize to canonical JSON text ({"beta":true}) at injection time. |
extends | Ordered parent document paths, relative to this document. See layering. |
tenant | The team (tenant) id that owns any referenced secrets. Read from the entry document only. |
$schema | Optional editor hint: https://mkenv.net/schemas/environment-document.v1.json |
#Layering with extends
Documents compose by ordered layering: parents apply first in declared order, the declaring document last. Each layer is an RFC 7396 merge patch — objects merge key by key, arrays replace wholesale, and null deletes a variable inherited from any earlier layer.
// production.jsonc — a patch on base, not a copy of it
{
"extends": ["./base.jsonc"],
"variables": {
"LOG_LEVEL": "warn", // overrides base
"DEBUG_PORT": null, // deletes base's DEBUG_PORT
},
}
Cycles in the extends chain are rejected with the cycle named.
#Secret references
A variable whose value is exactly {"$secret": "NAME"} is a secret reference. At run time, mkenv fetches the encrypted value, decrypts it locally, and injects the plaintext — which exists only in the process's memory.
{
"tenant": "acme",
"variables": {
"DATABASE_URL": { "$secret": "DATABASE_URL" },
},
}
Because a reference is plain document data, a later layer can replace it with a literal value (say, in local development) — or replace a literal with a reference in production. A document with references fails closed on a machine that isn't logged in: the reference never injects as literal JSON.
#mkenv login
mkenv login uses the device-authorization flow: the CLI prints a short code and a verification URL, you approve in a signed-in browser, and the CLI stores an access token in ~/.config/mkenv/.
On first login, the CLI also generates this machine's key pair. The private key never leaves your machine — it is what lets this machine (and only this machine) unwrap your team's data key.
#Managing secrets
# once per tenant: generate the data key locally, store only a wrapped copy
$ mkenv secrets init
# the value arrives on stdin — it never lands in shell history
$ mkenv secrets set DATABASE_URL
postgres://user:password@host/db
$ # encrypted on this machine; the server stores ciphertext
Secret names match [A-Za-z_][A-Za-z0-9_.-]*, up to 128 characters.
#Zero knowledge
Every secret value is encrypted with AES-256-GCM under your team's data key before transmission. The data key itself is stored only wrapped — once per member, using ephemeral-static ECDH (P-256) against that member's public key. The backend holds ciphertext envelopes and wrapped keys, and no key material that can open either.
The crypto and document code is open source (Apache-2.0), so the code that touches your plaintext is the code you can read.
#CLI reference
| command | behavior |
|---|---|
mkenv <command> [args...] | Resolve the document, inject variables, run the command, exit with its exit code. Resolved variables win over inherited ones. |
mkenv login | Device-flow sign-in; stores the access token and generates the machine key pair on first run. |
mkenv secrets init | Generate the tenant data key locally and store only the wrapped envelope server-side. |
mkenv secrets set <name> | Encrypt the stdin value under the tenant data key and store the ciphertext envelope. |
| exit code | meaning |
|---|---|
child's code | The command ran; mkenv is transparent (signals map to 128+N). |
1 | Resolution failed — missing document, schema violation, unreachable secret. The command never ran. |
2 | Usage error (no command given). |
127 | The command could not be spawned. |
#Environment variables
| variable | meaning |
|---|---|
MKENV_URL | Backend base URL. Defaults to https://api.mkenv.net; point it at a self-hosted backend and the CLI needs no other change. |
XDG_CONFIG_HOME | Overrides where mkenv/ config (token, machine key) lives. Default ~/.config. |