Skip to content

Configure GCP to Cloudflare R2 synchronization and CDN distribution (One-Time)

This guide explains how to set up an automated synchronization pipeline between a Google Cloud Storage (GCS) bucket and a Cloudflare R2 bucket, and how to distribute those resources via a Cloudflare Worker CDN.

This is typically used when you need to mirror GCP storage to Cloudflare to leverage their CDN network and edge compute capabilities for specific resource modules (e.g., CodePush).


Environment note (important)

In examples, you may see placeholder values such as <project-id>, <project-number>, or <env>.

For your setup, ensure you replace these with your actual GCP Project ID, numeric Project Number, and the correct environment prefix (e.g., dev, prod).


Scope

Covers:

  • Creating the target Cloudflare R2 bucket and generating API credentials
  • Configuring a GCP Service Account with proper IAM permissions for Eventarc and Pub/Sub
  • Deploying the Sync Service container to Google Cloud Run
  • Setting up Eventarc triggers for object finalization (upload) and deletion
  • Migrating legacy data via Cloudflare's Data Migration tool
  • Provisioning a Cloudflare Worker to act as a secure CDN layer
  • Updating the Resource Service configuration to route traffic through the new CDN

Prerequisites

  • Access to the relevant GCP project and permissions to manage:
  • IAM & Admin (Service Accounts and Role Bindings)
  • Cloud Run
  • Eventarc and Pub/Sub
  • Artifact Registry / Container Registry
  • Access to the Cloudflare Dashboard with permissions to manage R2 and Workers.
  • The codebase for both the ac-r2-sync-service and ac-cloudflare-cdn-worker.

1) Configure the Cloudflare R2 bucket and credentials

First, establish the destination bucket in Cloudflare and generate the API keys required for Cloud Run authentication.

  1. Open the Cloudflare Dashboard.
  2. Navigate to R2 from the left sidebar and click Create bucket.
  3. Enter a standard name for your bucket (e.g., codepush-<env>) and click Create.
  4. Return to the main R2 page and click Manage R2 API Tokens on the right side.
  5. Click Create Account API token.
  6. Set the Permissions to Object Read & Write and explicitly restrict access to the specific bucket you just created.
  7. Click Create Account API Token.
  8. Securely store the Access Key ID, Secret Access Key, and Endpoint URL. You will need these for the Cloud Run configuration in step 4.

Note: The Secret Access Key is only displayed once upon creation.


2) Create the GCP Service Account and grant permissions

A dedicated Service Account is required to securely run the synchronization service and process Eventarc triggers.

2.1) Create the Cloud Run Service Account

  1. Open Google Cloud Console.
  2. Go to IAM & Admin → Service Accounts and click Create Service Account.
  3. Use a consistent naming convention (e.g., r2-sync-service-account).
  4. Grant the following roles:
  5. Storage Object Viewer: To read files from the source GCS bucket.
  6. Cloud Run Invoker: To allow Eventarc to trigger the service.
  7. Click Done.

2.2) Grant Pub/Sub permissions to Google-managed service accounts

To manage Pub/Sub events and trigger Cloud Run, the Google-managed service accounts require explicit IAM policy bindings.

You will need your Project Number.

# Find your project number
PROJECT_ID="<project-id>"
gcloud projects describe "$PROJECT_ID" --format="value(projectNumber)"

Once you have the project number, apply the required roles:

PROJECT_NUMBER="<project-number>"

# Grant pubsub.publisher to the GS project account
gcloud projects add-iam-policy-binding ${PROJECT_NUMBER} \
  --member="serviceAccount:service-${PROJECT_NUMBER}@gs-project-accounts.iam.gserviceaccount.com" \
  --role="roles/pubsub.publisher"

# Grant token creator to the Pub/Sub service account
gcloud projects add-iam-policy-binding ${PROJECT_NUMBER} \
  --member="serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com" \
  --role="roles/iam.serviceAccountTokenCreator"

Security note: Ensure these bindings are applied accurately to prevent Eventarc delivery failures.


3) Build and deploy the sync service container

The custom synchronization service handles the data movement between GCS and R2.

  1. Clone the develop branch of the synchronization service repository:
  2. https://github.com/appcircleio/ac-r2-sync-service
  3. Build the Docker image.
  4. Push the image to your Google Cloud Artifact Registry.

Tip: It is highly recommended to automate the Docker build and Artifact Registry push process using a Jenkins pipeline instead of manual execution.


4) Deploy the Cloud Run service and configure Eventarc triggers

4.1) Deploy the Cloud Run service

  1. In the GCP Console, navigate to Cloud Run and click Deploy Container.
  2. Select the sync service container image from the Artifact Registry.
  3. Under Authentication, select Require authentication (restricts access to internal triggers).
  4. In the Security tab, assign the Service Account created in Step 2.1.
  5. In the Variables & Secrets tab, inject the R2 credentials obtained in Step 1:
  6. R2_ENDPOINT
  7. R2_ACCESS_KEY
  8. R2_SECRET_KEY
  9. R2_BUCKET_NAME
  10. Click Create.

4.2) Configure Eventarc Triggers

  1. On the Cloud Run service details page, go to Triggers → Add Trigger.
  2. Configure the Upload trigger:
  3. Event provider: Cloud Storage
  4. Event type: google.cloud.storage.object.v1.finalized
  5. Service Account: Select the r2-sync-service-account
  6. Bucket: Select the source GCS bucket and save.
  7. Click Add Eventarc Trigger again.
  8. Configure the Deletion trigger:
  9. Event provider: Cloud Storage
  10. Event type: google.cloud.storage.object.v1.deleted
  11. Service Account: Select the r2-sync-service-account
  12. Bucket: Select the same source GCS bucket and save.

4.3) Optional: Configure Pub/Sub retry policy

Eventarc relies on Pub/Sub. Configure retry policies to handle transient errors gracefully.

  1. Go to Pub/Sub → Subscriptions in the GCP Console.
  2. Select the subscription automatically generated by your Eventarc trigger.
  3. Click Edit, locate the Retry Policy section.
  4. Enable Exponential backoff and configure your minimum/maximum backoff times.

5) Migrate existing data (GCS to R2)

Eventarc triggers only process new object events. Legacy data must be migrated manually using Cloudflare's built-in tool.

  1. In the Cloudflare Dashboard, navigate to R2 → Data Migration.
  2. Click Migrate data from Google Cloud Storage.
  3. Provide your GCS bucket details and upload a GCP Service Account JSON key (must have Storage Object Admin rights).
  4. Select your destination R2 bucket and input your R2 Access Key ID and Secret.
  5. Start the migration.

6) Create the Cloudflare Worker and bind R2

The worker acts as the CDN layer, validates URL signatures, and serves the files.

Code repository: https://github.com/appcircleio/ac-cloudflare-cdn-worker

  1. Go to Workers & Pages → Overview in the Cloudflare Dashboard.
  2. Click Create application → Create Worker. Name it (e.g., codepush-cdn-<env>) and deploy.
  3. Go to the Worker's Settings → Variables tab to configure environment variables.
  4. Go to Settings → Bindings.
  5. Under R2 Bucket Bindings, click Add binding. Bind the variable BUCKET to your R2 bucket.
  6. Click Edit Code at the top of the Worker page.
  7. Paste the source code from the repository into the editor and click Save and Deploy.

Tip: Manual code injection via the UI is temporary. Integrating this repository into a Jenkins CI/CD pipeline is planned.


7) Add CDN mapping to the Resource Service

To route traffic through the new Cloudflare infrastructure, configure the environment variables in the .NET Resource Server.

1. Update the CDN Mapping: Update ASPNETCORE_S3_CDN_MAPPING to map the resource (e.g., CodePush) to the Worker domain.

ASPNETCORE_S3_CDN_MAPPING="Build=https://local-cdn1.appcircle.io,...,CodePush=https://codepush-cdn.<your-subdomain>.workers.dev"

2. Define the CDN Module Provider: Explicitly route the module to the Cloudflare URL signer.

ASPNETCORE_CDN_MODULE_PROVIDERS="CodePush=CLOUDFLARE"

3. Set the URL Signing Secret: Add the HMAC generation secret.

Note: This exact string must also be stored as an encrypted variable in your Cloudflare Worker so it can successfully validate signatures.

ASPNETCORE_CLOUDFLARE_URL_SIGN_SECRET="<your-secure-random-secret-string>"

Validation checklist

  • Cloudflare R2 bucket created and API tokens securely stored.
  • GCP Service Account (r2-sync-service-account) configured with Storage Object Viewer and Cloud Run Invoker.
  • Google-managed service accounts granted pubsub.publisher and iam.serviceAccountTokenCreator.
  • Sync service container built and pushed to Artifact Registry.
  • Cloud Run service deployed with "Require authentication" and R2 environment variables.
  • Eventarc triggers (finalized and deleted) configured for the target GCS bucket.
  • Legacy data successfully migrated using Cloudflare Data Migration.
  • Cloudflare Worker deployed, R2 binding established, and code saved.
  • .NET Resource Server updated with ASPNETCORE_S3_CDN_MAPPING, ASPNETCORE_CDN_MODULE_PROVIDERS, and URL signing secret.
  • End-to-end test completed successfully.

Appendix: Future enhancements

  • Jenkins Automation: Integrate both ac-r2-sync-service and ac-cloudflare-cdn-worker into Jenkins for automated CI/CD deployments.
  • Custom Domain Setup: Map an approved custom domain (e.g., cf-cdn.<env>.appcircle.io) to the Cloudflare Worker instead of using the default .workers.dev domain.