Values Lifecycle
Where do values come from, and where do they land? This page traces the full journey of a config value from declaration to consumption.
The three carriers
| Carrier | What | Who reads it | File artifact? |
|---|---|---|---|
Tamer config (vars) | Non-secret deploy values (API URLs, flags, env labels) | Worker at runtime + build step at compile time | tamer/project.config.ts (committed) |
Tamer vault (secrets) | Worker runtime secrets (Stripe keys, JWT secrets) | Worker at runtime via tamer secrets push | None (encrypted in D1) |
.env | CF API auth + vault decryption keys | Tamer/Wrangler themselves | .env (gitignored) |
Nothing that lands in a Worker or a bundle ever passes through .env. The apps get their values exclusively from Tamer config and the Tamer vault.
Lifecycle of a var
1. DECLARED in tamer/project.config.ts
────────────────────────────
vars: { VITE_API_CLIENT_URL: "http://127.0.0.1:8993/v1" }
env: { dev: { vars: { VITE_API_CLIENT_URL: "https://dev.api.example.com/v1" } } }
2. MERGED for target env
──────────────────────
mergedWorkerConfigForEnv(workerConfig, "dev", tenant)
→ base vars overlaid with env.dev vars
3. RESOLVED against state
──────────────────────
${tamer:worker:api.name} → resolved to deployed script name from state
${tamer:import:net.edgeQueue} → resolved from sibling stack's outputs
4a. WRITTEN to wrangler.json
──────────────────────────
Lands as runtime Worker env vars:
"vars": { "VITE_API_CLIENT_URL": "https://dev.api.example.com/v1" }
4b. INJECTED as build env (if worker has `build`)
───────────────────────────────────────────────
spawnBuildSync("vite build", { env: stringifiedVars })
Vite reads VITE_* from process.env → baked into bundle
5. CONSUMED
─────────
Worker runtime: env.VITE_API_CLIENT_URL (dead weight on assets-only workers)
Browser bundle: statically replaced at build time by Vite's defineLifecycle of a secret
1. DECLARED (names only)
─────────────────────
secrets: { required: ["STRIPE_API_KEY"] }
2. STORED via CLI
──────────────
tamer secrets set STRIPE_API_KEY
→ encrypted with vault master key
→ stored in tamer-secrets D1
3. PUSHED on deploy
────────────────
tamer deploy → tamer secrets push
→ decrypts from vault
→ pushes to Worker via CF Workers Secrets API
4. CONSUMED at runtime
───────────────────
Worker: env.STRIPE_API_KEY
Never in wrangler.json. Never in .env. Never in the browser bundle.Lifecycle of .env
Bun auto-loads .env from CWD
│
├── CLOUDFLARE_ACCOUNT_ID ──→ Tamer API client auth
├── CLOUDFLARE_API_TOKEN ───→ Tamer API client auth
└── TAMER_SECRETS_KEY_{env} → Vault decryption (one key per env)
That's it. No app values. No deploy config. No VITE_* keys.Per-env resolution
Values are resolved per-env at every lifecycle step:
tamer deploy --env dev
└── mergedWorkerConfigForEnv(workerConfig, "dev", tenant)
├── base vars: { VITE_API_CLIENT_URL: "http://127.0.0.1:8993/v1" }
└── dev override: { VITE_API_CLIENT_URL: "https://dev.api.example.com/v1" }
── merge ──→ { VITE_API_CLIENT_URL: "https://dev.api.example.com/v1" }The merge is a shallow overlay: env-specific vars replace base vars by key. References (${tamer:...}) are resolved against state for the target env after the merge.
Strict vs tolerant reference resolution
| Mode | Commands | Behavior on unresolved ${tamer:...} |
|---|---|---|
| Strict | apply, deploy, destroy | Throws TamerReferenceError with field path and "run apply first" hint |
| Tolerant | plan, drift, status, sync | Leaves the literal ${tamer:...} placeholder in place — read-only commands work on fresh checkout |
Cross-stack imports and plan
For ${tamer:import:…} refs specifically, plan sits between the modes: un-applied sibling stacks are tolerated (placeholders preserved), but an error reading the shared state database aborts the plan instead of degrading — a plan computed from missing imports would be wrong. drift, status, and sync degrade to warnings instead.
SPA build-time values
For workers with build declared, the resolved vars are passed as environment variables to the build subprocess. This is the only way compile-time values (like VITE_*) should reach the bundler:
- No
.envfile generation - No
wrangler.jsonread-back - No codegen step
- Tamer holds the values in memory and passes them directly to the spawned build
See SPA Build for the full decision and rejected alternatives.