Uploading Files to CloudFlare R2 Using Rails ActiveStorage

R2 is a recently launched storage system developed by Cloudflare. It can be compared to Amazon S3 and is compatible with it. However, R2 is designed to be simpler, more affordable, and even offers a free plan with generous benefits. You can find more details about R2 here.
Now, let's learn how to set up R2 with Rails to enable file uploading for this article.

1. Setting Up ActiveStorage

To integrate R2 with Ruby applications, R2 utilizes the AWS Ruby SDK. Therefore, we must install this SDK and an image processing library. To do so, add the following code to your `Gemfile`.

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

Run bundle install.

Now, install ActiveStorage by following these steps in the terminal:

1. Run the command:
   
   bin/rails active_storage:install
   bin/rails db:migrate
   

2. Set up R2 and create buckets:

  •    If you haven't already, sign up on Cloudflare and go to your dashboard.

  •    On the sidebar, click R2, then select "Create Bucket" and give it a name.

  •    After creating the bucket, navigate to the R2 overview dashboard and click on "Manage R2 API Tokens" at the top right (at the time of writing).

  •    Create a new API token, give it a name, and select "Edit access" for most permissions.

  •    Copy the resulting Access Key ID, Secret Access Key, and your Account ID from the top right. We'll use these in the next steps.

3. Set up ActiveStorage:

   Configure ActiveStorage for your environments.

   Start by adding the Cloudflare credentials to the production environment. In the terminal, run the following command:
   
   bin/rails credentials:edit -e production
   

   Add your Cloudflare credentials, including:
   
   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 the `config/storage.yml` file and create different `storage.yml` files based on your environments.

   In the `config/storage/production.yml` file, include the following for uploading files to R2:
   
   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 a single bucket for all your uploads or separate buckets for different services (e.g., article images, and user avatars). Name them accordingly for clarity.

   For example, to also upload user avatars to R2, include 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) %>
   

   In the config/development/storage.yml file, use the following configuration to upload files to local disk storage:
   
   local:
     service: Disk
     root: <%= Rails.root.join("storage") %>
   

   In the `config/test/storage.yml` file, configure temporary storage that will be deleted after the tests:
   ```
   test:
     service: Disk
     root: <%= Rails.root.join("tmp/storage") %>
   ```

4. Point your app to use the correct ActiveStorage services:

 To specify the default service for file uploads in each environment, add the following code to the respective environment configuration files :
      
config/environments/production.rb 
       config.active_storage.service = :article_files_bucket 
config/environments/development.rb 
       config.active_storage.service = :local 
config/environments/test.rb 
       config.active_storage.service = :test 


Finally, in the models where you want to upload files, add the following code:
   
   has_many_attached :images, service: :article_files_bucket
   

This way, you can upload multiple images for any article, and in production, they will be stored in R2. It's also a good idea to validate the file content types, sizes, and other criteria.