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.
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:
# config/environments/production.rb .. config.action_controller.asset_host = "http://aabbccdd.cloudfront.net" ..
This will make rails add the specified asset host to the generated url when you use
or similair helpers in production. For example if you do
<%= stylesheet_link_tag 'application' %>
you will get
<link href="http://aabbccdd.cloudfront.net/assets/application-388a2a900c29a176d20c18ed000f77fa.css" media="screen" rel="stylesheet" type="text/css" />
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.
There is two ways to do this. By overriding the
# lib/tasks/disable_assets_precompile.rake Rake::Task['assets:precompile'].clear namespace :assets do task :precompile do puts '* rake assets:precompile has been disabled (lib/tasks/disable_precompile.rake)' end end
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.
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
true in your production settings:
# config/environments/production.rb .. config.assets.compile = true ..
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
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:
require ::File.expand_path('../config/environment', __FILE__) use Rack::Deflater run MySuperApp::Application
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!blog comments powered by Disqus