Rails 4+ Asset Pipeline on Heroku
Last updated October 03, 2022
Table of Contents
The asset pipeline was introduced into Rails in version 3.1. This article contains information needed to run the asset pipeline in Rails version 4 and above on Heroku. This guide is not comprehensive, please see the Rails Asset Pipeline on Heroku Cedar for help with debugging and troubleshooting. This article adds additional information that is specific to the behavior in Rails 4.
Serve assets
By default Rails 4 will not serve your assets. To enable this functionality you need to go into config/application.rb
and add this line:
config.serve_static_assets = true
Alternatively you can achieve the same result by including the rails_12factor gem in your Gemfile:
gem 'rails_12factor', group: :production
This gem will configure your application to serve static assets so that you do not need to do this manually in a config file.
Only generate digest assets
In Rails 3, the version of sprockets used in the asset pipeline would produce a “digest” or a “fingerprint” for an asset and then attach it to it’s file name. For example this file:
app/assets/stylesheets/application.css.erb
When precompiled would become:
public/assets/application-c655834251f73d7714351aa287be8556.css
Where “c655834251f73d7714351aa287be8556” is the “digest” or “fingerprint” generated by MD5. In addition to “digest” file names Rails 3 asset pipeline would produce a non-digest version public/assets/application.css
. This was useful for things like sending out emails where the recipient may or may not download the asset in a timely manner.
In Rails 4 sprockets will only produce digest filenames. This means that you must use an ERB helper such as this to reference your assets:
<%= asset_path('logo.png') %>
Make sure to add a .erb
extension to any files in app/assets
that use an ERB helper. So application.css
would need to be application.css.erb
.
To get around the “email problem”, sprockets will now keep up to 3 copies of the same modified asset at a time. This also helps with rolling deploys so requests for older assets are still available. Even with this behavior it is recommend you use a CDN to serve your assets.
Caching
Heroku now caches 50mb worth of tmp/cache/assets
which is a cache directory for the asset pipeline to store intermediate files. This means that future asset compilations will be faster due to not having to recalculate these files.
This is a difference in Heroku and not in the Rails framework. Rails 3 assets are generated on every deploy as there are several bugs that prevent caching from being used effectively.
The directory tmp/cache/assets/sprockets/
will be removed from runtime to reduce your slug size (which gives you a faster boot times). This directory will only be removed if your application does not need these files i.e. dynamic asset compilation is disabled:
config.assets.compile = false
If you see a large directory size that appears to exceed 50 megabytes while inspecting your dyno in a heroku run bash
session, it may be due to sparse files. This is because a file that is smaller than the operating system’s block size will have a larger apparent size on disk. You can get the correct size that is stored in the cache between deploys by running the du
command with the --apparent-size
flag. For example:
$ heroku run bash
~ $ du -hd 1 tmp/cache/assets/
386M tmp/cache/assets/sprockets
~ $ du -hd 1 tmp/cache/assets/ --apparent-size
27M tmp/cache/assets/sprockets
In this case the first call to du
showed 386mb worth of assets, however the correct size for the files that would be zipped and stored in the cache is only 27mb.
Multiple versions
By default the Rails asset pipeline keeps the last 3 generated versions of an asset. This is to support assets being used in locations such as emails that might not be rendered until days or weeks later.
If you delete an asset, the last 3 versions of that asset will remain available. Sprockets does not track deleted assets and so it cannot know when an asset has been deleted. If you wish to remove an asset you have deleted, you can clear the cache which is how we persist the generated assets in public/assets
between builds. Note that clearing the cache will remove all older versions of assets and not just the ones that have been deleted.
Debug output
When rake assets:precompile
is run in production Rails 4 will now shows debug output while generating tmp files, this does not mean your files end up in /tmp
. The files will be copied to their correct locations after they are generated.
Known issues
There are two known issues with Rails 4 (sprockets) that can be difficult to detect or debug. To help with this we recommend using the sprockets better errors gem gem in production. These checks have been merged into Rails 4.1+ but the gem is still needed for 4.0.x versions.
You may get errors if you use sprockets_better_errors
with Rails 4.1+.
Dependencies improperly used
You can see a failure in sprockets When you change an asset file that is referenced in another file and the reference does not change. This is due to missing sprockets dependency declarations.
For example you can have a CSS file app/assets/stylesheets/application.css.erb
that references another file:
.logo {
background-image:url('<%= asset_path("logo.png") %> ');
}
Here application.css.erb
relies on app/assets/images/logo.png
. When precompile is run both assets will be generated. What happens if the logo.png
is resized to be larger per a client request? The next time precompile runs, sprockets will see that logo.png
has changed since it’s MD5 fingerprint is now different so it will be compiled. Our application.css.erb
however has not changed, so it will not be compiled again. This means our stylesheet now points to the wrong copy of our image. To fix this add an asset declaration to the top of your application.css.erb
file:
//= depend_on_asset "logo.png"
.logo {
background-image:url('<%= asset_path("logo.png") %> ');
}
Now when your logo.png
changes, your application.css.erb
will be required to compile again.
Missing Asset in precompile list
Another common production failure in sprockets is caused by assets not being declared as needing to be precompiled. By default all “application” assets and images are precompiled. This includes application.css
and application.js
. It is assumed that any CSS or JS in your project will be rolled into these files and so any extra JS or CSS in your project will not be precompiled. The gem sprockets_better_errors
will check for missing assets in your precompile list in development.
Debugging
Before you can compile your assets on Heroku, you’ll need to be able to compile them locally, run this command to debug your assets:
$ RAILS_ENV=production bundle exec rake assets:precompile
This should complete with no errors. Do NOT check in the assets into git after running this command.
If you are getting an error at page load, but not while building assets you can use heroku run bash
to debug it
$ heroku run bash
$ ls public/assets
You should see a manifest-<digest>.json
or .sprockets-manifest-<digest>.json
where <digest>
is a long alphanumeric string as well as all of your asset files.
You can also see the exact file name that Rails believes it should serve by running $ heroku run rails console
. In the console you can use the helper
object and asset_path
to determine the full path.
$ heroku run rails console
> puts helper.asset_path("application.js")
/assets/application-6aae32862efc758cf08c7b7fc0e85e15.js
Hanging asset builds
If your asset compilation appears to hang, try removing the assets from your vendor/assets
directory and putting them in your app/assets
directory.
Additional troubleshooting
Please see a comprehensive list in the Asset Pipeline troubleshooting section.