Skip to content

Remote state on Cloudflare R2

Every configuration under terraform/ stores its state in the homelab-tfstate bucket on Cloudflare R2, each under its own key.

The same configuration is applied from a workstation and from CI on the self-hosted runner. Local state would mean the two disagree about what exists. A shared bucket gives one source of truth and keeps state (which can include secrets) out of the repository.

R2 is S3-compatible, so OpenTofu’s built-in s3 backend works with it. It has no egress fees, and it sits with the rest of the Cloudflare setup (DNS, certificates, these docs). It also lives outside the homelab, so the state survives if the Proxmox host is lost.

R2 is not AWS, so the backend turns off the checks that only make sense against AWS:

terraform {
backend "s3" {
endpoint = "https://<account-id>.r2.cloudflarestorage.com"
bucket = "homelab-tfstate"
key = "proxmox/vm/terraform.tfstate"
region = "auto"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_region_validation = true
skip_requesting_account_id = true
skip_s3_checksum = true
use_path_style = true
}
}

Credentials come from AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, set to an R2 API token’s key pair.

The bucket is created by the cloudflare/ configuration. That configuration can’t store its state in the bucket it creates, so it keeps local state.

The backend has no locking configured. Concurrent applies against the same key are avoided by running applies through CI, one workflow per configuration.