Cloudflare

Storing Terraform state in an R2 bucket

R2 is S3-compatible but you need to set it up properly to work with Terraform.

First, create an R2 bucket. I conveniently called mine terraform.
Then, create an R2 scoped token.
It's important to create a token from here, rather than a general API token as this page will give you the secret key and access key to use for the S3 API.

terraform {
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~>4.44.0"
    }
  }
  backend "s3" {
    bucket = "terraform"
    key    = "main.tfstate"

    endpoints  = { s3 = "https://<ACCOUNT_ID>.r2.cloudflarestorage.com" }
    region     = "auto"
    access_key = "<ACCESS_KEY>"
    secret_key = "<SECRET_KEY>"

    skip_credentials_validation = true
    skip_region_validation      = true
    skip_requesting_account_id  = true
    skip_metadata_api_check     = true
    skip_s3_checksum            = true
    use_path_style              = true
  }
}

It's also important to set the endpoint be ${account_id}.r2.cloudflarestorage.com as this is the only S3-compatible endpoint and custom domains will not work.

Hosting on Cloudflare Pages

Configure infrastructure with Cloudflare Pages. First push afterwards will trigger a deployment.

terraform {
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~>4.44.0"
    }
  }
  # init with `tf init -backend-config=main.tfbackend`
  backend "s3" {}
}

variable "cloudflare_api_token" { sensitive = true }
provider "cloudflare" {
  api_token = var.cloudflare_api_token
}

locals {
  account_id = "271d70c872fc142477a92de255c0a75a"
  zone_id    = "b1df4c83881b1414bff6c99c5bc9b31e"
}

resource "cloudflare_pages_project" "main" {
  account_id        = local.account_id
  name              = "<PROJECT_NAME>"
  production_branch = "main"

  build_config {
    destination_dir = "dest/"
  }

  source {
    type = "github"

    config {
      owner             = "mitiko"
      repo_name         = "<REPO_NAME>"
      production_branch = "main"
    }
  }
}

resource "cloudflare_pages_domain" "main" {
  account_id   = local.account_id
  project_name = cloudflare_pages_project.main.name
  domain       = "<SUBDOMAIN>.mitiko.xyz"
}

resource "cloudflare_record" "main" {
  zone_id = local.zone_id
  name    = "<SUBDOMAIN>"
  type    = "CNAME"
  proxied = true
  content = cloudflare_pages_project.main.subdomain
}

Import (as of 4.44.0):

import {
  to = cloudflare_pages_project.main
  id = "{account_id}/{project_name}"
}

import {
  to = cloudflare_pages_domain.main
  id = "{account_id}/{project_name}/{domain_name}"
}

import {
  to = cloudflare_record.main
  id = "{zone_id}/{record_id}"
}
# curl -X GET https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records -H "X-Auth-Email: {email}" -H "Authorization: Bearer {api_token}"

Uploading to R2 bucket

wrangler r2 object put bucket/path/filename --file=a.jpeg