Skip to content

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 ​

bash
tamer bootstrap

Creates account-scoped Tamer metadata (shared across all envs):

  • tamer-state (D1) — deployment state, keyed tamer_state:{env}:{stackName}
  • tamer-artifacts (R2) — Tamer-managed bundles
  • tamer-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 ​

bash
tamer sync --env dev

Reads Cloudflare API listings and merges them into state. No writes to Cloudflare. Use after out-of-band changes or when adopting existing resources.

Plan ​

bash
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 destruction

Read-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) ​

bash
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 ​

bash
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            # scoped

Provisions 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 ​

bash
tamer deploy --env dev
tamer deploy --env dev --worker api    # single worker

For each worker (in topological order by service bindings, including durable_objects.bindings[].script_name):

  1. Push required secrets from vault
  2. Generate wrangler.json
  3. Run build step (if declared) — spawns with resolved vars as env
  4. Run wrangler types
  5. Run wrangler deploy
  6. Register Workflows declared by this worker (after the script is live)
  7. Apply zone-name tamerRoutes via Workers Routes API

Drift ​

bash
tamer drift --env dev
tamer drift --env dev --json

Read-only diff between Tamer state, Cloudflare reality, and current config. Reports missingFromCloudflare, unrecordedInState, and undeployed.

Destroy ​

bash
# 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 --force

Removes 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:

PathFlags
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 ​

bash
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, run tamer deploy --env <env> (and tamer 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), apply won't recreate it while the state entry exists. A targeted destroy tolerates the Cloudflare 404 and prunes the entry; then apply recreates 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 status lists an Orphaned section (stack + tenant).
  • tamer drift reports a removed from config direction per kind.

Acting commands then skip orphans so you don't waste work on the condemned:

  • wfp tenant reset skips utility D1s and shard groups removed from the worker template (logging a line per skip), resetting only declared siblings.

To actually remove orphans:

bash
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 tenant

destroy --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) ​

bash
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 d1

One 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 timestamps
  • operationHistory — last 50 operations (capped)
  • tamer events --env dev — prints operation timeline

Released under the Tamer Evaluation License.