infra/web manages the Cloudflare Worker, immutable Worker versions, the live
production deployment, and the production custom domain.
Prerequisites:
- OpenTofu
1.11.xinstalled locally - Node.js/pnpm installed locally so you can build
dist/before Worker uploads - A Cloudflare account with the target zone already onboarded to Cloudflare
- A GitLab project to store the OpenTofu state
- A GitLab access token that can read and write that project's OpenTofu state
Required GitHub repository secrets:
TF_CLOUDFLARE_API_TOKENTF_VAR_ACCOUNT_IDTF_VAR_ZONE_IDTF_HTTP_USERNAMETF_HTTP_PASSWORD
Required variables in both the development and production GitHub Environments:
SENTRY_ORGSENTRY_PROJECTTF_HTTP_ADDRESSTF_HTTP_LOCK_ADDRESSTF_HTTP_UNLOCK_ADDRESSTF_VAR_CUSTOM_DOMAINTF_VAR_WORKER_NAME
The HTTP backend addresses must point to distinct OpenTofu states for development and production.
The workflows map those secrets and variables onto the runtime environment variable names that Cloudflare and OpenTofu expect.
Cloudflare API token permissions:
Account > Workers Scripts > Edit- Scope the token to the specific Cloudflare account that owns the Worker.
- Scope the token to the specific zone that serves
app.sable.moe. - Do not grant Pages or DNS edit permissions here. The Worker script upload and custom-domain attach endpoints used by this repo accept Workers Scripts Write, and Cloudflare creates the DNS record for the Worker custom domain automatically.
GitLab access token permissions:
api
Helpful reference links:
- Create the main Cloudflare API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
- Find your account ID and zone ID: https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/
- GitLab-managed OpenTofu state: https://docs.gitlab.com/user/infrastructure/iac/terraform_state/
Local setup:
- Copy
terraform.tfvars.exampletoterraform.tfvarsand fill in shared values. - Copy
gitlab.http.tfbackend.exampletogitlab.http.tfbackendand fill in the GitLab project ID, state name, and username. - Run
pnpm installfrom the repo root. - Export the GitLab access token as the backend password.
- Export the Cloudflare API token for OpenTofu.
- Run
pnpm run buildbeforetofu planortofu apply, becausecloudflare_worker_versionuploads the builtdist/assets. - Initialize the backend.
Local OpenTofu production flow from the repo root:
pnpm run build
export TF_HTTP_PASSWORD="<your-gitlab-access-token>"
export CLOUDFLARE_API_TOKEN="<your-cloudflare-api-token>"
tofu -chdir=infra/web init -reconfigure -backend-config="../gitlab.http.tfbackend"
tofu -chdir=infra/web validate
tofu -chdir=infra/web plan -var-file="../terraform.tfvars"
tofu -chdir=infra/web apply -var-file="../terraform.tfvars"Optional local OpenTofu deployment message:
export TF_VAR_workers_message="$(git log -1 --pretty=%s)"
tofu -chdir=infra/web apply -var-file="../terraform.tfvars"If you already created local state before switching to GitLab state, use
tofu -chdir=infra/web init -reconfigure -migrate-state -backend-config="../gitlab.http.tfbackend"
once instead.
Preview builds:
infra/web/main.tfenables preview URL capability withsubdomain.previews_enabled = true.- Previews are handled by Cloudflare Workers Builds, not GitHub Actions.
- Connect the repo once in Cloudflare Workers Builds.
- Set the Cloudflare Builds deploy command to
npx wrangler versions upload. - This disables automatic deployments while still allowing Cloudflare to build PRs/branches and save them as preview versions.
- That keeps Cloudflare from promoting
devcommits to production. Production stays on the OpenTofu/GitHub Actions path in this repo.
npx wrangler versions uploadProduction deploys:
.github/workflows/cloudflare-web.ymlcomments PR plans forinfra/webchanges.- That PR plan job only runs for same-repo PRs, not fork PRs, because it needs repo secrets.
- The workflow applies dev on pushes to
devor manual dispatch without tag; production on pushes to tags or manual dispatch with a tag. tofu applyuploadsdist/throughcloudflare_worker_versionand promotes it withcloudflare_workers_deployment.- Production lives on
app.sable.moe.