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/assetsdirectory, and also copies all your assets fromapp/assetstopublic/assets. - Rails preferentially picks up application.css1 from
public/assetsbeforeapp/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:
1 2 3 4 5 6 | |
Now a script to inject the gems, pre-compile, and then revert the gems:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
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/assetsfor Rails to pick up, so instead the latest versions inapp/assetsare used.
Depending on your environment, you may want to script this all up and include pushing back into your version control system, e.g.:
1 2 3 4 5 6 | |
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.