about 
 damon.io
articles
 » « 

rails, twitter bootstrap, & heroku

By Damon Mannion · · ·

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 primary issue discovered with the asset pipeline and Heroku is as follows:

  • It’s a good idea to have MD5 digests switched on for your assets.
  • 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
1
2
3
4
5
6
# adds in the gems required only for precompiling the css and js
group :production do
  gem 'less-rails-bootstrap'  # twitter bootstrap 
  gem 'uglifier'              # js compression
  gem 'yui-compressor'        # css compression
end

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

precompile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# backup gemfile
cp Gemfile Gemfile.bak

# inject pre-compile gems
echo -e "\n" >> Gemfile
cat Gemfile.assets >> Gemfile

# install gems
bundle install

# remove previous pre-compiled assets
rake assets:clean

# pre-compile assets
rake assets:precompile:primary RAILS_ENV=production

# reinstate original gemfile
cp Gemfile.bak Gemfile

# reinstate original gems
bundle install

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.:

deploy script
1
2
3
4
5
6
precompile
git add public/assets
git commit -a -m'precompiled assets'
git push github
git push heroku
heroku restart

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.

  1. 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.

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