Uploading Files to CloudFlare R2 Using Rails ActiveStorage

R2 is a new object storage system by Cloudflare. It is comparable and compatible with Amazon S3, except much simpler and cheaper, and with a very generous free plan. You can read more about it here

Let us see how to set it up with Rails so we can upload files for this article!



1. Set Up ActiveStorage


R2 uses the AWS Ruby SDK to integrate with Ruby applications. Let us install that together with an image processing library. Add the following to your `Gemfile`

gem "image_processing"
gem "aws-sdk-s3", require: false

Run `bundle install`

Now install ActiveStorage by running the following, on the terminal still:

bin/rails active_storage:install
bin/rails db:migrate


2. Set Up R2 And Create Buckets


Sign up on Cloudflare if you haven't, and visit your dashboard.

On the sidebar, click on R2, then click on Create Bucket and give it a name.

You will need an R2 auth token to authenticate you app against your R2 buckets, once your bucket is created, go back to the R2 overview dashboard and on the top right(at the time of this writing), click on "Manage R2 API Tokens", then create a new API token.

Give it a name, then grant the right permissions - for most, select Edit access, then create it.

Copy the resultant Access Key ID and the Secret Access Key,  we will use in on the next steps.

Copy your Account ID as well, from the top right.


3. Set Up ActiveStorage


Set up ActiveStorage for your environments.

We will start by adding the above credentials to our production environment, so that in production, files are uploaded to R2.

On the terminal, run the following:

bin/rails credentials:edit -e production

Add your Cloudflare credentials


cloudflare:
  account_id: YOUR_ACCOUNT_ID
  access_key_id: YOUR_ACCESS_KEY_ID
  secret_access_key: YOUR_SECRET_ACCESS_KEY
  endpoint: https://<YOUR_ACCOUNT_ID>.r2.cloudflarestorage.com
  article_files_bucket: YOUR_ARTICLES_BUCKET_NAME
  user_files_bucket: YOUR_USERS_BUCKET_NAME

Delete your `config/storage.yml` file and create different `storage.yml` files based on your environments.

On production, we will upload files  to R2, so in `config/storage/production.yml`, have the following:

article_files_bucket:
  service: S3
  endpoint: <%= Rails.application.credentials.dig(:cloudflare, :endpoint) %>
  access_key_id: <%= Rails.application.credentials.dig(:cloudflare, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:cloudflare, :secret_access_key) %>
  region: auto
  bucket: <%= Rails.application.credentials.dig(:cloudflare, :article_files_bucket) %>

You can create one bucket for all your uploads, or, like I prefer, create buckets for each service you need, eg one for article images, another for user avatars, etc. Whichever you choose, name them appropriately for clarity.

For example, to upload user avatars to R2 as well, I would have the following in the same file as above

user_files_bucket:
  service: S3
  endpoint: <%= Rails.application.credentials.dig(:cloudflare, :endpoint) %>
  access_key_id: <%= Rails.application.credentials.dig(:cloudflare, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:cloudflare, :secret_access_key) %>
  region: auto
  bucket: <%= Rails.application.credentials.dig(:cloudflare, :user_files_bucket) %>

On development, I want to upload files to my local disk storage, so the following would go to `config/development/storage.yml`

article_files_bucket:
  service: Disk
  root: <%= Rails.root.join("storage") %>

And for the test environment, you want to upload them to a temporary storage unit that will be deleted after, so in your `config/test/storage.yml`

article_files_bucket:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>



4. Point Your App To Use The Right ActiveStorage Services

Hold on, we're almost there.

Next, we would have to tell our different environments what default service to use for our file uploads.

This is where you might have a better way of doing it than I do, if so, leave a comment below!

I have the following in all my environment configurations. You could create a default service and use it instead as well. Inside `config/environments/production.rb`, `config/environments/development.rb` and `config/environments/test.rb`, have the following:

  config.active_storage.service = :article_files_bucket

Finally, inside the models that you want to upload files for, add the following:

  has_many_attached :images, service: :article_files_bucket

This way, I can upload many images for any article, and, on production, they will all go to R2! (You might want to validate the file content-types, as well as their sizes, and other validations)

To show that it works, here's one photo of my workspace a little over a year ago in a different country!



workspace.jpeg 60.8 KB