April 02, 2025 • 2 minutes read
27
💽 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:
- Remove the
mirror
section fromconfig/storage.yml
— it’s no longer needed. - Change
config.active_storage.service = :mirror
toconfig.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.