Faster Heroku deploys with Rails and webpacker


Some time ago in Pilot, we switched to use webpacker as a tool for compiling our JavaScript code. It is a simple way to integrate webpack into your Rails project.

Lately, we noticed that our Heroku deploys got frustratingly long. Over 10 minutes to build and release our main application wasn't acceptable. Add CI runtime and Heroku's preboot to the mix and now we could easily end up with around 25-30 minutes of full deploy time. This was getting very annoying when trying to ship a hotfix.

What if we could make it shorter?

What's going on?

The biggest chunk of time in the build process has been asset compilation. With each build, Heroku was installing all npm dependencies (using yarn) and compiling webpacker assets from scratch. It took very long with each build, even if we didn't touch any code (deploying the same code twice).

Something didn't smell right as on local dev environment if there were no changes, the process ran in less than a second.

As time passed, we also noticed that our slug size was growing, which causes deploys to be longer. There were lots of uncleaned leftovers after each webpacker build. Simple repo purging was enough to get rid of this problem, but it required manual command execution.

It was clear that there are a couple of problems:

  • Yarn cache isn't re-used.
  • Full webpacker build runs even if JavaScript code was not touched.
  • Old build products were not cleaned.

Getting to a solution

Luckily some people already worked on a solution. With that webpacker will reuse the previous build if JavaScript files were not touched. Otherwise, webpacker will do a full rebuild. On top of that, these changes include a Yarn cache, which remedies problems with dependencies installation (however this could be achieved installing official node.js buildpack from Heroku).

Another problem is that webpacker does not ship with a cleaning task (similar to assets:clean). Without it, the slug size of our application will keep growing in time. Luckily for us, there is a webpack plugin called clean-webpack-plugin that does exactly this.

Combining that plugin and making a little tweak in the solution by @kpheasey we can achieve the effect we want. We included that tweak on our GitHub fork of heroku-buildpack-ruby.

Putting the solution together

This solution works on Rails versions >= 5.1, < 6.0. Though, it should be easy to modify it to work with other versions. :)

Firstly, you need to have clean-webpack-plugin in place:

yarn add clean-webpack-plugin

Now add plugin's config code to config/webpack/environment.js:

// ...
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); environment.plugins.prepend("CleanWebpackPlugin", new CleanWebpackPlugin()); // ...

And the last step – replace the official Ruby buildpack with the custom one in your app's Settings on Heroku.

https://github.com/pilotcreative/heroku-buildpack-ruby.git#feat/yarn-cache

Heroku buildpacks of one of our apps

Now run a full build to build your cache and you're done! For us builds take from 2.5 to 5 minutes depending on what changes are being deployed.

Sources