Active Storage on Heroku
Last updated April 24, 2024
Table of Contents
Rails 5.2 introduced Active Storage as a way of managing attaching and saving files to Active Record models. This guide will cover how to use Active Storage on Heroku.
Ephemeral Disk
Heroku has an “ephemeral” hard drive, this means that you can write files to disk, but those files will not persist after the application is restarted. By default Active Storage uses a :local
storage option, which uses the local file system to store any uploaded files. While file uploads that are stored with the :local
option will appear to work at first, the attachments will exhibit seemingly strange behavior and eventually disappear. The files will go away when the app is deployed, or when it is automatically restarted (once every 24 hours). If the app has multiple dynos, not all files will be present on every dyno. This means that the dyno that serves a web request, might be different than a dyno that contains a specific uploaded file. For example if you have two dynos, and upload a file, it will only be present on one dyno. When you refresh the webpage, there will be a 50% chance that the web request will be routed to the dyno with the file, and a 50% chance it will appear to be broken. In addition, any files stored on disk will not be visible from one-off dynos such as a heroku run bash
instance or a scheduler task because these commands use new dynos.
Instead of storing uploaded files to disk, the best practice is to leverage a cloud file storage service such as Amazon’s S3.
To use a different storage backend, you will need to modify the config/storage.yml
file. If you are using the bucketeer addon to manage S3 for you, then you can add this to your config/storage.yml
:
amazon:
service: S3
access_key_id: <%= ENV['BUCKETEER_AWS_ACCESS_KEY_ID'] %>
secret_access_key: <%= ENV['BUCKETEER_AWS_SECRET_ACCESS_KEY'] %>
region: <%= ENV['BUCKETEER_AWS_REGION'] %>
bucket: <%= ENV['BUCKETEER_BUCKET_NAME'] %>
Once you’ve done this, you will need to tell your application to use this storage backend in production. In your config/environments/production.rb
make sure that you have set your active storage service to amazon:
config.active_storage.service = :amazon
Finally you need to also include the AWS gem. Add this to your Gemfile
:
gem "aws-sdk-s3", require: false
Don’t forget to run this command locally:
$ bundle install
Then commit your files to git before trying to deploy to Heroku.
Attachment Previews
One of the marquee features of Active Storage is the ability to use “previews” of non-image attachments. Specifically you can preview PDFs and Videos. To use this feature your application needs access to system resources that know how to work with these files. By default Rails ships with support with poppler
for PDF previews, and ffmpeg
for Video previews. These system dependencies are not available by default on Heroku.
If you want the ability to preview these types of files with Active Support you need to run:
$ heroku buildpacks:add -i 1 https://github.com/heroku/heroku-buildpack-activestorage-preview
Once you’ve done this, you need to deploy again to get the binaries. You can verify that the dependencies are installed by running which ffmpeg
on the command line. If there is no output then the operation was not performed correctly. If you see a result then the binaries are ready to be used:
$ heroku run bash
~$ which ffmpeg
/app/.heroku/activestorage-preview/usr/bin/ffmpeg
If you try to preview a PDF without poppler
, the page will error and you’ll see this in your logs:
ActionView::Template::Error (ActiveStorage::UnpreviewableError)
If you try to preview a video without ffmpeg
, the preview will appear to be broken and you’ll see an error like this in your logs:
Errno::ENOENT (No such file or directory - ffmpeg):