This is the 14th in a series of posts leading up Node.js Knockout, and covers deploying your Node.js app to a Linode VPS.

A Linode VPS means freedom. You get everything from the Linux kernel and root access on up. All managed by a simple yet very powerful control panel.

This post will get you going with a Node.js/Socket.IO app on Linode.

Do I need to sign up with Linode?

Short answer: no. Linode will be providing VPSs during the competition and judging period for teams as a deploy option. More details before the competition.

Pick your Linux distro

Linode has a choice of Linux distribution. This blog post will be using Ubuntu 11.04. The same instructions definitely apply to Ubuntu 10.04 (32-bit or 64-bit) and are easily adaptable to Debian.

32-bit or 64-bit?

If you’re going to be installing something like mongodb, 64-bit is highly recommended. If you’re going to be using redis heavily, maybe you want 32-bit. The choice is up to you and it’s possible to wipe the VPS later and pick a different option, but that could take time you don’t have.

TL;DR StackScripts

Setting up your own server from scratch is not for the faint of heart. If you know what you’re doing, then this guide should be full of good directions to take: read on. If you don’t want to muck around with apt-get, upstart, sudoers, and more, use the StackScript:

Deploy using StackScripts
Search for knockout

After your linode is booted up from that, skip to the deploy script section.

Boot and SSH in

Boot your Linode from the Linode dashboard. When creating your Linode, you picked a root password. SSH in as root to complete the next steps. Your linode’s IP address can be found on the Remote Access tab from the control panel.

All of the commands below prefixed with # should be run as root. Any prefixed with $ are run as the deploy user (set up later).

Install git and other tools

We’ll definitely need git and most likely a C compiler (for compiling node modules with C-bindings):

# apt-get install -y build-essential curl
# apt-get install -y git || apt-get install -y git-core

Install node.js

The easiest way to install node.js is via apt:

# apt-get install -y python-software-properties
# add-apt-repository ppa:chris-lea/node.js
# apt-get update
# apt-get install -y nodejs nodejs-dev

If you’d really rather compile from source:

# apt-get install -y build-essential python libssl-dev
# curl -O
# tar xzf node-v0.4.11.tar.gz
# cd node-v0.4.11
# ./configure
# make install

Install npm

# curl | clean=no sh

By default, this will install npm in /usr/bin when using the apt-get method above. When you install modules with npm later, they’ll get installed to your local working directory. If you use npm to install modules globally, you’ll need to be root or use sudo: sudo npm install -g coffee-script.


Now that we have node and npm installed on our linode, we want to get our app out there and running. The rest of this guide uses a version of the Knocking out Socket.IO example app to deploy with.

Setting up a deploy user

No one wants their own code running as root, right? Create a deploy user to own where your app code lives and switch to it:

# useradd -U -m -s /bin/bash deploy
# su - deploy

Set NODE_ENV to production

Setting NODE_ENV will tell frameworks such as Express to turn on its caching features. It’s also important for telling our knockout check-in module to notify us of a deploy from your server.

$ echo 'export NODE_ENV="production"' >> ~/.profile

Add to known_hosts

$ ssh
The authenticity of host ' (' can't be established.
RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added ',' (RSA) to the list of known hosts.
Permission denied (publickey).

You can safely ignore the “Permission denied (publickey)” part for now.

SSH keys

Drop your SSH public keys into /home/deploy/.ssh/authorized_keys to make deploying and SSHing in much easier later. While you’re at it, you should add the Knockout organizers’ public ssh key for auditing at the end of the competition. SSH access for organizers is a required step in deploys to Linode.

$ curl >> ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys

Upstart script

We’re going to use upstart to make sure our node app is running on server start along with restarting it if it should die. As root:

# cat <<'EOF' > /etc/init/node.conf 
description "node server"

start on filesystem or runlevel [2345]
stop on runlevel [!2345]

respawn limit 10 5
umask 022

  . $HOME/.profile
  exec /usr/bin/node $HOME/app/current/app.js >> $HOME/app/shared/logs/node.log 2>&1
end script

post-start script
  PID=`status node | awk '/post-start/ { print $4 }'`
  echo $PID > $HOME/app/shared/pids/
end script

post-stop script
  rm -f $HOME/app/shared/pids/
end script

To use upstart as the deploy user, we’ll have to give it sudo permission for stopping and starting the node process:

# cat <<EOF > /etc/sudoers.d/node
deploy     ALL=NOPASSWD: /sbin/restart node
deploy     ALL=NOPASSWD: /sbin/stop node
deploy     ALL=NOPASSWD: /sbin/start node
# chmod 0440 /etc/sudoers.d/node

Deploy script

Ok! The server’s ready. Now onto our local development machine setup.

We’re going to use (a fork of) TJ's deploy shell script to make deploying our code repeatable and easy for everyone on the team. On your local machine, in your project’s root directory:

$ curl -O
$ chmod +x ./deploy
$ cat <<EOF > deploy.conf
user deploy
host __96.126.102.14__
ref origin/master
path /home/deploy/app
post-deploy npm install && [ -e ../shared/pids/ ] && sudo restart node || sudo start node
test sleep 1 && curl localhost >/dev/null

Make sure to change the IP address and GitHub repo to ones for your team.

Now run ssh-add && ./deploy linode setup to get things setup:

$ ssh-add && ./deploy linode setup
  ○ running setup
  ○ cloning
Cloning into /home/deploy/app/source...
  ○ setup complete

And finally ./deploy linode to deploy:

$ ./deploy linode
  ○ deploying
  ○ hook pre-deploy
  ○ fetching updates
Fetching origin
  ○ resetting HEAD to origin/master
HEAD is now at bfadb51 bind to port 80 and downgrade
  ○ executing post-deploy `npm install && [ -e ../shared/pids/ ] && sudo restart node || sudo start node`

node start/running, process 13623
  ○ executing test `sleep 1 && curl localhost >/dev/null`
  ○ successfully deployed origin/master

You should commit both ./deploy and ./deploy.conf to your git repo. That way, anyone on your team can just run ./deploy linode later to push a new deploy out. Make sure to add everyone’s SSH keys to the deploy user too.

An aside on SSH agent forwarding

We’re taking advantage of SSH agent. In practice, this translates into needing to run ssh-add at inopportune times to get around errors like Permission denied (publickey). Read through the Wikipedia article on it though if you’re heavily concerned about security (or like to geek out about public key cryptography).

Binding to port 80

Take note of the listen call in our app.js:

app.listen(process.env.NODE_ENV === 'production' ? 80 : 8000, function() {

  // if run as root, downgrade to the owner of this file
  if (process.getuid() === 0)
    require('fs').stat(__filename, function(err, stats) {
      if (err) return console.log(err)

It specifically binds to port 80 when run in production mode and otherwise to port 8000. Because we’re running node under upstart (and therefore as root initially), node has the chance to bind to the privileged port 80. Once it’s bound though, it downgrades its uid to the owner of the app.js file, namely our deploy user. This is much more secure than running your app as the root user.

Try it out

You should now be able to hit your linode directly and see your app running!


If you run into any problems, we’re here to help. Email or try us on Twitter.

Blog comments powered by Disqus