Simple Deployment Script with Flightplan.js and PM2

TL;DR

In this post I'm sharing a simple Flightplan.js deployment script that I wrote recently. It follows Capistrano's convention but I find it much easier to configure. With the example script at the end of this post, you can easily do:

  1. fly deploy:dev to deploy through copying your local git files to your remote server
  2. fly restart:dev to restart your remote application.

This script assumes your remote server is installed with Node and PM2. For example, on my remote server I'm using NVM to setup my dev environment, which is why my node_modules looks something like '/home/' + username + '/.nvm/v0.10.25/bin/'.

After you deploy, your app directory /srv/webapp/ will have a few directories:

├── cached_modules
├── tmp
├── releases
└── current -> /srv/webapp/tmp/1437423777498

where cached_modules stores all the nodemodules so it doesn't have to be re-installed on every push; releases stores all previous releases; tmp is a temporary folder to link the most recent release with installed nodemodules; and current is where the app is serving from.

Here's the example script, enjoy!

var plan = require('flightplan');  
var username = <YOUR_SERVER_USER_NAME>;  
var timeStamp = new Date().getTime();  
var tmpDir =  'tmp/' + timeStamp;  
var srvDir = '/srv/webapp/';  
var cachedModulesDir = 'cached_modules';  
var modulesDir = 'node_modules';  
var remoteHostIP = <YOUR_REMOTE_HOST_IP>  
var nodePath = '/home/' + username + '/.nvm/v0.10.25/bin/' 

plan.target('dev', [  
  {
    host: remoteHostIP,
    username: YOUR_SERVER_USER_NAME,
    agent: process.env.SSH_AUTH_SOCK
  }
]);

plan.remote(['deploy'], function(remote) {  
  remote.log('===========Make sure remote directory exists===========');
  remote.exec('mkdir -p ' + srvDir + 'releases');
  remote.exec('mkdir -p ' + srvDir + cachedModulesDir);
  remote.exec('mkdir -p ' + srvDir + 'tmp');
  remote.exec('rm -rf ' + srvDir + 'releases/*');
  remote.exec('rm -rf ' + srvDir + 'tmp/*');
  remote.exec('rm -rf ' + srvDir + 'current');
  for (var x = 0; x < internalScripts.length; x++) {
    var script = internalScripts[x];
    remote.log('--Removing ' + cachedModulesDir + '/' + script);
    remote.exec('rm -rf ' + srvDir + cachedModulesDir + '/' + script);
  }
});

plan.local(['deploy'], function(local) {  
  local.log('===========Copy files to remote hosts===========');
  var filesToCopy = local.exec('git ls-files', {silent: true});
  local.transfer(filesToCopy, srvDir + tmpDir);
});

plan.local(['deploy'], function(local) {  
  local.log('===========Copying package_prod.json to remote===========');
  local.transfer(['package_prod.json'], srvDir + cachedModulesDir + '/package.json');
});

plan.remote(['deploy'], function(remote) {  
  remote.log('===========Install dependencies===========');
  remote.exec( nodePath + 'npm --production --prefix ' + srvDir + cachedModulesDir + ' install ' + srvDir + cachedModulesDir)
});

plan.remote(['deploy'], function(remote) {  
  remote.log('===========Move folder===========');
  remote.cp('-R ' + srvDir + 'tmp' + ' ' + srvDir + 'releases');
});


plan.remote(['deploy'], function(remote) {  
  remote.log('===========Symlink application===========');
  remote.exec('ln -s ' + srvDir + tmpDir + ' ' + srvDir + 'current');
  remote.cp('-R ' + srvDir + cachedModulesDir + '/' + modulesDir + ' ' + srvDir + 'current/' + modulesDir);
});

plan.remote(['deploy', 'restart'], function(remote) {  
  remote.log('===========Stopped all processes in PM2===========');
  remote.exec(nodePath + 'pm2 kill');
});

plan.remote(['deploy', 'restart'], function(remote) {  
  remote.log('===========Restart===========');
  remote.exec('cd ' + srvDir + 'current && ' + nodePath + 'pm2 start start.sh --interpreter bash -n webapp');
});

plan.remote(['deploy', 'restart'], function(remote) {  
  remote.log('===========Checking Status===========');
  remote.exec(nodePath + 'pm2 show webapp');
});

plan.remote(['log'], function(remote) {  
  remote.log('===========Checking Status===========');
  remote.exec(nodePath + 'pm2 show webapp');
  remote.exec(nodePath + 'pm2 logs --timestamp "HH:mm:ss"', {exec: {pty: true}});
});

plan.remote(['killlog'], function(remote) {  
  remote.log('===========killing Log===========');
  remote.exec('kill $(ps aux | grep \'pm2 logs\' | awk \'{print $2}\')');
comments powered by Disqus