Skip to content

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

CarrierWhatWho reads itFile artifact?
Tamer config (vars)Non-secret deploy values (API URLs, flags, env labels)Worker at runtime + build step at compile timetamer/project.config.ts (committed)
Tamer vault (secrets)Worker runtime secrets (Stripe keys, JWT secrets)Worker at runtime via tamer secrets pushNone (encrypted in D1)
.envCF API auth + vault decryption keysTamer/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 define

Lifecycle 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

ModeCommandsBehavior on unresolved ${tamer:...}
Strictapply, deploy, destroyThrows TamerReferenceError with field path and "run apply first" hint
Tolerantplan, drift, status, syncLeaves 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 .env file generation
  • No wrangler.json read-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.

Released under the Tamer Evaluation License.