justjs: node.js tutorials

New here? You might want to start at the beginning.

7/14 '12

Deploying node.js and mongodb in production with stagecoach

Folks have asked me how I deploy the justjs blog, as well as the vidmood sample app. Fair question. I've explained how to run node apps on your own computer, using silly URLs like http://localhost:3000/, but http://justjs.com/ and http://vidmood.justjs.com/ require reliable hosting and a way to respond on port 80 (the standard HTTP port) to take that ":3000" business away. Even more important, deployment to a production server requires a solid strategy for pushing code to the production server after you've tested it... rather than hacking on your live site and crashing it as you go.

But why stagecoach?

There are several cloud hosting options out there. Heroku and, yes, Microsoft's Azure are cloud platforms that support Node. And Nodejitsu is the up-and-comer as well as being a major contributor to Node.

But you're not limited to cloud platforms. You can also run Node yourself on any Linux server. The Linux VPS (Virtual Private Server) market is highly competitive, and you can easily run many node apps on a single VPS, so there's an opportunity to save a lot of money here. Hosting your own also allows for the possibility of an Intranet site not accessible by the outside world. And you can also guarantee high performance by running on a dedicated server.

For me, the biggest win is the ability to host many apps on a single VPS I've already paid for. But the second biggest win is performance. You probably chose Node in part for speed. But most Node cloud hosting companies don't provide MongoDB hosting that runs on the same CPU. You can use MongoDB hosting companies like MongoDB, but you can expect some latency due to the network connection between your node app and the MongoDB server. That means a delay in the response of your app. And this is completely avoidable by hosting your own apps on a VPS or dedicated server that also runs MongoDB.

That's why I developed stagecoach, an open source solution for deploying and hosting Node apps, as part of my work at P'unk Avenue. Let's check it out.

Requirements for installing stagecoach

stagecoach requires a Unix server to deploy to. It's been tested a lot with Linux in particular, including Ubuntu (which it loves) and CentOS (which it can get by with).

Ubuntu Linux (10.04 or better) is strongly recommended, because stagecoach comes with Ubuntu-specific scripts to take care of a lot of busywork for you:

  • Installing node properly, in such a way that it can be updated
  • Installing npm
  • Installing MongoDB properly, in such a way that it can be updated
  • Setting up "upstart" scripts to start and stop all of your apps properly when your server reboots or shuts down.

Also, Ubuntu lets you upgrade from one major release to another. CentOS and Red Hat don't. Yuck.

Yeah, I like Ubuntu. How'd you notice?

However, you can still use stagecoach on other Linux systems, as long as you don't mind taking care of some of the setup tasks yourself. In fact, I often use it on CentOS.

Installing stagecoach

For simplicity I'll assume you're deploying to Ubuntu Linux.

We'll start by setting up stagecoach on our production server (the Ubuntu server). Later we'll install it on our development Mac as well, in order to use the deployment tools.

Let's start by cloning the stagecoach github repository on our production server. If you're not yet familiar with git, you soon will be, as almost all Node projects use git for version control.

First make sure git is actually installed on your Ubuntu server. Open an ssh connection to your production server as root, or log in as a non-root user and use the sudo command to obtain a root shell your server is set up that way:

sudo bash

Now let's install git with the Ubuntu package manager's apt-get command:

apt-get install git-core

Once this command succeeds, we can install stagecoach from github. Stagecoach insists on being installed in /opt/stagecoach. Every modern Unix system - be it Ubuntu Linux or MacOS X - has a /opt folder. By convention, this folder is an acceptable place for third-party software.

Here's what we need to do:

cd /opt

git clone git://github.com/punkave/stagecoach.git

This command will copy stagecoach from the official git repository to the /opt/stagecoach folder on your server. If you think you might make interesting changes to stagecoach, you could "fork" the project first on github.com and then clone your fork, which gives you the option of pushing your own changes and sending us pull requests with your thoughtfully designed and tested features. But that's up to you.

Now that stagecoach is on the system, we can take advantage of an included script that installs Node and MongoDB correctly, from repositories maintained by responsible folks associated with each project. They aren't official Ubuntu repositories - they are actually better and more up to date when it comes to these packages. The script also configures MongoDB to listen to connections only from the same server it's running on, which is great since our Node apps will be on the same server. You can adjust that, but you should not run without some security restriction for MongoDB.

The script also installs the extremely useful "forever" utility, which automatically restarts Node apps if they should happen to crash. This script is an important part of how we keep sites running in a stable way with stagecoach.

Here's the command to run:

sc-proxy/install-node-and-mongo-on-ubuntu.bash

If you have trouble with this command, triple-check that you are running Ubuntu Linux (at least 10.04). If you still have trouble, please open an issue on github and we'll talk about it.

Now MongoDB is up and running and waiting for connections, and the "node" command should be available at the command line. You can type "node" and press enter to check. Then press "control-D" to exit the interactive node shell.

With Node and MongoDB ready to go, we could start launching apps right away. But only one app can listen on port 80, which means we can only have one app on the server... or can we? That's where sc-proxy comes in. sc-proxy is a simple node app, included in stagecoach, that listens on port 80 for us and forwards connections to Node and non-Node apps as necessary, allowing us to have many apps on one server, much as one does with Apache virtual hosts. In fact, you can point traffic for Apache-based sites to an Apache server listening on a port other than 80. That's all thanks to a great Node module by the Nodejitsu team called node-http-proxy.

We'll set up sc-proxy soon. First let's create a directory to deploy our apps to, and tell stagecoach what Linux account to run them under (since running everything as root is a silly security risk).

First, take note of thenon-root  Linux username you want to run your apps as. I'll use "boutell" in my examples, but you should use your own non-root account that already exists and accepts ssh connections on your server. Indeed, once you've set up stagecoach, you'll never need to do anything as root again, except when stopping and starting the entire stagecoach system.

Now copy /opt/stagecoach/settings.example to /opt/stagecoach/settings and edit the file. Change USER=nodeapps to the user you have in mind, in my case boutell:

USER=boutell

Leave DIR set to /opt/stagecoach (this is not something you can currently change).

Your node apps will be deployed to subdirectories of /opt/stagecoach/apps. Create that folder and use the "chown" command to give it to the appropriate user:

mkdir -p /opt/stagecoach/apps

chown boutell /opt/stagecoach/apps

By default, stagecoach provides hosting for staging sites, and automatically sets them up to respond as subdomains of a shared domain. It's a great strategy for making sites available to your clients before the production domain name is available. But right now our goal is to host an app in production. stagecoach can do this too.

For the sake of example, let's get ready to deploy the "vidmood" app from the previous installment. We want to run it as vidmood.justjs.com. So let's create the /opt/stagecoach/apps/vidmood/data folder, and pop a "hosts" file in that folder containing the hostname we want:

mkdir -p /opt/stagecoach/apps/vidmood/data

echo "vidmood.justjs.com" > /opt/stagecoach/apps/vidmood/data/hosts

chown -R boutell /opt/stagecoach/apps/vidmood

Again we take care to give the folder to the non-root user that will be running the app later.

You can list more than one hostname in that file if you wish (such as example.com and www.example.com). Just list one per line.

The "data" folder for each app is special because it is not overwritten every time we deploy a new version of the app. So both data files (like photos uploaded by users) and server-specific configuration files (like "hosts", "port" and "options.js") should live there. After deployment a symbolic link will make sure this folder is visible as the "data" subdirectory of the folder where the app was launched.

Don't Forget Your App's Local Configuration File

There's one more file that we should pop into the data folder before deploying the app: options.js. This file is specific to the vidmood application we checked out in the previous installment. Recall that the vidmood app reads its settings from data/options.js, and that some of those settings specify Twitter app credentials. To run the app in production, you'll need to register a separate Twitter application. Those credentials don't belong in the data/options.js file in your dev environment. That's why the file is not deployed and is separate on each server.

Use a command-line text editor like "nano" or "vim" to create /opt/stagecoach/apps/vidmood/data/options.js on the production server. Paste in your development options.js settings, then edit them to suit the production environment. The host setting will be different, of course, with no port number. And your Twitter credentials will be different as well.

sc-proxy: our HTTP port 80 traffic cop

Nearly ready to deploy! We still need to set up "sc-proxy," the node app that acts as a traffic cop (aka "proxy server") for all of our node apps, allowing them to share port 80 harmoniously. In the "sc-proxy" subdirectory, you'll need to copy the provided config-example.js file to config.js:

cd sc-proxy

cp config-example.js config.js

Now edit the config.js file. If you are new to using the shell prompt to edit things over an ssh connection, you can use your favorite sftp program to copy it to your machine first, edit it there, and push it back. Or try:

nano config.js

nano is a user-friendly command line editor that should be fairly easy for Mac users to pick up. Just remember to use that "control" key you're used to ignoring.

Here's a reasonable config.js file. PLEASE NOTE: listening on port 80 means that Apache CANNOT listen to port 80 at the same time! You'll need to tweak your Apache configuration if you want both on the same server, as explained below. You could also bind on separate IP addresses if you have them.

module.exports = {
  // Subdomains of this are used for apps that don't have a hosts file
  domain: 'mystagingdomain.com',
  port: 80,
  // Listen on all IP addresses
  bindIp: '0.0.0.0',
  // Do not change this
  appsDir: '/opt/stagecoach/apps',
  // If the request doesn't match any app or hosts file, forward to
  // this port (usually Apache)
  defaultPort: 9898
};

Running Apache At The Same Time

Those who are not running Apache on the same server can skip the "defaultPort" setting entirely. Those who are will need to change:

Listen 80

To:

Listen 9898

And:

NameVirtualHost *:80

To:

NameVirtualHost *:9898

In their Apache configuration. You'll also need to restart Apache before starting up sc-proxy for the first time, so that Apache "lets go" of port 80.

Enough about Apache - let's turn our attention back to Node! In the sc-proxy folder, we're ready to install the npm packages for the sc-proxy app. Thanks to the package.json file this is a snap:

npm install

We could launch sc-proxy manually at this point and put it in the background:

sudo node server.js &

And then start all of the apps, if we had deployed any yet (we do NOT run this command as root, we run it as the non-root user we chose for the USER setting):

./sc-start-all

But since we're running Ubuntu, there's a better way. We can install an Upstart script that allows Ubuntu to automatically start sc-proxy and our apps every time the server boots up, and also makes it easy for us to manually start and stop the whole shebang. In fact, Upstart scripts are what Ubuntu uses to start and stop pretty much everything (at least in Ubuntu 10.04 and newer).

Just copy upstart/stagecoach.conf to the /etc/init folder:

cp upstart/stagecoach.conf /etc/init

Now you can just type:

start stagecoach

To launch sc-proxy and any apps that have been deployed. Of course, we haven't deployed any apps yet! So let's take care of that important detail.

Deploying apps with sc-deploy

stagecoach includes a simple deployment script called sc-deploy. sc-deploy uses the rsync utility to deploy the site in such a way that the cutover from one version of the site to the next is almost instantaneous. That's done by copying the latest code to a new, date-stamped subdirectory, stopping the old version, changing a symbolic link from /opt/stagecoach/apps/vidmood/current to point to the new subdirectory, adding a symbolic link from the new subdirectory to the shared "data" folder, and starting up the new version of the app.

You don't have to sweat those details, though. You just have to install stagecoach on your Mac.

Start by installing stagecoach in /opt, just as you did on the production server. Enter your password when prompted by "sudo" (remember, we are NOT using ssh anymore, we are entering local commands in the Terminal app):

sudo git clone git://github.com/punkave/stagecoach.git

Now make a symbolic link from your /usr/local/bin folder to /opt/stagecoach/bin/sc-deploy so that the terminal can find the sc-deploy command in your PATH:

sudo ln -s /opt/stagecoach/bin/sc-deploy /usr/local/bin/sc-deploy

Just one more step and we can deploy the app! The sc-deploy command is designed to be flexible - it runs "stop," "migrate," and "start" scripts that it expects to find in the "deployment" subdirectory of your app. If you peek in the "deployment" subdirectory of "examples/vidmood-1" in the justjs github repository, you'll see these simple bash scripts. Here's the good news: you can copy them, and you don't have to change them at all for new sites, unless they have special needs. So we won't study them in detail. Their main job is to ensure that each app gets a separate port number to listen to and that the "data" symbolic link is established for each new version deployed. If you have additional migration tasks to carry out when a new version is deployed you can add them to the "migrate" script.

You'll also need a "settings" file. Again, you can copy mine; just be sure to change the PROJECT setting to the name of your app. Also, if your host requires you to make your ssh connections at a nonstandard port number, change SSH_PORT appropriately.

FInally, you'll need a "settings.production" file, with settings specifically for your production server. Mine looks like this:

USER=boutell
SERVER=boutell.com

USER is the Unix user the ssh connection should be made under (usually the same as the USER setting in /opt/stagecoach/settings on the production server). SERVER is, of course, the hostname of your production server. Create this file with appropriate settings for your server and account.

You can also have a "settings.staging" file if you have a separate staging server. A great idea if you need to demonstrate the latest modifications to a client project without pushing them straight to production.

Finally, you'll want an rsync_exclude.txt file. This file tells sc-deploy what files not to deploy. Its function is very similar to .gitignore. Here are the contents of rsync_exclude.txt for vidmood. The data folder, of course, shouldn't be copied to the server because that would crush production data files as well as the data/options.js and data/hosts files. .git and .gitignore are skipped because you don't need to use git commands on production and your git history database can be quite large.

data
.git
.gitignore

If your "deployment" folder is set up properly, it should contain the following files:

migrate
settings
start
rsync_exclude.txt
settings.production
stop

Now we're ready to deploy the app! Just use the sc-deploy command, like this (NOT as root). Be patient, it takes a little while to copy files over the internet tubes sometimes:

sc-deploy production

(If the sc-deploy command is not found, make sure you have a symbolic link to it from /usr/local/bin and that /usr/local/bin is part of your PATH setting.)

IMPORTANT: this command will prompt you for your ssh password, more than once, as it carries out different commands remotely. Pretty soon you'll get sick of that. When you do, just set up your server to trust your computer based on a public key, rather than a password. No changes to your stagecoach configuration are required. The password prompts just go away.

When stagecoach finishes deploying the app, it should become accessible right away. If not, make sure you installed the Upstart script for stagecoach and ran:

start stagecoach

On the production server.

"What if I want to deploy a new version of my app?" Just run:

sc-deploy production

Each time you finish testing a new set of changes. Remember, your app is only down for a split second at the very end of the process when the symbolic link changes over and the app is launched again.

"What happens to the old deployment directories when I deploy new versions?"

They are still there, in /opt/stagecoach/apps/appname/deployments. You can manually purge the older folders here if you wish (not the newest one, of course). There's an open issue in github for me to write a convenient tool for purging old deployments now and then.

"Can I revert to an old deployment?"

Yes, but at the moment this is manual. Note the name of the deployment folder you want to roll back to. Then, on the production server, do this (not as root):

cd /opt/stagecoach/apps/vidmood/current
bash deployment/stop
cd ..
ln -s deployments/VERSION-I-WANT current
cd current
bash deployment/start

Again, there's a github issue open for me to write a handy rollback script. Perhaps you'll contribute it as a pull request. That would be nice.

Make sure your app respects "data/port"!

The vidmood app was written with support for stagecoach in mind. Your app probably wasn't. What do you need to change?

Just one thing: your app needs to look for a file called data/port, and if it's there, read its port number from that file, so that sc-proxy can figure out where to send traffic for the appropriate host. If data/port doesn't exist, feel free to listen on a default port for convenience on your development Mac.

Here's what that code looks like in the server.js file of vidmood:

var port = 3000;
try
{
  // In production get the port number from stagecoach
  port = fs.readFileSync(__dirname + '/data/port', 'UTF-8').replace(/\s+$/, '');
} catch (err)
{
  // This is handy in a dev environment
  console.log("I see no data/port file, defaulting to port " + port);
}
console.log("Listening on port " + port);
app.listen(port);

We made it to the mountaintop!

Yes, that took some doing. But look what we have: a way to deploy as many apps as you can stuff into your server. Once stagecoach is in place, each new app is just a matter of copying the deployment folder, editing two settings files and running "sc-deploy production" for the first time. All of your apps can share port 80, and you can even run Apache at the same time with a little fiddling. And your mongodb connection is pretty much instantaneous because it's running on the same machine. Isn't the future outstanding? Sure, for hugely popular apps you may need more, but this is a great way to get there, and at minimal cost for those of us who run lots of little apps.

Nevertheless, in an upcoming installment I'll talk about how to pull off the same trick with at least one of the cloud hosting options, like Heroku and Nodejitsu. Not everyone wants to be an Ubuntu Linux administrator. Just cool people with lots of friends and toys and pet dinosaurs.

 

blog comments powered by Disqus