Grunt Deployment over SSH with Git

Grunt Logo

Grunt.js is a task runner that comes with various plugins for compiling, building, formatting, etc. within your project. I covered some of the basics of using this tool in my article about using Grunt Watch and LiveReload for real-time compilation.

I recently setup a simple deployment process using Grunt, so I thought I’d share the details. I found a couple deployment-related Grunt plugins out there, but they didn’t really suit my needs. Instead, I opted to simply use the grunt-ssh plugin to connect to my server and run the necessary commands to update, build and restart my application. Let’s take a look at a simplified

module.exports = (grunt) ->


        host: ''
        username: 'someuser'
        agent: process.env.SSH_AUTH_SOCK
        agentForward: true

        command: [
          'cd /home/someuser/app'
          'git pull origin master'
          'npm install'
          'forever stop server.js'
          'forever start server.js'
          'forever list'
        ].join(' && ')
          config: 'someserver'

  grunt.registerTask 'deploy', [


When the task above is executed, by running grunt deploy, a number of things will happen. Grunt will SSH into, with the user someuser. It’ll then move into the /home/someuser/app directory, pull down the latest master from the git repo, run an npm install (which, in my case, also triggers a build task), then restart the application using the forever package.

Most of the commands (and definitely the host/user names) will need to be customized on your end to suite your needs. For instance, if this isn’t a Node.js project, you may not be using npm install or forever. You can replace those commands with the proper commands to build and restart your application. This example also assumes that you already have your Git repo setup in /home/someuser/app. You may have it somewhere else, or you may not be using Git at all, and instead need to pull down files using some other process.

The entire array specified under command can be updated to suite your needs. Because I’m combining the commands using &&, if any one of them fails, the deployment process will stop.

Notes on Authentication

The example above makes a couple assumptions about authentication which may not apply in your case, so I want to offer an alternative.

In the code above you’ll see two lines like this:

agent: process.env.SSH_AUTH_SOCK
agentForward: true

This is telling the SSH process to login into the server using my active set of private keys. For this to work, I must have the server configured to accept my private key. In short, this means I need to have mykey_rsa in the ~/.ssh directory of my local machine, and the matching loaded into ~/.ssh/authorized_keys on the server. If you’re unfamiliar with this setup, or can’t make it work, you can change the agent lines above to use a password instead, like this:


Because with this approach though because you’ll be saving your plain text password in your Gruntfile.


While perhaps not the most ideal candidate for deployment, Grunt can accomplish quite a bit. The example above is very simple, but the solution I’m currently using extends this code to include deployments to multiple servers, uploads to S3, and more. If you’re shopping around for a simple way to deploy your application, give the code above a shot.

  • Pingback: Grunt.js Deployment with Git - Justin Klemm | J...()

  • CodeLearner

    Hey, thanks for the writeup! I’ve got a question: how do you handle failed deployments in production? For example, if a git pull doesn’t work, or if running a database migration breaks mid-way.


    • Justin Klemm

      Hey, that’s an excellent question.

      The code above doesn’t have much failure logic built in. However, one benefit of using “.join(‘ && ‘)” is that the commands will be executed in order and the sequence will halt if one of them fails. So for example, if the “git pull” fails, the sequence will stop. It will *not* continue on to the “npm install” command. That gives you some flexibility to order your commands in a way that prevents issues if a failure happens.

      Of course that’s very remedial failure support. If you need something more robust, you’ll need to do some coding. You may want to introduce “backup” and “rollback” commands of some type. You may also consider options outside of Grunt. Grunt isn’t really an ideal deployment system, but it’s flexibility let’s you do some simple deployments, like the example above.

      Hope that’s helpful!

  • Anthony Bouch

    Nice write-up and I’ll take this approach for smaller deployments. For larger projects it would be great if there was an npm module similar to the ruby capistrano gem.

    • Justin Klemm

      Thanks, Anthony. Yea, definitely agree. I’ve been looking for a node-centric equivalent to capistrano, but haven’t found anything yet. Let me know if come across something.

  • ericrowan

    This is a really helpful article. I’ve been able to work through a few snags but can’t get past one problem:

    > This key should be passed automatically if you use the “agent” authentication method above. However, if things aren’t configured properly, you may get a permission error when the deployment process tries to access your remote Git repository.

    I can’t seem to get my public key to be recognized and running into permissions issues on the server while trying to connect to GitHub. I’d rather not have to create another ssh key on the server and feel like this can be fixed, but I don’t know what else to try. Anyone have any suggestions?

    • Justin Klemm

      Hey Eric, yea, unfortunately this is a common problem. The grunt-ssh package doesn’t have an option for forwarding the key when it connects. I worked briefly with a guy on Twitter to send a pull request that adds the option (, but it hasn’t been merged yet… I’ll try to ping the package author again today and see if we can get it done.

      • ericrowan

        Thanks for getting back to me man. Grunt-ssh is working flawlessly for me other than the issue I noted above, and honesty I think the problem is on my end. Despite my best efforts, I wasn’t able to resolve my “access denied” when attempting to connect to GitHub on my Digital Ocean droplet. I eventually just did what I should’ve done in the first place and created a private key on the remote server.

        • Justin Klemm

          Yea, that’s how I’ve gotten around it too. If we can get the grunt-ssh author to merge that pull request, we’ll be able to pass the authentication over the connection… but until that happens, I think placing a key on the server is the best workaround.

          • Joe Auty

            a private key without a passphrase, correct? I would love to figure out how to do this using keys containing passphrases.

          • Justin Klemm

            Joe, the config above just uses whatever key information you have loaded. Keys with a passphrase should work, you just need to load everything into your session before you deploy, meaning, you may need to do this:

            ssh-add ~/.ssh/mykeyfile

          • Joe Auty

            Thanks! I was missing the “agentForward” argument because I’m evidently not terribly observant. This works great.

          • Justin Klemm

            Haha, not your fault! I actually pulled a fast one on you… Grunt-ssh just added that option recently and when I saw your comment, I was reminded that I should add it to the post… so that line is new. Glad it’s working for you now.

          • Robert J Archer

            Is there usually a problem with this? I’ve added the agentForward, but I’m getting “Error: Authentication failure. Available authentication methods: publickey,gssapi-keyex,gssapi-with-mic”

            Is it possible that node.js doesn’t have access to ~/.ssh? Because I had that problem with using private key authentication.

          • Justin Klemm

            Hi Robert, it may be that your terminal session doesn’t have the key loaded when you run the command. Try manually adding it to your terminal session before running the grunt command. You can do that like this: “ssh-add ~/.ssh/keyfile” (Replacing “keyfile” with your key’s filename).

            Once you’ve done that, try running the grunt command again and see if the authentication works.

          • Robert J Archer

            Yes. That did it. Now the previous comment about adding the key makes sense.

        • Justin Klemm

          Hey man, just a heads up that grunt-ssh now has a ‘agentForward’ option that properly forwards the auth info (and should fix the GitHub issues you mentioned above). I added the options in the code above. Just make sure you update to grunt-ssh 0.12.0+.