Ruby on Rails + Active Storage: Migrating from AWS S3 to Cloudflare R2

byElvinas Predkelis

March 17, 2025

Over the years, there are more and more alternatives for cloud storage popping up. Luckily, most of them have an S3-compatible API which makes it very easy to switch between them. And that's exactly what we'll be doing throughout this article.

Our team has been gradually migrating all sorts of projects to Cloudflare R2 over the last year. We decided to migrate for a couple of reasons. Firstly, Cloudflare offers a friendlier pricing scheme which is always attractive. Secondly, it's just so much nicer to use - I personally try to avoid logging in to an AWS dashboard at all costs.

Even though this particular article covers migration to Cloudflare R2, it will work with any storage provider that has an API that's compatible with S3. For example, you could also opt-in for Digital Ocean Spaces, Hetzner Object Storage or Vultr Object Storage

Prerequisites

I'll assume you have already set up your account on Cloudflare. Also, if you're reading this - you have probably already used Active Storage for your Ruby on Rails project.


Setting up a new bucket

First of all, you'll need to create a new bucket on Cloudflare R2. Fortunately, it's very easy to do so.

  1. Navigate to R2 Object Storage in your dashboard
  2. Press on Create bucket
  3. Configure the bucket to your liking

After you're done, you'll need to also generate the credentials to access the newly created bucket. In order to do so, you'll need to create an API token. To do so, follow these steps:

  1. Navigate to R2 Object Storage > API > Manage API tokens
  2. Press on Create API token
  3. Configure the properties of the token. Make sure to add the Object Read & Write permission.

After you're done, you'll be presented with the Token value, Access Key ID, Secret Access Key. Copy these over as we'll be using these credentials setting up Active Storage and during the file migration.


Migrating files into R2

There's this tool that Cloudflare Super Slurper and it helps migrating the files over from a supported provider.

Using is pretty simple, so you can just follow these steps.

  1. Navigate to R2 > Data Migration when you log in to your dashboard
  2. Press on Migrate files
  3. Provide the credentials to your source bucket
  4. Provide the credentials to the new R2 bucket
  5. After you're done, just press Migrate files

Now, all the files will be transferred over to the new bucket you've just created.

Similarly, you can also use Sippy which migrates files as they're requested. However, we haven't used it and it won't be covered in this article.


Configuring Active Storage

Similarly to any other cloud storage provider, we'll add the newly created credentials to the credential file. Feel free to add the credentials as environment variable if that's prefered.

# bin/rails credentials:edit
cloudflare:
  r2:
    api_key: xxxxxxxxxxxx
    access_key_id: xxxxxxxxxxxx
    secret_access_key: xxxxxxxxxxxx
    endpoint: https://xxxxxxxxxxxx.r2.cloudflarestorage.com

Now, we'll add the Active Storage configuration.

# config/stroage.yml
cloudflare:
  service: S3
  region: auto
  endpoint: <%= Rails.application.credentials.dig(:cloudflare, :r2, :endpoint) %>
  access_key_id: <%= Rails.application.credentials.dig(:cloudflare, :r2, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:cloudflare, :r2, :secret_access_key) %>
  bucket: application-bucket-<%= Rails.env %>

And finally, we can update the Rails application to use Cloudflare as the Active Storage provider. In this article, we'll only set it for the production environment. However, setting up a bucket for the development environment could make the workflow nice and consistent.

# config/environments/production.rb
config.active_storage.service = :cloudflare

And that is pretty much it!


Updating Active Storage records

After you're done with setting up R2 as service, the last thing you should do is to update the Active Storage records in the database. Every record references the service_name, so we need to make sure the attachments are pointing to the right location. To do so, you might want to run this simple script to do the needed changes.

ActiveStorage::Blob.find_each do |blob|
  blob.update_column(:service_name, "cloudflare")
end

Please keep in mind that you might have multiple cloud storage services in your project. If so, filter out the blob records so you're only updating the relevant ones.


Wrapping up

This is how we approached the migration to R2 for all of the projects we maintain. Hopefully, this will give a good idea on how to migrate between cloud storage providers while using Active Storage.

We love big ideas and ambitious people

Reach out to us and let's build something great together

Schedule an exploration call