Let’s walk through a Node.js server development process:

  1. Install the newest Node.js version
  2. Install a database for your app (Mongo? CouchDB? MySQL?)
  3. Maybe add ElasticSearch if you want to add some super-fast queries
  4. Test your app on your machine
  5. Deploy it
  6. Watch it fail in production

Well, we all walked this path at least one time, right?
Developing a Node.js app sometimes can be challenging, ’cause you may need a lot of external dependencies (databases, search engines, etc) and sometimes you only test it on your local environment, with different versions of a X database, or the Y service… and then it crashes in production, ’cause the environment is totally different.

Introducing Docker

Docker is a simple but powerful OpenSource project which helps you to develop and deploy any software in any language on a container, which is basically a kind of Virtual Machine.
With Docker, you can run any software/language without even installing it on your machine!

Let’s see an example:

You’ve developed a new incredible chat app with the following stack:

  • Database: CouchDB

  • Backend: Node.js + Koa

  • Search Engine: ElasticSearch

  • Frontend: Koa + Next.js

You’re currently hosting it on AWS, but for some reason you want to switch to Google Cloud.
Without Docker, you need to recreate the whole environment on Google Cloud, so you need to get a new server, install every dependency, test your code, then hope that everything works fine.

With Docker, you just deploy your container, which contains all the informations needed to create the production environment.

So, instead of getting a server for each service above, you just need to deploy the same containers you were using on AWS on Google Cloud!

Another great advantage of using Docker, is that you don’t need to reproduce the production environment on your machine: you just install every dependency on a separate container, then orchestrate them (with Minikube or docker-compose) while developing and you’re done! The exact same dependencies will be deployed in test/production, which means no more “works on my machine” excuse!

Setting Up Docker for Node.js

Node.js on Docker

First of all, you need to create a Dockerfile.
We’ll write all the configurations about our docker image inside that file:

FROM node:10.15.3-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install
RUN npm install -g pm2

COPY . ./

EXPOSE 3000

CMD npm run start

Let’s analyze the file above:

  • On line 1 we declare which image we want to use. In that case, we’ll use the official Node 10.15.3 image. You can find more images on DockerHub.

  • On line 3, we create the directory which contains our application code (inside the image).

  • On line 5, we copy both package.json and package.lock.json in our WORKDIR path.

  • On line 7, we run npm install in order to (of course) install all dependencies listed in our package.json file

  • On line 8, we install the awesome PM2 process manager, which will start our application in a supervised mode.

  • On line 10, we copy every file in our current working directory, inside the WORKDIR image path.

  • On line 12, we declare which TCP port to expose. For example, if your Express server works on port 3000, that is the port to expose.

  • Last but not least, on line 14 we declare which command will boot up our application. In that case, the command is npm run start.

Great! Now, let’s pretend we have a simple Express.js server to be Dockerized:

const express = require("express");
const app     = express();

app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(3000, () => console.log('Server ready on http://localhost:3000'));

Our package.json file should look like this:

{
  "name": "my-super-app",
  "version": "1.0.0",
  "description": "Simple Docker project for JSMonday",
  "main": "server.js",
  "scripts": {
    "start": "pm2-runtime start ./server.js --name node_app"
  },
  "author": "Michele Riva <ciao@micheleriva.it>",
  "license": "Unlicense",
  "dependencies": {
    "express": "4.16.4"
  }
}

Let’s create another file called .dockerignore where we’ll list the files to be ignored by docker:

.git
*Dockerfile*
*docker-compose*
node_modules

Great! Now we can start our Docker image! Run the following command inside your project folder:

docker build -t my-awesome-node-app .

We used the -t (tag) flag, so when we run the command docker images, we’ll be able to find our image with the given name:

REPOSITORY            TAG     IMAGE ID      CREATED         SIZE
my-awesome-node-app   latest  b88102119539  2 minutes ago   400MB

Now let’s boot up our image:

docker run -p 3000:3000 -d my-awesome-node-app

Let’s go to http://localhost:3000 and see if it works!

Node.js Express.js Docker

We just Dockerized our first Node.js application! Greatest thing about that? You don’t even need to install Node.js on your machine in order to get that Hello World page! It’s all managed by Docker!

Next steps

At this point, you may need to manage your dependencies (Databases, frontends, API servers etc), so you have a bunch of options here.

  1. Create a Docker image for every dependency. Pretty hard to maintain, not a good solution!

  2. Use docker-compose in order to build multiple images at once (once for each dependency).

  3. Use Minikube and Kubernetes for orchestrating multiple containers on both development and production environment.

Did you like this article? Consider becoming a Patron!

This article is CC0 1.0 (Public Domain) licensed.