Anything a service needs at runtime that the platform does not provision for it — an API key,
a third-party account id — is a config var. Tunables (feature flags, rates, limits, URLs
shown to users) are not config vars; they are runtime settings edited in the Console (see
runtime-settings). Set a config var with tawa config, and
the builder injects it into the pod on the next deploy.
tawa config set LOG_LEVEL=debug API_TIMEOUT=30000 # plain, readable
tawa config set --secret STRIPE_API_KEY=sk_live_... # encrypted at rest
tawa config set --secret BING_WEBMASTER_KEY --from-locker lk://insureco/daryx/bing-search-key
# ↑ the default for any secret: value never touches this terminal
tawa config list # names only for secrets
tawa config unset OLD_KEY
tawa config pull # → .env.local, for local dev
tawa deploy --prod # REQUIRED to apply any of the above
tawa config set does not change the running pod. Values are materialised into Kubernetes
at deploy time, so a value set after a deploy reaches the pod on the next deploy.
On a brand-new service, the service record is created by its first deploy (or by tawa link),
so tawa config set before then 404s. Full ordering:
build-pipeline → "First Deploy".
tawa config set K=V | tawa config set --secret K=V | |
|---|---|---|
| At rest in the builder | plaintext | AES-256-GCM under CONFIG_ENCRYPTION_KEY |
Returned by tawa config list | key and value | key only, K=******** |
| In the pod | env var | env var (via {service}-managed-secrets) |
| In the deploy snapshot | value recorded | name only, value omitted |
Both end up as ordinary env vars in the pod. --secret is about who can read it before then.
A secret that already lives in the Locker should never be pasted. Use --from-locker, below.
--from-vault (the daryx vault) is deprecated — see the migration.
--from-locker — a secret from the Locker, never through a terminalThis is the way to give a service a secret. The Locker (iec-locker) is where org credentials live. Hand the builder the item's handle and it fetches the value itself (tawa ≥ 0.29.0). Bindings, the runtime service door and the agent tools are in locker; each item's Use this item panel in the portal has the exact command.
tawa config set --secret BING_WEBMASTER_KEY --from-locker lk://insureco/daryx/bing-search-key
tawa config set --secret DB_PASSWORD --from-locker lk://insureco/carriers/paragon/password # name a field
tawa CLI ──{ "BING_WEBMASTER_KEY": { "lockerHandle": "lk://insureco/daryx/bing-search-key" } }──▶ builder
│
iec-locker ◀── POST /api/resolve (builder's Bio-ID token, scope locker-resolve) ─────────────────┘
──▶ value ──▶ encrypted into the service's managed secrets
The plaintext travels Locker → builder only — not the CLI, a log line, the config API
response, the deploy snapshot or a Septor payload. The Locker audits every attempt as a config
use with the handle and the destination (service/env:KEY), never the value.
tawa config list shows BING_WEBMASTER_KEY=******** (locker: lk://insureco/daryx/bing-search-key).
The Locker only hands a value to a service the item names. In the Locker, open the item and add
the service name (e.g. iec-webmaster) to its service bindings (bindings.services). An item
with no service bindings resolves for nobody. Also:
lk://<spec.owner>/…;lk://me/…) are refused — move the item to an org vault;ITEM_SEALED);lk://org/vault/item/field), or the Locker answers FIELD_AMBIGUOUS.Same rules as --from-vault: --secret is required, one key per handle, a value together with
the handle is refused without echoing it, every handle resolves before anything is written, and
it is a one-time copy — rotating the item does not propagate; re-run, then deploy.
| Error | Means | Do |
|---|---|---|
LOCKER_HANDLE_NOT_FOUND | no such item in the owner org's locker | copy the handle from the item in the Locker |
LOCKER_RESOLVE_FORBIDDEN | the Locker refused; its code is in the message (NOT_BOUND, ORG_MISMATCH, PERSONAL_NOT_RESOLVABLE, ITEM_SEALED, FIELD_AMBIGUOUS, …) | for NOT_BOUND, bind the item to the service |
LOCKER_UNREACHABLE | the Locker did not answer | retry; nothing was set |
LOCKER_NOT_CONFIGURED | the builder has no LOCKER_URL, or Bio-ID would not issue it a locker-resolve token | a platform operator must fix the builder |
--from-vault — deprecatedDeprecated (2026-10-04). The daryx vault is being retired in favour of the Locker. Use
--from-lockerfor every new secret, and move existing--from-vaultkeys over as part of iec-builder#258.--from-vaultstill works, so existing config keeps deploying. The rest of this section is kept for those keys.
--from-vault <alias> no longer goes straight to the daryx vault. The builder:
legacyAlias (the daryx import kept every alias on its Locker
item), in the service owner's org locker. The item must be bound to the destination service.[warn] naming the alias and saying it should move.NOT_BOUND,
ITEM_SEALED, ORG_MISMATCH, an unreachable Locker. The item exists, so a stale daryx copy
would only hide the real problem. Fix the binding, then re-run.A successful Locker answer is recorded exactly as --from-locker records it, so tawa config list
shows which store answered: (locker: lk://…) or (vault: <alias>). The Locker path also warns
with the handle to switch to — run --from-locker <that handle> next time.
Pasting a secret is the problem this solves. tawa config set --secret KEY=sk-... puts the
plaintext in the terminal, in shell history, in whatever scrolled past, and — when an agent ran
the command — in that agent's context. The daryx vault already holds
these values and is built so they are never returned to a caller.
So hand over the alias and let the builder fetch the value:
tawa config set --secret ANTHROPIC_API_KEY --from-vault ledger-anthropic-lily
tawa CLI ──{ "ANTHROPIC_API_KEY": { "vaultAlias": "ledger-anthropic-lily" } }──▶ builder
│
daryx vault ◀── POST /credentials/<alias>/resolve (builder's own token)┘
──▶ value ──▶ encrypted into the service's managed secrets
The plaintext travels vault → builder and nowhere else. It is not in the CLI's process, not in a log line, not in the config API response, not in the deploy snapshot, and not in a Septor payload — only the alias name and the env-var key are.
tawa config list shows where it came from:
ANTHROPIC_API_KEY=******** (vault: ledger-anthropic-lily)
Rotating the value in the vault does not propagate. Re-run the command after a rotation, then deploy. (A live reference would put a vault call on the deploy path, so a vault outage would start failing deploys — a bigger change, deliberately not taken here.)
--from-vault requires --secret. A vault value in a plain config var would be readable
by anyone who can read the service, which defeats the point.--from-vault together is refused — and the refusal does not echo what you
typed, because what you typed may be the secret.Every alias is resolved before anything is written, so a failure leaves the service's config exactly as it was — never half-applied.
| Error | Means | Do |
|---|---|---|
VAULT_ALIAS_NOT_FOUND | no such alias, or it was deleted | daryx ssh credentials list |
VAULT_RESOLVE_FORBIDDEN | the builder's token may not use that alias | ask a platform operator to tag its token for the alias |
VAULT_UNREACHABLE | the vault did not answer | retry; nothing was set |
VAULT_NOT_CONFIGURED | this builder holds no vault credential | a platform operator must set DARYX_VAULT_TOKEN |
daryx ssh credentials export <alias> --to tawa --service <svc> --key KEY does the same trip
with the vault pushing instead of the builder pulling. It predates --from-vault and still
works. Both are deprecated with the daryx vault; the Locker equivalent is --from-locker.
BIO_CLIENT_ID, BIO_CLIENT_SECRET, BIO_ID_URL, MONGODB_URI, REDIS_URL, NEO4J_*,
KOKO_URL, JANUS_URL, IEC_WALLET_URL, SERVICE_NAMESPACE, HTTP(S)_PROXY and friends are
injected by the builder on every deploy. So are three Claude access ids —
ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID — for a
service that has it switched on. (ANTHROPIC_WORKSPACE_ID is injected with them but stays
settable, because a service on a plain API key sets it itself.) tawa config set refuses them
(RESERVED_ENV_VAR) — a manual value would shadow the provisioned one and break auth, DB
connections, or gas attribution. Declare the feature instead (spec.auth, spec.databases,
spec.egress) and let the builder provision it.
spec:
config: # ← spec.config, NOT spec.configDeclarations
- key: ANTHROPIC_API_KEY
secret: true
required: true
- key: LOG_LEVEL
default: info
The block is spec.config. The builder reads key, secret, required and default and
ignores anything else — a description: is dropped silently, and a whole block written as
spec.configDeclarations parses, deploys, and does nothing.
A required declaration with no value fails the deploy in the config preflight, naming what
is missing. A default is injected when nothing is set. tawa preflight checks the same thing
without spending a build.
NEXT_PUBLIC_* / REACT_APP_* values are baked into the client bundle at docker build time,
before the pod exists. Declare them in insureco.io/env-vars and the builder writes the
declared keys into a .env.production in the build context. Only declared keys, which are
public by design — the rest stay runtime-only. See nextjs.
| Var | Purpose |
|---|---|
CONFIG_ENCRYPTION_KEY | AES-256-GCM key for managed secrets. Without it, --secret fails. |
DARYX_VAULT_URL | daryx vault base URL (default https://vault.daryx.com). Deprecated with --from-vault |
DARYX_VAULT_TOKEN | the builder's vault token. Mint it with the vault's config-resolve scope and nothing else, pinned to the builder host IP, tagged to the aliases it should reach. Empty disables --from-vault. |
LOCKER_URL | iec-locker base URL (default https://iec-locker.tawa.pro). Empty disables --from-locker. No secret: the builder uses its own Bio-ID client (builder-prod), whose allowed scopes must include locker-resolve. |
On the Locker side, POST /api/resolve accepts only a client_credentials token from a client
in iec-locker's LOCKER_RESOLVE_CLIENTS (today builder-prod) carrying the exact
locker-resolve scope — * does not satisfy it.
POST /credentials/:alias/resolve is the only vault route that returns a plaintext value, and
it is reachable only with that scope — a * scope does not satisfy it, so existing broad tokens
gain nothing. Every call is audited with the alias and its destination, never the value.
# ❌ paste a secret you already keep in the Locker
tawa config set --secret ANTHROPIC_API_KEY=sk-ant-...
# ✅
tawa config set --secret BING_WEBMASTER_KEY --from-locker lk://insureco/daryx/bing-search-key
# ❌ --from-vault for a new key — deprecated (iec-builder#258)
tawa config set --secret ANTHROPIC_API_KEY --from-vault ledger-anthropic-lily
# ✅ move the item into the Locker, bind it to the service, use --from-locker
# ❌ expect a config change to reach a running pod
tawa config set --secret K --from-locker lk://org/vault/item # then wonder why the pod has the old value
# ✅ deploy — that is when config is materialised
tawa config set --secret K --from-locker lk://org/vault/item && tawa deploy --prod
# ❌ commit .env.local (tawa config pull writes decrypted secrets to it, 0600)
# ✅ .gitignore it
# ❌ set MONGODB_URI / BIO_CLIENT_SECRET by hand — refused, and would break the service
Last updated: October 6, 2026