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_ADDRESSTF_HTTP_LOCK_ADDRESSTF_HTTP_UNLOCK_ADDRESSTF_HTTP_USERNAMETF_HTTP_PASSWORD
The workflows map those secrets onto the actual 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-deploy.ymlcomments PR plans forinfra/webchanges.- That PR plan job only runs for same-repo PRs, not fork PRs, because it needs repo secrets.
- The same workflow applies production on pushes to
devor manual dispatch. tofu applyuploadsdist/throughcloudflare_worker_versionand promotes it withcloudflare_workers_deployment.- Production lives on
app.sable.moe.