Terraform repository for Cloudflare infrastructure. The layout uses multiple independent stacks (each with its own state and scope), sharing modules where it makes sense.
modules/— reusable building blocks such as Workers and DNS. They are consumed by stacks and are not applied directly.stacks/— one Terraform root per logical site/environment. Each stack owns its own backend, provider config, variables, and state.Makefile— the standard interface for Terraform operations. Usemake ... STACK=<stack>instead of calling Terraform manually.- CI — plan runs on pull requests and apply runs on pushes to
main. Jobs are selected per changed stack and executed in parallel through a matrix.
Typical flow: pick a stack, initialize, plan, and apply. Stacks do not share state with each other, which isolates changes and blast radius.
- Terraform installed
- A Cloudflare account and an API token with suitable access for your stacks
- Access to the Terraform Cloud organization/workspaces used by each stack backend
- If working locally with Terraform Cloud, authenticate with
terraform login
Each stack configures the Cloudflare provider in stacks/<stack>/providers.tf. Sensitive inputs must not be committed.
This repository uses GitHub Actions with one GitHub Environment per stack.
Environment naming convention:
website-<stack>
Example:
- stack
profileuses GitHub Environmentwebsite-profile
Current workflows expect these values in each stack environment:
- Secret
TF_API_TOKEN - Secret
CLOUDFLARE_API_TOKEN - Secret
CLOUDFLARE_ACCOUNT_ID - Secret
CLOUDFLARE_ZONE_ID - Variable
DOMAIN
How CI selects work:
- changes under
stacks/<stack>/run only for that stack - changes under
modules/run for all stacks - plan/apply run as a matrix, one job per stack
Terraform Cloud authentication in CI uses:
TF_API_TOKENfrom the stack environment, mapped toTF_TOKEN_app_terraform_io
If local commands work but CI fails with Terraform Cloud unauthorized, check the GitHub Environment secret first. Local success can come from an already-authenticated Terraform CLI on your machine.
List available stacks (directories under stacks/):
make listShow Makefile targets:
make helpWork on one stack (replace my-stack with the folder name under stacks/):
make init STACK=my-stack
make validate STACK=my-stack
make plan STACK=my-stack
make apply STACK=my-stackCI-safe apply command used in GitHub Actions:
make apply-auto STACK=my-stackFormat all Terraform in the repository:
make fmtCheck formatting (CI-friendly):
make fmt-checkRun init / validate / plan across every stack (sequential):
make init-all
make validate-all
make plan-allShow stack outputs:
make output STACK=my-stackDestroy resources in the stack (use with care):
make destroy STACK=my-stackIf the stack ships terraform.tfvars.example, copy it to terraform.tfvars (or equivalent) and fill in local values; production secrets usually come from TF_VAR_* or the remote backend, not from the repository.
Local note:
stacks/<stack>/.envmay exist for local convenience- CI does not read those files
- CI reads from the GitHub Environment for that stack
- Create
stacks/<new-stack>/withmain.tf,variables.tf,versions.tf,providers.tf, andbackend.tffollowing the pattern of existing stacks. - Configure the remote backend and matching Terraform Cloud workspace.
- Create a GitHub Environment named
website-<new-stack>. - Populate that environment with the required secrets and variables.
- Open a PR so plan runs for the new stack before apply.
Developed and maintained by @bedatty.