Hosting & Deploying NodeJS Apps on Ubuntu

Doing local development with Node is simple. All you have to do is node app.js in the folder where your source code is and your application is working.

Where things get complicated is when you want to put your app in production, on a web server for the whole world to admire it.

If you are coming from PHP or Ruby on Rails you might be used to having a very simple way of hosting & deploying your application. All you have to do is put your code into a specific folder. Every time you have an update, just replace the code. It just works.

Let’s look at how you can host and deploy a production ready NodeJs application.

For this purpose we will need SSH access to a freshly installed server. For this tutorial we will look at doing this on Ubuntu, but all steps are easily reproducible on CentOS or any other flavor of Linux.

Your server can be running in AWS, Rackspace or even in your local VirtualBox. It doesn’t really matter, the steps are always the same.

Getting your hands dirty

I know that you are impatient to learn what your setup will be, so let’s get this out of the way.

First, we will use the Nginx web server to handle all requests from the web.

Requests for static content will be handled directly by Nginx. If we need to support SSL, this will also be handled by Nginx. All other web requests will be handled again by Nginx but forwarded to our Node application code.

Second, to ensure that our node application is always on, even when the application crashes or the server is restarted, we will use upstart or systemd tasks depending on what is available on your system of choice.

Last, we will launch as many instances of our application as cores and cpus has our server machine. The purpose of this is to use the maximum of the available resources. Each instance will listen for requests on a separate port and Nginx will forward the appropriate requests.

If you don’t understand this setup or you’ve never heard some of its components. Don’t worry, everything will be explained in details below.

On the other hand if all of this is very clear to you, you can skip the next few paragraphs and go directly to the actual commands and files that we will use to make it all work.

What does it mean to host an app?

Just like on your development machine you can run node app.js on your server and your code will be executed perfectly.

However, this is far from ideal and there are many problems with it. Let’s look at just a few of them.

Serving static files, like JavaScript, images & CSS, can be done with node but it is not very efficient. It may use too much memory and it might not cache frequently accessed files.

Establishing secure SSL connection is not simple, yet most web applications require it. There are certificate files to handle as well as other small details. Moreover, your application code does not care whether the connection is secure or not, as this will only add unnecessary complication to your logic.

Limiting file uploads is critical for any app which allows file uploading. Otherwise, unintentionally or intentionally a user may try to upload a 10GB (or much bigger) file and crash your server. Implementing this efficiently in Node is hard.

These are just few of the reasons, and there are many more, why you should have something else in front of your node application which will handle user requests.

It should serve static files, establish secure connections, as well as other things and decide when a request should be handled by your application code and when not.

Nginx is one of the most widely used web servers. It can do all of the above and lot more. In addition, it is very easy to setup. That is why we are going to use it.

Use all available resources

Node is famous for being a single threaded process. What this means is that no two things happen at the same time. If your server has multiple cores or processors it will only use one of them.

This is not very efficient. If you can use all of the cores you will be able to handle higher load with the same server and as a result save some money.

One solution is to write some additional code. Node ships with a cluster module which can handle the situation described above.

However, I believe it is better to keep in Node only your application logic, and handling multiple processes is not part of that.

Instead all you have to do on your server is run node app.js multiple times, each time providing a different predefined port the app to listen to. Then Nginx can forward requests to each of these ports.

Always On

The next problem that you will face is to keep your app always on. It may crash or your server may restart or something else could happen. You need to ensure that no matter what, your app is always running.

Moreover, this should happen automatically. You don’t want to wake up in the middle of the night, just because your server restarted due to temporarily power outage (this happens even on Amazon or Rackspace servers).

Fortunately, all Linux instances come with what is called an init system. This are robust systems which can monitor the status of your application, restart it when certain conditions are met and start it when the server itself is started.

In the past, using the init system required writing complicated scripts and everybody hated it.

These days, most modern Linux distributions come with a modern init system called “systemd” which is very easy to use. The only exception until recently was Ubuntu, which up to version 14.10 came with “upstart”, which is also easy to use. Beginning with version 15.04 Ubuntu also uses “systemd”.

Deploying

Now that you know how your are going to run your code, the next question is how you are going to put your code on your server.

We are going to use Git. It has many great features but we are going to use just a few of them, mainly it’s ability to push changes between computers.

Now that you know how it should work in theory, let’s look how it is done in practice.

Install

First, you will need to install all necessary packages on your server.

Nginx

Run the following in your Linux shell.

sudo -s
nginx=stable
add-apt-repository ppa:nginx/$nginx
apt-get update
apt-get install nginx
exit

This will install the latest stable version of Nginx on your Ubuntu server.

Node & NPM

curl -sL https://deb.nodesource.com/setup | sudo bash -
sudo apt-get install nodejs
sudo apt-get install build-essential

This will install the latest versions of Node & NPM. The tools in build-essential are required by some npm modules when installing.

Git

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

After this you will have the latest version of Git on your Ubuntu.

Putting some code on your server

For the purpose of this article we are going to use our base-express repository. It’s a repository that anyone can use to start his Node web project with the Express web framework.

cd /opt/
mkdir app
chown your_user_name app
git clone https://github.com/terlici/base-express.git app
cd app
npm install

I prefer using /opt to contain my application files, but you can choose any other folder.

Now our app is ready to run and all of its modules are installed.

Little customization

Let’s customize a little bit our application. You will see later why.

First replace the app.js file in the root of the folder

var express = require('express')
  , app = express()
  , port = process.env.PORT || 3000

app.engine('jade', require('jade').__express)
app.set('view engine', 'jade')

app.use(require('./controllers'))

app.listen(port, function() {
  console.log('Listening on port ' + port)
})

Now our application has a default port to listen to, but can also listen to any other provided by the environment. In addition, it can no more serve by itself its static files in the public/ folder.

Next, please replace the views/index.jade with the following

doctype html
html
  head
    title Your basic web app structure
    link(href="/public/css/style.css", rel="stylesheet")
  body
    h1 Welcome to your basic web app structure
    p
      | If the title above is red
      | then Nginx is serving static files!

The last file to change is public/css/styles.css

h1 {
  color: red;
}

Running 24x7

Now that we have our application ready, how do we start it and keep it always running?

Up until version 14.10 of Ubuntu the default init system is upstart. From Ubuntu 15.10 and Debian 8, the default init system is systemd.

Upstart

Let’s see how we can run our application forever with upstart. In upstart there are jobs. Each job describes how your application will be running.

Here is the upstart job for the node app

description "App Server"
author "Stefan Fidanov <[email protected]>"

start on (filesystem and net-device-up IFACE=lo)
stop on runlevel [!2345]

respawn

env PORT=5000

chdir /opt/app/
exec node app.js

The start on line ensures that your app will start when the server starts and the file system and network are loaded. The respawn ensures that if your app instance dies for whatever, then it will be launched again.

Put this in /etc/init/node-app-1.conf. Then take the same file, replace env PORT=5000 with env PORT=5001 and save it as /etc/init/node-app-2.conf.

Now you have two upstart jobs node-app-1 and node-app-2. Each can look after one instance of your application. You need to create as many as processor cores you have on your server. For example, if you have two cpus, each with the cores, then you need 4. For the purpose of this article we will imagine that we have 1 CPU with 2 cores.

You can run your app instances with

$ sudo start node-app-1
$ sudo start node-app-2

Your app will handle requests at the ports 5000 & 5001. If one of them crashes or the server is restarted, they will be also restarted. Your application is now really always on.

Systemd

Since Ubuntu 15.04 and Debian 8, systemd is the default init system. Systemd has services. Each service describe how an application is running.

Let’s have a look how the service for our app will look like

[Service]
ExecStart=/usr/bin/node /opt/app/app.js

Restart=always

StandardOutput=syslog

StandardError=syslog

SyslogIdentifier=node-app-1

User=your_app_user_name

Group=your_app_user_name

Environment=NODE_ENV=production PORT=5000


[Install]
WantedBy=multi-user.target

Put this in /etc/systemd/system/node-app-1.service but don’t forget to replace your_app_user_name with the appropriate user name.

Then create another file with the name /etc/systemd/system/node-app-2.service and put the same content but replace 5000 with 5001 and node-app-1 with node-app-2.

These two services describe how each of our app instances should run and where their outputs should go. When the app is killed it will automatically be restarted.

$ sudo systemctl start node-app-1
$ sudo systemctl start node-app-2

These two lines will run our app instances and keep them alive.

To make them also start when the server starts or restarts do the following

$ sudo systemctl enable node-app-1
$ sudo systemctl enable node-app-2

Systemd will now run and keep alive forever both instance of our apps.

Re-deploying your app

With the current setup, if we have some new application code in our repository, all you have to do is the following

$ cd /opt/app
$ git pull

And then if you have upstart do

$ sudo restart node-app-1
$ sudo restart node-app-2

Or if you have systemd do

$ sudo systemctl restart node-app-1
$ sudo systemctl restart node-app-2

Afterwards the latest version will be ready to serve your users.

Configure Nginx

Listening on ports 5000 & 5001 is nice but by default browsers are looking at port 80. Also in our current setup no static files are served by our application.

Here is our nginx configuration

upstream node_server {
   server 127.0.0.1:5000 fail_timeout=0;
   server 127.0.0.1:5001 fail_timeout=0;
}

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    index index.html index.htm;

    server_name _;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_redirect off;
        proxy_buffering off;
        proxy_pass http://node_server;
    }

    location /public/ {
        root /opt/app;
    }
}

This configuration will make available all static files from /opt/app/public/ at the /public/ path. It will forward all other requests to the two instances of our app listening at the ports 5000 and 5001. Basically, Nginx is both a web server and load balancer.

To use this configuration save it in /etc/nginx/sites-available/node-app and then do the following:

sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/node-app /etc/nginx/sites-enabled/node-app
sudo /etc/init.d/nginx restart

This will remove the current default configuration, then it will make active the configuration we just implemented and finally it will restart nginx so that the latest configuration is loaded.

If all works as expected, at the web address of your server you should see the following screen.

Production Working Node App

Where to go from here?

This is just the tip of the iceberg when hosting and deploying node applications.

One thing that can be improved is to create a new user specifically for the node app and another for nginx. This will make the application more secure, as it will have very minimal access.

Something else which you could do and I think I would do in a future article is take everything above and create an Ansible playbook out of it.

Ansible is a great tool to configure and orchestrate servers. It’s really simple. Using this playbook you will be able to launch & deploy even hundreds of servers.


Other articles that you may like

Did you like this article?

Please share it
Enter your email and get our NPM Cheat Sheet for NodeJS Developers and the links to our 5 most popular articles which have helped thousands of developers build faster, more reliable and easier to maintain Node applications.

We are Stefan Fidanov & Vasil Lyutskanov. We share actionable advice about development with Node, Express, React and other web & mobile technologies.

It is everything that we have learned from years of experience working with customers from all over the world on projects of all sizes.

Let's work together
© 2018 Terlici Ltd · Terms · Privacy