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!
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