Both OpenTofu roots under infra/ keep state locally, encrypted with
TF_VAR_state_passphrase. That fits one operator on one workstation: no concurrent apply
to serialise, no shared passphrase, and losing the state costs a re-import rather than a
rebuild (infra/cloudflare/generate-imports.sh, then a plan that reports 0 to add).
A second person planning or applying the edge breaks that, so decide before it happens:
- No locking.
infra/cloudflare-zone owns all three zone entrypoint rulesets, and
Cloudflare allows one per (zone, phase). Two interleaved applies leave whichever
finished last, silently discarding the other's rules. prevent_destroy blocks a
destroy at plan time; it does nothing about that race.
- Divergent state. Two local states look correct until one plan proposes destroying
what the other created.
- A shared passphrase needs a rotation story a single-holder one does not.
Options:
- R2 (or any S3-compatible backend) with
use_lockfile = true — real locking; costs
a bucket and a credential pair, and the endpoint carries the account id so it can't be
committed here.
- A hosted state backend — locking and history without running storage, at the price
of another account in the trust boundary.
- Stay local, name one owner per root — zero machinery, relies on convention.
Done when the choice is recorded in infra/cloudflare/README.md next to the existing
state notes, and either both roots are migrated with locking verified by two concurrent
plans, or the owner and the propose-don't-apply rule are written down.
Worth doing in the same pass, since it also only pays off with more than one operator: a
drift-detection recipe (plan -detailed-exitcode) on a schedule, so a dashboard edit is
caught by CI rather than by the next person's plan.
Both OpenTofu roots under
infra/keep state locally, encrypted withTF_VAR_state_passphrase. That fits one operator on one workstation: no concurrent applyto serialise, no shared passphrase, and losing the state costs a re-import rather than a
rebuild (
infra/cloudflare/generate-imports.sh, then a plan that reports0 to add).A second person planning or applying the edge breaks that, so decide before it happens:
infra/cloudflare-zoneowns all three zone entrypoint rulesets, andCloudflare allows one per (zone, phase). Two interleaved applies leave whichever
finished last, silently discarding the other's rules.
prevent_destroyblocks adestroy at plan time; it does nothing about that race.
what the other created.
Options:
use_lockfile = true— real locking; costsa bucket and a credential pair, and the endpoint carries the account id so it can't be
committed here.
of another account in the trust boundary.
Done when the choice is recorded in
infra/cloudflare/README.mdnext to the existingstate notes, and either both roots are migrated with locking verified by two concurrent
plans, or the owner and the propose-don't-apply rule are written down.
Worth doing in the same pass, since it also only pays off with more than one operator: a
drift-detection recipe (
plan -detailed-exitcode) on a schedule, so a dashboard edit iscaught by CI rather than by the next person's plan.