rails, twitter bootstrap, & heroku

By Damon Mannion · · · 23 Apr 2012

There’s a minor issue with deploying Rails apps built using Twitter Bootstrap, on Heroku’s cedar stack. By default, even if you pre-compile your assets, the production environment has the less gem (and dependencies). This uses ~10M of your 100M slug space, slowing down deploys and increasing the instantiation time for webs and workers. One workaround is explained in this article.

The following are useful background articles on the asset pipeline feature introduced in Rails 3.1:

The MD5 digest creation by Rails uses the environment name, so that only digests created in production environment will work (i.e. be correctly linked to) in the production environment (see here).

The less, uglifier, etc. gems all must be in production environment to perform the pre-compile.

Which means those gems end up in your live Heroku environment, where they are never used.

Rails can be configured to use an asset group in Gemfile, which I believe is there to handle such issues. However, Heroku’s Cedar stack can’t ignore bundle groups at the moment, so this doesn’t help.

There’s a secondary issue, which leads to confusion during the development cycle:

Pre-compilation creates assets with MD5 hashes in the file names in the public/assets directory, and also copies all your assets from app/assets to public/assets.

Rails preferentially picks up application.css1 from public/assets before app/assets.

Once your assets are pre-compiled, any changes to application.css won’t appear until pre-compilation happens again, so the ‘old’ pre-compiled version will over-ride the new working versions in app/assets.

Which leads to head-scratching and “I just fixed that, why isn’t it working”.

The only solution I found to resolve both issues was to script the pre-compilation as follows.

Create a file, say Gemfile.asset, which contains only the gems needed for the pre-compilation. Think of this as a replacement for group :assets:

Gemfile.asset

123456

# adds in the gems required only for precompiling the css and jsgroup:productiondogem'less-rails-bootstrap'# twitter bootstrap gem'uglifier'# js compressiongem'yui-compressor'# css compressionend

Now a script to inject the gems, pre-compile, and then revert the gems:

This script deals with the primary issue as your MD5 digests are built in the production environment, and on a push to Heroku the ‘asset’ gems aren’t there; saving 10M from the slug size.

The secondary issue is dealt with by assets:precompile:primary. This parameter is not well documented2, and causes the pre-compile action to only create the digest versions in public/assets, and does not copy over the non-digest versions. The secondary issue is now resolved, as:

By default, in the development and test environments the digest flag is not on, so Rails doesn’t generate links to digest versions of assets.

The non-digest versions are no longer copied into public/assets.

So, in development, there is nothing in public/assets for Rails to pick up, so instead the latest versions in app/assets are used.

Depending on your environment, you may want to script this all up and include pushing back into your version control system, e.g.:

Finally, as all your assets are now precompiled, there is no need to deploy app/assets to Heroku, so add this to the .slugignore file.

I have been using this for some months now, and not hit any snags. I’m no Rails expert, but as this took me a while to work through, and struggled to find any information on it, I hope this helps someone.

This is the case with the css, I haven’t investigated whether the same issue exists with js and images, but I would guess so.↩

I couldn’t even find a reference to it when I was writing this article!↩