Lifecycle
The Tamer workflow mirrors Terraform: bootstrap → sync → plan → apply → deploy → drift → destroy.
Local is a separate track. --env local never writes tamer-state and never calls Cloudflare. Generate the gitignored wrangler pair, then run Wrangler yourself — see Local Development.
Bootstrap
tamer bootstrapCreates account-scoped Tamer metadata (shared across all envs):
tamer-state(D1) — deployment state, keyedtamer_state:{env}:{stackName}tamer-artifacts(R2) — Tamer-managed bundlestamer-secrets(D1) — encrypted secrets vault
Run once per account. Idempotent. No --env flag — these resources are account-scoped (no -<env> suffix); per-env data is isolated by row key.
Sync
tamer sync --env devReads Cloudflare API listings and merges them into state. No writes to Cloudflare. Use after out-of-band changes or when adopting existing resources.
Plan
tamer plan --env dev
tamer plan --env dev --json # machine-readable
tamer plan --env dev --out plan.json # save plan with attestation
tamer plan --env dev --destroy # preview destructionRead-only preview of what apply + deploy would create/update/replace. Never mutates state or Cloudflare. See CLI Reference for all flags.
Local generate (tamer wrangler)
tamer wrangler --env local
tamer wrangler --env local --worker api--env local only. Writes wrangler.json + wrangler.vitest.json with synthesized binding IDs. No Cloudflare API, no credentials, no state row. Then run wrangler dev / Vitest yourself. WFP templates use tamer wfp tenant wrangler --env local --workspace <ws> instead.
tamer apply --env local is the same emit (still no provisioning). Prefer tamer wrangler. tamer wrangler --env dev is rejected — remote wrangler.json comes from apply / deploy.
Apply
tamer apply --env dev
tamer apply --env dev --plan plan.json # saved plan
tamer apply --env dev --rollback-on-failure # CI safety
tamer apply --env dev --target d1:settings # scopedProvisions missing resources (D1, R2, KV, Queues, Hyperdrive, Vectorize, AI Gateway, Pipelines, Workflows, Secrets Stores, DNS records). Generates wrangler.json for every worker. Does not deploy workers.
--env local is generate-only: it writes the gitignored wrangler pair (wrangler.json + wrangler.vitest.json) with synthesized IDs and does not create Cloudflare resources. Prefer tamer wrangler for that emit. See Local Development.
Workflows: deferred registration on greenfield envs
Workflows are a chicken-and-egg case — Cloudflare can't register a Workflow until its host script exists. On a fresh env, apply runs before deploy, so the script isn't live yet. Tamer handles this by deferring Workflow registration during apply when the host script returns a 404, and re-registering it during deploy after wrangler deploy succeeds. You'll see a deferred (script "..." not deployed yet; will register during deploy) notice — that's expected on first apply.
Deploy
tamer deploy --env dev
tamer deploy --env dev --worker api # single workerFor each worker (in topological order by service bindings, including durable_objects.bindings[].script_name):
- Push required secrets from vault
- Generate
wrangler.json - Run
buildstep (if declared) — spawns with resolved vars as env - Run
wrangler types - Run
wrangler deploy - Register Workflows declared by this worker (after the script is live)
- Apply zone-name
tamerRoutesvia Workers Routes API
Drift
tamer drift --env dev
tamer drift --env dev --jsonRead-only diff between Tamer state, Cloudflare reality, and current config. Reports missingFromCloudflare, unrecordedInState, and undeployed.
Destroy
# Non-protected envs (dev, staging, pr-* by default) — no confirmation needed
tamer destroy --env dev
tamer destroy --env dev --plan destroy.json
# Protected envs (default: prod, production) — typed confirmation
tamer destroy --env prod --confirm-env prod
# Break-glass: skip the confirmation gate (does not change what gets deleted)
tamer destroy --env prod --forceRemoves workers, resources, routes, and namespaces for the env.
Protected-env confirmation gate
Envs listed in root-level protectedEnvs (default ["prod", "production"] when unset) require an explicit confirmation before destroy / reset:
| Path | Flags |
|---|---|
| Normal | --confirm-env <name> — must match --env |
| Break-glass | --force — skips the typed confirmation |
--force only skips this safety check. It does not change what gets deleted (preserveOnDestroy, email rules vs substrate, --wipe-metadata, scoped --target / --kind, etc.).
Ephemeral pr-* envs are usually not in protectedEnvs, so they need neither --confirm-env nor --force unless you added them to that list. CI often still passes both for consistency.
Widen the gate with protectedEnvs: ["prod","production","qa"] or opt out with []. See Environments.
The same gate applies to tamer reset (--confirm-env / --force) and to tamer wfp tenant destroy / reset (--confirm-tenant <workspace> / --force).
Destroying a single resource
tamer plan --env dev --destroy --target d1:app-db # preview
tamer destroy --env dev --target d1:app-db # delete just that DB
tamer apply --env dev --target d1:app-db # recreate fresh--target <kind>:<logical> scopes destroy to one declared resource: only its state entries are deleted (every shard, for a sharded D1) — workers, routes, and all other resources are untouched. The protected-env gate still applies. Destroy also accepts shard_group:<groupName>; --wipe-metadata, --skip-workers, and --plan are rejected alongside --target.
Two things to know:
- Recreating a D1 changes its UUID. Deployed workers bind the old id, so after the targeted
apply, runtamer deploy --env <env>(andtamer migrate --env <env>for a fresh database). - It's also the stale-state fix. If a resource was deleted out-of-band (drift reports
missingFromCloudflare),applywon't recreate it while the state entry exists. A targeted destroy tolerates the Cloudflare 404 and prunes the entry; thenapplyrecreates cleanly.
Orphan reconciliation (resources removed from config)
Removing a resource from tamer.config.ts does not delete it from Cloudflare — destruction stays explicit. Instead, tamer sync flags every state entry that's no longer declared (an "orphan"), recording it in the orphanedAt map with the timestamp it was first detected. Two commands surface orphans so you can find them:
tamer statuslists an Orphaned section (stack + tenant).tamer driftreports a removed from config direction per kind.
Acting commands then skip orphans so you don't waste work on the condemned:
wfp tenant resetskips utility D1s and shard groups removed from the worker template (logging a line per skip), resetting only declared siblings.
To actually remove orphans:
tamer destroy --env dev --orphans # stack: every orphaned resource
tamer destroy --env dev --orphans --dry-run # list without deleting
tamer wfp tenant destroy --env dev --workspace acme --orphans # one tenantdestroy --orphans honors preserveOnDestroy and the protected-env gate. A resource re-added to config has its flag cleared on the next sync.
Sync runs automatically before drift / status / destroy / wfp tenant destroy / wfp tenant reset (on non-local envs) so orphan flags are fresh; --allow-stale skips that sync.
Reset (D1 greenfield loop)
tamer reset --env dev --target d1:app-db
tamer reset --env dev --kind d1 # every declared stack D1
tamer reset --env dev --target d1:app-db --skip-deploy
tamer reset --env local --target d1:app-db # local miniflare sqlite only
# WFP tenant D1s (remote greenfield):
tamer wfp tenant reset --env dev --workspace acme --kind d1
# WFP tenant D1s (local miniflare under the template worker dir):
tamer wfp tenant reset --env local --workspace localdev --kind d1One command for the stack dev-loop: targeted destroy → targeted apply → migrate → seed → redeploy workers that bind the reset database(s). Tenant reset is the parallel path for WFP template D1s (remote: recreate CF D1 + reupload; local: wipe sqlite + migrate + seed). See Squashing migrations and Seed for the full workflow (including manual migration-file squashing). D1 only in v1; protected envs require --confirm-env (stack) or --confirm-tenant (remote tenant), or --force to skip that gate.
State management
State lives in tamer-state (account-scoped D1, shared across all envs). Each env's state is isolated by row key: tamer_state:{env}:{stackName}. There is no state row for local — binding IDs are synthesized in memory. Each stack writes to its own row within the env's namespace. The state schema auto-migrates older versions in place on read.
Key operations:
beginOperation/finishOperation/failOperation— tracks in-progress commands with timestampsoperationHistory— last 50 operations (capped)tamer events --env dev— prints operation timeline