27views

💽 Migrating to S3 with ActiveStorage Mirroring

In this post, I’ll explain how to safely migrate your Ruby on Rails application to use S3 for file storage.

Quick Overview

To safely switch to S3 (self-hosted or AWS), you first need to copy all existing files so that they are stored both locally and on the S3 server. Only after that can you safely delete the local files and fully move to remote storage.

Setting Up Mirroring

In config/storage.yml, add configurations for local storage, S3, and the mirror service:

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

s3: # you can name it anything, e.g., aws
  service: S3
  endpoint: "https://s3.example.com"
  access_key_id: <%= Rails.application.credentials.dig(:s3, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:s3, :secret_access_key) %>
  region: us-east-1 # set your region
  bucket: <%= Rails.application.credentials.dig(:s3, :bucket) %>
  force_path_style: true

mirror:
  service: Mirror
  primary: local
  mirrors: [s3]

I used Rails.application.credentials to store sensitive tokens, but you can fetch them from environment variables or any other secrets storage.

The mirror section is the key part — here we tell Rails to store files both locally and duplicate them to S3.

Then, in the desired environment (config/environments/production.rb, config/environments/staging.rb, etc.), switch the storage service:

# config/environments/production.rb

config.active_storage.service = :mirror

I recommend enabling :mirror only in server environments, and keeping :local in development and test.

Copying Existing Files

After updating the config, new files will immediately be saved through the mirror, but the old ones will remain in the local folder. To transfer them, run this in the Rails console:

# Run once
ActiveStorage::Blob.update_all(service_name: "mirror")
ActiveStorage::Blob.find_each { |blob| blob.mirror_later }

This will update all records and trigger background jobs to upload the files to S3. After that, all old files will appear in the cloud, and new ones will be mirrored as well.

Thanks to Oliver Eidel for his post, which helped clarify the migration process.

Full Migration to S3

Once all files have been successfully copied:

  1. Remove the mirror section from config/storage.yml — it’s no longer needed.
  2. Change config.active_storage.service = :mirror to config.active_storage.service = :s3 (or whatever name you used for your S3 service) in the environment configs.

To make S3 the "master" for all existing records, run:

ActiveStorage::Blob.update_all(service_name: "s3")

Optional: You can now clean up the local storage:

Make sure all files have been uploaded to S3 first!

rm -rf storage/

Done! Your Ruby on Rails application now stores all files exclusively on S3.