CLI exec
Run a command with your secrets in its environment, without writing a .env file.
Exec command
envilder exec resolves every secret in your map file and starts a command with those values in its environment. Nothing is written to disk: there is no .env file to gitignore, keep in sync or clean up. The map file and its $config work exactly as in pull mode.
Usage
envilder exec [options] [--] <command> [args...]Where your command starts: the -- rule
Envilder options come first. Your command starts at the first argument that is not an Envilder option, or right after -- when present. Everything from there on is passed to your command verbatim, even if it looks like an Envilder option. The -- is optional; you only need it when the command itself starts with -.
# Everything belongs to npm
envilder exec npm run dev
# --map is Envilder's, the rest is npm's
envilder exec --map=cfg.json npm run dev
# --map=x belongs to npm: it comes after the command
envilder exec npm run dev --map=x
# Same as above, with an explicit separator
envilder exec --map=cfg.json -- npm run devOptions
| Option | Description |
|---|---|
--map | Path to the map file (default: envilder.json) |
--profile | AWS CLI profile. Overrides $config.profile |
--provider | aws or azure. Overrides $config.provider |
--vault-url | Azure Key Vault URL. Overrides $config.vaultUrl |
Envilder options go after exec, never before it. An option written before exec is rejected with exit code 125, and Envilder shows the corrected command:
$ envilder --map=config/prod.json exec -- npm start
error: Put envilder options after "exec": envilder exec --map=config/prod.json -- npm …Exec mode never reads or writes a .env file and never pushes secrets, so --envfile, --push, --key, --value and --secret-path are rejected with exit code 125 before any provider call. An unknown option before your command is also an error, so a typo such as --mapp never reaches your command by accident.
| Option | Message |
|---|---|
--envfile | --envfile cannot be used with exec: exec mode never reads or writes a .env file. |
--push | --push cannot be used with exec. |
--key, --value, --secret-path | Single-secret push options cannot be used with exec. |
Examples
# Start your dev server with the secrets from envilder.json
npx envilder exec -- npm run dev
# Use a specific map file
envilder exec --map=config/envilder.json -- node server.js
# Azure Key Vault via CLI flags
envilder exec --provider=azure --vault-url=https://my-vault.vault.azure.net -- dotnet run
# Docker Compose can interpolate the secrets in compose.yaml
envilder exec -- docker compose upOr wire it into your package.json scripts:
{
"scripts": {
"dev": "envilder exec -- vite",
"start": "envilder exec --map=config/prod.json -- node server.js"
}
}Output
Your command owns stdout. All Envilder output goes to stderr, so envilder exec -- node print.js > out.json writes only your command's output to out.json. On success Envilder prints a single summary line that names your command by program name only, because arguments can carry secrets:
envilder: 2 secrets resolved (aws, profile=dev) → npm …Common use cases
In every case the values exist only in the environment of your command and the processes it starts, Envilder never creates a .env file, and your command's exit code becomes Envilder's exit code. What your command does with the values is up to it: Docker, for example, keeps a container's environment in its on-disk metadata (visible with docker inspect) until the container is removed.
Start LocalStack with its auth token
When you need it: LocalStack needs LOCALSTACK_AUTH_TOKEN and you do not want the token in a .env file or in your shell history.
{
"LOCALSTACK_AUTH_TOKEN": "/envilder/development/localstack/authToken"
}With Docker Compose, reference the variable in compose.yaml. Compose takes its value from the environment it runs in, so no .env file is needed:
services:
localstack:
image: localstack/localstack:stable
ports:
- "4566:4566"
environment:
LOCALSTACK_AUTH_TOKEN: ${LOCALSTACK_AUTH_TOKEN}envilder exec -- docker compose upWith plain Docker, -e NAME without a value copies the variable from the environment of the docker client, so the token never appears on the command line:
envilder exec -- docker run --rm -e LOCALSTACK_AUTH_TOKEN -p 4566:4566 localstack/localstack:stableThe same setup with Testcontainers (TypeScript, Python, .NET) and Aspire is in envilder-examples
Integration tests that start containers
When you need it: your tests start containers (for example, with Testcontainers) and read a token or credentials from the environment (process.env, os.environ, Environment.GetEnvironmentVariable) to pass them in.
envilder exec -- npm test
envilder exec -- dotnet test
envilder exec -- pytestA failing test run keeps its exit code, so CI still fails. If a secret cannot be resolved, the tests do not start and the exit code is 125.
Database migrations and one-off scripts
When you need it: a migration tool or a script needs DATABASE_URL once, and you do not want the connection string on disk. Use --map to run the same command against another environment.
{
"DATABASE_URL": "/my-app/development/database/url"
}envilder exec -- npx prisma migrate deploy
envilder exec -- python scripts/backfill.pyTerraform and other IaC tools
When you need it: a Terraform input variable holds a secret. Terraform reads TF_VAR_<name> environment variables as values for the input variable <name>.
{
"TF_VAR_db_password": "/my-app/development/database/password"
}envilder exec -- terraform planDeclare the variable with sensitive = true so Terraform hides it in plan output. Terraform still stores values it writes into resources in its state, so protect the state as usual.
Local dev server through package scripts
When you need it: everyone on the team should start the app with the same secrets by running npm run dev.
{
"scripts": {
"dev": "envilder exec -- next dev"
}
}Add envilder as a dev dependency (npm install --save-dev envilder) so the script finds it in node_modules/.bin.
A step in GitHub Actions
When you need it: one step of a workflow needs the secrets, and only that step. Configure AWS credentials with OIDC, then run your command through npx envilder exec:
permissions:
id-token: write # Required for OIDC
contents: read
steps:
- uses: actions/checkout@v5
- uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
- run: npx envilder exec -- npm testResolved values are registered with GitHub log masking before your command starts, and the next steps do not see them.
Several commands with the same secrets
When you need it: you want to chain commands or keep working in a shell that has the secrets.
envilder exec -- sh -c 'npm run migrate && exec npm start'
envilder exec -- bashQuote the whole sh -c script in single quotes so your own shell does not run && outside envilder exec. The exec before the last command replaces the shell with it, so signals forwarded by Envilder reach npm start directly. In the interactive shell every command you run sees the secrets; type exit to leave it.
Behavior
Exit codes
Exit codes follow GNU env and docker run, so you can tell an Envilder failure apart from your command's own exit code:
| Code | Meaning |
|---|---|
n | Your command exited with code n |
128 + n | Your command was killed by signal n (for example, 137 for SIGKILL) |
125 | Envilder failed before starting the command: bad options, missing command, no map file, unresolved secrets or expired credentials |
126 | The command exists but the operating system refused to run it (for example, Permission denied) |
127 | The command was not found |
Pull and push modes keep exiting with 1 on failure.
Fail-closed
Pull mode warns and skips a missing secret. Exec mode never starts your command with an incomplete set of secrets: if any secret is missing, empty or cannot be read (for example, access denied), the command is not started, Envilder lists the affected variables with their masked paths and exits with 125. Values are resolved once, when the command starts.
Environment
Your command inherits the full environment of the shell that runs Envilder, plus every variable in the map file. The map file wins: when an inherited variable has the same name as a mapped one, your command sees the value from the provider, and Envilder prints the names (never the values) on stderr:
Overriding inherited variables: API_KEYEnvilder does not start an extra shell: arguments are passed to your command as a list. Your own shell still parses the command line first, so quote what it should not interpret: '$DB_URL' or '&&' in single quotes reach your command literally, while an unquoted $DB_URL expands to your shell's value (not the resolved secret) and an unquoted && runs a second command outside envilder exec. If you need shell features with the secrets, start a shell explicitly:
envilder exec -- sh -c 'echo $DB_URL'GitHub Actions masking
When GITHUB_ACTIONS=true, Envilder registers every resolved value with GitHub log masking (::add-mask::), so values your command prints are masked in the job log. Every registration is written to stderr before your command starts; if one cannot be written, the command does not start (exit code 125). The Envilder GitHub Action does not offer exec mode: run the CLI from a run step.
- run: npx envilder exec -- npm testDo not redirect the stderr of envilder exec away from the step log (for example, with 2> file or 2>/dev/null): that is where the runner reads the masks from. The runner reads stdout and stderr through separate pipes, so a value your command prints immediately can, in rare cases, reach the log before its mask is processed.
Signals
stdin, stdout and stderr are inherited and the TTY is preserved, so REPLs, shells and prompts work as usual. Ctrl+C in a terminal reaches your command directly. SIGTERM and SIGHUP (for example, from docker stop or Kubernetes) are forwarded to your command, and Envilder waits for it to exit, with no timeout of its own, before returning its exit code. Signals go to the direct child only: a launcher such as npm is responsible for stopping its own children.
envilder-examples · cliFull exec command guide on GitHub: Windows details, SSO recovery and every error message