config & secrets
Out-of-band env vars that always win.
Why out-of-band?
Manifest env blocks are great for non-secrets (NODE_ENV, PORT, LOG_LEVEL). But secrets shouldn't live in a file that gets committed.
voodu config stores env vars in the controller's encrypted store, scoped per app. config:set always wins over manifest env blocks — a runaway apply can't reset a production secret.
Set
voodu config set DATABASE_URL=postgres://... -a prod
voodu config set SECRET_KEY_BASE=$(openssl rand -hex 32) -a prodGet / list
voodu config get DATABASE_URL -a prod
voodu config list -a prodReload
Some env changes require a restart:
voodu config reload -a prodreload re-renders the env into running containers and bounces replicas one at a time.
Virtual buckets
Group secrets that belong to a tool, not an app — useful for shared CLI configs (AWS, R2, Docker auth):
voodu config aws/cli set AWS_ACCESS_KEY_ID=AKIA...
voodu config aws/cli set AWS_SECRET_ACCESS_KEY=...
voodu config aws/cli set AWS_DEFAULT_REGION=us-east-1Reference from a manifest:
cronjob "prod" "backup" {
image = "amazon/aws-cli:latest"
env_from = ["aws/cli"]
command = ["s3", "sync", "/data/", "s3://bucket/data/"]
}env_from feeds parse-time ${VAR}
env_from = ["scope/name"] does two things:
- At runtime — keys from the bucket are injected as env vars into the container.
- At apply time — the CLI fetches the bucket before parsing the manifest, so
${KEY}interpolation can resolve from it.
This second point is the unlock: webhook URLs, pager keys, slack hooks no longer need a per-dev export.
deployment "prod" "api" {
env_from = ["prod/shared"]
on_deploy {
success { url = "${SLACK_WEBHOOK_URL}" } # resolved from prod/shared
}
}voodu config prod/shared set SLACK_WEBHOOK_URL=https://hooks.slack.com/...
voodu apply -f voodu.hcl -r prod-1Precedence on collision: shell env wins over bucket. So SLACK_WEBHOOK_URL=https://test ./bin/voodu apply ... overrides without touching the bucket — handy for testing.
Caveat: bucket-fed interpolation is local-apply only. With -r <remote>, the CLI runs in your shell, fetches the buckets locally, and ships the resolved manifest. The SSH-forward path keeps shell-only resolution.
Precedence
voodu config set ... ← always wins
app.env { ... } ← manifest defaults
build args / docker ENV ← lowest priority