Do the asset serving dance on Heroku Cedar with Rails 3.1 and CloudFront
The asset pipeline in Rails 3.1
and Heroku’s Cedar stack adds a few new
things to think about when serving static assets from your apps. Heroku has removed
Varnish and nginx from
the the cedar stack which means that static files served from a app won’t get cached or gzipped automagically.
One way to go is to use Amazon’s S3 as asset host together with the
asset_sync gem. However there are a few problems
that can occur when Heroku run rake assets:precompile
during the deploy process.
With these facts you might realize that you want to put your compiled assets on a content delivery network like Amazon’s CloudFront so the assets can be served fast and your dynos don’t have to be bothered with serving and compiling assets.
How do we solve this in a good way?
First create a new CloudFront distribution. A great thing with CloudFront is that you can set any domain as the source for the distribution. You do this for your Heroku hosted app by specifying the apps domain as the ”Custom Origin” when creating a new distribution. With this set CloudFront don’t need a S3 bucket, instead it will mirror the assets from the custom origin domain. When the distribution gets a request for a file that it don’t have mirrored yet it will just issue the same request to your app, cache the result and return it. This also makes it possible to serve gzipped versions of your assets as CloudFront forwards the clients request headers and rack can serve gzipped versions of your assets if the client supports it (more on this further down).
Add the new distribution’s domain name (or CNAME if you specifed one) as the the asset host for your app in production environment:
This will make rails add the specified asset host to the generated url when you use stylesheet_link_tag
or similair helpers in production. For example if you do
you will get
Great, but we are not quite done yet because Heroku will automatically run the rake task to compile the assets when your app is deployed. This is a problem because if the files that CloudFront requests exists on disk then they will not be served from your rails app but from the disk, so we won’t get the correct cache headers and we won’t be able to serve gzipped versions of the assets.
What we need to do
1. Disable the assets precompile rake task
There is two ways to do this. By overriding the assets:precompile
rake task:
You can also add a empty file called public/assets/manifest.yml
to your project,
if Heroku detects this file it will not run the assets:precompile
task on deploy.
Both these ways are kind if hackish, so choose the one that suits you best.
2. Enable on-the-fly asset compiling in production
As we now need to serve the assets through the rails stack
when CloudFront makes it’s first requests we need to enable asset compiling in the production environment,
this is done by setting config.assets.compile
to true
in your production settings:
3. Enable serving of gzipped content
As Heroku has removed nginx from the cedar stack we have to do the gzipping by ourselves, this is done
by adding a rack middleware called
Rack::Deflater.
It’s part of Rack core so it’s really easy to add,
just update your config.ru
file in the root of your rails project to look like this:
Done, deploy your app and celebrate with beer. With this setup you get a geo-aware, fast and cachable way to serve your Rails 3.1 assets.
Thanks to @joeljunstrom for the proofreading!