We’ve been using Grunt as a build tool for our nodejs apps, and it’s brilliant. It lints, it configures, it minifies, it tests and it packages.
As we move towards getting our first node app into production, we were looking at ways to deploy it. First we thought of Capistrano.
Capistrano is a fully featured deployment framework written in ruby and levering rake style tasks. It’s extremely powerful and very robust, plus there is a gem for node deployments. Alas, it was not to be. After half a day of tail chasing and hoop jumping, it occurred to me that there must be an easier way. Capistrano was encouraging me to make my project fit their template, rather than allowing me to configure the deployment to match my project. When I dug down into the Capistrano source, I found that it was just using ssh and sftp to run remote commands and copy files. But we can simplify this process.
Grunt has been great so far, so I started looking at deploying directly through grunt. We would be deploying to Ubuntu server boxes, so the only tools necessary are ssh and sftp.
There are Grunt modules for nearly everything (linting, minifying, testing, waiting, packaging, shell-exec’ing, tagging, etc.), and rather predictably, sshing (with sftp).
Grunt-ssh provides tasks for executing remote ssh commands, and for copying files using ssh. Let’s dive into some code.
This is going to go over some old ground (available on the Grunt-ssh readme), but we can build up the commands pretty quick.
This is the basic config for executing ssh commands from your Gruntfile:
We’ve registered a command, which we can invoke with:
The Grunt-ssh module also provides the ability to specify multiple host configurations (shared between commands), and to select one at runtime:
So when we invoke the grunt task, we can specify a config:
Or we can set it programmatically (inside the Gruntfile)
Grunt-ssh allows you to upload files via SFTP:
There are a couple of options here, so let’s break it down:
This will copy all files from the “package/“ folder locally. If you want to specify only certain types of files, you can use grunt’s standard file globbing.
Optionally strip off an initial part of the path (without it, files would upload to “/path/on/server/package/“).
Putting it all together
We’ve got all the component parts, now lets put it together (plus a few other cool bits).
Note: at the time of writing, there is a bug in Grunt-ssh where the sftp task does not use the shared sshconfig, so if you want the fixed code, use my fork (there is a pull request outstanding)
This snippet assumes that:
- You can connect to your deployment server using ssh
- You are deploying to /var/www/myapp
- You are using forever to run your app
- Your application files are copied to ./package/
(but, since we’re just using bash commands, this is easily configurable)
It should all make sense, the sshexec is just running remote ssh commands (making directories, starting and stopping using forever etc). Let’s just re-iterate what this is doing:
sshexec:stop: stops the app (assumes you’re using forever)
sshexec:make-release-dir: this will create the folder /var/www/myapp/releases/[current-date-time]
sshexec:update-symlinks: this will create a symlink from /var/www/myapp/current to the release folder we just created (this means that rolling back is just a case of changing the symlink back).
sftp:deploy: copy the files into place
sshexec:npm-update: installs any missing node modules
sshexec:set-config: copy the environment configuration into place
sshexec:start: start the application using forever, pointing the logs to /var/www/myapp/current/logs/
Deploying with one command
Also, if you noticed the production config I specified in that snippet, you’ll see that I didn’t include any host, username or password configs. The
grunt.option('value') allows us to access the command line switches, which means we don’t have to keep any sensitive passwords in source control; we can specify them on the command line.
There are lots of other solutions to the problem of credentials, but this is by far the simplest. It’s worth remembering the Grunt-ssh uses the ssh2 module, so by default it will look to
~/.ssh/ for keys when connecting without a password.
But wait, there’s more
Basically any task you can think of is scriptable using grunt (and some combination of tools). Extra things that we’ve added to our deployment process include:
- Removing the application server from the load-balancer before deploying (and pushing it back when the deployment is complete).
- Making a http request to check the health of the service before going live.
- Rollback from a single command
Oink, Oink …..