Framework agnostic, fast zero-downtime Javascript app deployment

Using Grunt, Redis, and S3

A while ago, I was inspired by a talk at RailsConf 2014 called “Lightning Fast Deployment of Your Rails-backed JavaScript app”. In the talk, Luke Melia talks about how Yapp deploys their Rails based Ember app completely independent of the Asset Pipeline using Rake Pipeline, Redis, and S3. I was quite impressed with the idea and wanted to try it out myself since I have been experiencing the pains of deploying the entire Rails app every time I make a change in Ember.

If you are wondering about the benefits of deploying your Javascript app independent of a back-end, do take a look at the video above (deploy in seconds, no downtime). As far as overall architecture, the tl;dr is:

  1. Upload assets (JS, CSS, Images) to S3, drop in CloudFront for CDN distribution.
  2. Push the raw content of index.html to Redis with a unique deployment key.
  3. Keep track of all deployment keys in a manifest.
  4. Load the index.html directly from Rails using the latest deploy to avoid dealing with CORS.
  5. Go to any specific deploy given a deployment key (easy for previewing deploys, etc.)

While I loved the idea, Luke’s approach using Rake Pipeline didn’t work for me completely since it was still dependent on Ruby. It was also a bit more complicated than what I needed for my workflow.


Front-end

My experiment involved Ember CLI with Rails as backend. Ember CLI was a natural choice since it’s the official CLI for Ember and 100% Javascript based. I chose Grunt instead of Rake to run the tasks I needed given its extensive plugin ecosystem. For the sake of simplicity, I used unix timestamps as unique identifiers for each deploy. The new Grunt-based workflow for production environment looks something like this:

  1. Build the app, which is stored in the /dist folder in my case.
  2. Hash all assets using grunt-hashres.
  3. Deploy built assets to S3 using grunt-s3. My folder structure looks something like “bucket_name/1401666715802/assets/b478ca4.app.cache.js”
  4. Prepend the CDN url to all asset references in index.html.
  5. Store index.html to Redis with the timestamp as key.
  6. Store the latest deployment key in a list. The user can also specify a maximum manifest size so that he/she can choose to store the latest 10 or 100 deploys. The Redis related tasks uses my fork of the grunt-redis plugin.

See my sample Gruntfile here.

The great thing about Grunt here is that you can pass in options, which can specify build environments. As you can see in the Gruntfile, most tasks have both a “dev” and a “prod” key. For instance, If you pass in “--prod”, it will use production Redis, otherwise it will use local Redis.


Back-end

On the Rails side, route all requests to an index action, grab the latest deploy from the manifest, and serve out index.html. Since we have a history of all previous deploys stored in Redis, we can simply pass it in as a query param (example.com?deploy_key=123213213) and instantaneously switch to a specific deploy.

See my sample Rails controller here.

The beauty of the above approach is that the Gruntfile should work for any Javascript framework since all the tasks are fairly generic. If you have any feedback, feel free to tweet me at @feifanw ☺.