Wednesday March 25, 2015 - tags:    node.js, docker, microservice

Creating a docker image for a node.js service




Introduction

This is the third in a series of posts where I explore how to setup a dockerised micro-service node.js architecture. In this post we will create a notification service using node.js and a Dockerfile to create a new docker image for our service.

The source code for these posts can be found here: dockerised-micro-service-node-js-architecture

The introduction and future posts to this series can be found here:
http://blog.airasoul.io/dockerised-micro-service-node-js-architecture/

In this post

Creating a notifications service

Lets create a simple notifications service using node.js and express.

The service has a single endpoint:

GET /notifications  
Response:  
[
  { _id: "54dfc5919fc493cc4fc6bf2c", title: "notification one" }
]

Lets create a folder notifications and add a package.json file. npm install these dependencies.

{
  "name": "node-docker-microservice",
  "version": "0.0.1",
  "dependencies": {
    "express": "^4.11.2",
    "mongojs": "^0.18.1"
  }
}

Below is a simple express application app.js with a single route notifications listening on port 3000.

'use strict';

var express     = require('express');  
var routes      = require('./routes');  
var app         = express();

app.get('/notifications', routes.notifications);

app.listen(3000);  

Our route.js module has a single function that simply queries mongodb for all notifications, it will return either a 400 error, or a 200 ok with a notifications array.

'use strict';

var db = require('./db');

module.exports.notifications = function(req, res){  
  db.notifications.find({}, function (err, notifications) {
    if (err) {
      return res.status(400).json('Error');
    }

    return res.status(200).json(notifications);
  });
};

We need to connect to mongodb, so our db.js module uses mongojs a simple node.js module that implements the offical mongo api. The connect function simply creates or returns a Mongo instance; our mongo configuration is stored in config.db.

'use strict';

var Mongo  = require('mongojs');  
var config = require('./config');  
var db     = null;

var connect = function(){  
  if (db) { return db; }

  db = new Mongo(config.db.url, config.db.collections);
  return db;
};

module.exports = connect();  

Our config.js module contains configuration for mongodb. The DB_PORT_27017_TCP_ADDR and DB_PORT_27017_TCP_PORT environment variables are provided by docker and represent the mongodb host and port we will pass in on the command line when we run the container. We also default these values to localhost, 27017 respectively so we can also run this application outside of a docker container.

'use strict';

var name         = '/docker';  
var address      = process.env.DB_PORT_27017_TCP_ADDR || 'localhost';  
var port         = process.env.DB_PORT_27017_TCP_PORT || '27017';

module.exports = {  
  db : {
    collections : ['notifications'],
    url  : 'mongodb://' + address + ':' + port + name
  }
};

Testing our notification service

Before attempting to run our notifications service in a docker container, lets test it outside of docker.

First lets start by importing the following notifications:

[
  { "title" : "notification one",   "status" : "active"  },
  { "title" : "notification two",   "status" : "active"  },
  { "title" : "notification three", "status" : "active"  },
  { "title" : "notification four",  "status" : "active"  },
  { "title" : "notification five",  "status" : "deleted" }
]
Import notifications

Make sure you have mongodb installed locally and run the following:

$ mongoimport --db docker --collection notifications --file fixtures/notifications.json --jsonArray

We can start the application by simply running:

$ node app

Now test the service with curl.

curl http://localhost:3000/notifications  

Create a docker image

A Dockerfile is a script containing commands which are executed in order to create a new docker image. Below is our Dockerfile, its located in the root of our notifications service. Lets examine it:

The docker file inherits from a base image dockerfile/nodejs; dockers node.js image. This image will provide us with an ubuntu vm with node.js installed on it. We specify the maintainer and then add our package.json file to a temp folder /tmp/package.json. We navigate to it and install our package.json files dependencies with npm install. We then copy the temp node_modules folder to a src folder.

We set the WORKDIR working directory to /src, all RUN and CMD commands will execute from here. We ADD all files from the current folder location to /src and EXPOSE port 3000, as the port to listen on.

The next line is a little different; it only executes when we run a container using this image. The command CMD runs node app.js starting our notifications service.

FROM dockerfile/nodejs:latest

MAINTAINER Andrew Keig, andrew.keig@gmail.com

ADD package.json /tmp/package.json  
RUN cd /tmp && npm install  
RUN mkdir -p /src && cp -a /tmp/node_modules /src/

WORKDIR /src  
ADD . /src

EXPOSE 3000

CMD ["node", "app.js"]  

Build a docker image

Lets build our notification service docker image. First we need to add a rule to port forward on port 3000.

$ boot2docker stop
$ VBoxManage modifyvm "boot2docker-vm" --natpf1 "tcp-port3000,tcp,,3000,,3000";

Lets build the image. -t tags the build image with airasoul/notifications; importantly the . at the end tells us what to build, so make sure you are in the same folder as the Dockerfile.

$ boot2docker start
$ docker build -t airasoul/notifications .

Run notification service in a container

Before running our service we should, start our mongo container. Run the mongo container and import some notifications.

$ docker run -d -p 27017:27017 --name mongodb dockerfile/mongodb
$ mongoimport --db docker --collection notifications --file fixtures/notifications.json --jsonArray

Lets run a few commands against our mongo container and make sure this is working correctly.

$ mongo
$ show dbs
$ exit

You should see the following output.

admin   (empty)  
docker  0.078GB  
local   0.078GB  

Lets run our notification service, connected to a mongodb container. The -p switch specifies 3000:3000 as the host service port and links this to the container port of the same.

We then --link to mongodb:db; the mongodb container. The db part of this sets the environment variable port and address, as mentioned above DB_PORT_27017_TCP_ADDR and DB_PORT_27017_TCP_PORT. We then specify the airasoul/notifications image to be used for this container.

$ docker run -p 3000:3000 --link mongodb:db airasoul/notifications

Get configuration and status information

Below are a couple of commands to get config and status information for your containers.

Get environment variables

You can see port and host environment variables including the mongodb ports with.

$ docker run -p 3000:3000 --link mongodb:db airasoul/notifications env
Inspecting our notification service container

We can further configuration and status information using the docker inspect command.

$ docker inspect <container_id>