Sunday, 1 July, 2018 UTC


Summary

In this post, we are going to look at developing a Node.js application using Docker as the development environment.
Why Dockerize my development?
You may ask, what is wrong with my current setup? Why should I “Dockerize my Node app”?
Well for starters, your development environment need not be the machine on which you are coding.
Let’s say that you are new to a project and you are getting started with your environment setup and you are installing an awesome plugin like grunt-contrib-imagemin and you find that its dependency libpng is missing on your machine. You Google and try to solve the problem yourself because a. you are the new member in the team and you want to prove yourself and b. you don’t know anyone in the team yet.
Everything is good so far, you were able to fix the libpng issue and now you run
npm install
  again and you notice that grunt-yellowlabtools needs phantomjs and for some reason the download fails while installation. Again for the above said reasons, you are too shy to approach someone so you spend the first day on the project installing the dependencies and finally setting up your environment and running your project successfully.
Very productive day one.
Now, imagine, one your first day, all you would need to do is
  1. Install Docker
  2. Run
    docker run -it -p 3000:3000 myproject/my-app:0.1.0
And voila, the app is running on your machine. And you are all set to get started. How about that?

4 reasons why?

Here are a few reasons why you need a “Dockerized” development environment:
  1. Your production environment will almost always never be same as your development environment. So you can directly work on your production environment setup on your local to avoid “surprising” deployment issues.
  2. Easy for your devops team to work and scale with containers in higher environments than your local
  3. One developer machine can run multiple (Node.js) applications in an isolated way
  4. Docker compose lets us run dependent pieces of softwarer in an micro service architecture easily
Now that you feel it is a good idea to Dockerize your development environment, lets look at how.
How to Dockerize a Node.js app?
We are going to follow below steps
  1. Setup Docker
  2. Build a simple Node.js application
  3. Build a Docker image
  4. Create a container from the Image and start developing.
Before we actually get started, let’s look at what is Docker.
What is Docker?
Docker is a computer program that performs operating-system-level virtualization also known as containerization.
To understand the above jibber-jabber, take a look at this video
Quite a simple and powerful concept.
If you would like to dig deeper in the world of containers, checkout this playlist from Vmware

Now that we understand the why, what and how; let’s get our hands dirty by building a Dockerized Node.js app.
Getting Started
First we are going to install Docker

Install Docker

For this tutorial, we are going to use Docker Community Edition (CE). To Install Docker on our machine, navigate to here & download the software for your OS.
Once Installed, you can open up a command prompt/terminal and run 
docker ps
 and you should see something like
➜  ~ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
The above output shows that there are no images running.
Now, lets quickly test drive Docker.
Run 
docker run busybox echo "hello from busybox"
  and you should see that the busybox image downloaded locally and run the echo command that we have provided. The output would be as follows.
Now that we got the Docker installation done & verified, let’s move on.

Build a Node.js App

Now, we will setup a simple Node.js app. If you are planning to run this application on your local machine, make sure Node.js is setup on your machine. You can follow this post Hello Node. I am using the following versions
➜  ~ node -v       
v8.11.1
➜  ~  npm -v
5.6.0
If you are going to run the app directly in a docker container, you can skip local installation of Node.js.
Anywhere on your machine, create a folder named 
docker-node-app
 and open a new terminal/prompt there.
First, we are going to initialise a new Node.js project. Run
➜  docker-node-app npm init --yes
This will create a
package.json
  with default contents. Update it as shown below, as applicable
{
  "name": "docker-node-app",
  "version": "1.0.0",
  "description": "A node.js app that runs inside a docker contianer but lives outside",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": "https://github.com/arvindr21/",
  "keywords": ["docker", "nodejs", "dev", "hotreloading"],
  "author": "Arvind Ravulavaru <[email protected]> (http://thejackalofjavascript.com)",
  "license": "MIT"
}
Do note that I have added a script named 
start
, which launches the app that we are going to create in a moment.
Now, we are going to build a simple app, which prints the environment variables on the machine it is running.
Inside
docker-node-app
 folder, create a file named
index.js
  and update it as shown below
const http = require('http');
const port = process.env.PORT || 3000;

let server = http.createServer((req, res) => {
    let html = `<h1>Environment Variables</h1><br>`;
    // Iterate over `process.env` object and
    // print its values.
    Object.keys(process.env).forEach((k) => {
        html += `${k} = ${process.env[k]} <br>`
    });
    // Set the response status and response content 
    // type header
    res.writeHead(200, {
        'content-type': 'text/html'
    });
    return res.end(html);
});

// start listening
server.listen(port);
console.log(`Server running on port ${port}`);
All we are doing is iterating the 
process.env
 object & building a string with the key and value.

Test drive the Node.js application

Let’s test the application we have built. You can do this only if you have Node.js installed on your machine.
You can simply run
➜  docker-node-app node index.js 
Server running on port 3000
or
➜  docker-node-app nodemon index.js   
[nodemon] 1.17.5
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
Server running on port 3000
If you do not have nodemon on your machine, you can install it as follows
➜  ~ npm install nodemon --global
Once the server is up and running, navigate to http://localhost:3000/, we should see
Isni’t very brave of me to publish my machine’s environment variables out in public :/ 
Now that we have validated that the app is working fine, lets “containerize” it.

Build a Docker image

Now that we have our sample app running, let’s create an image. Inside
docker-node-app
  folder, create a file named
Dockerfile
 and update it as shown below
# A node.js v8 box
FROM node:8

# Who(m) to blame if nothing works
MAINTAINER [email protected]

# Create a working directory 
RUN mkdir -p /usr/src/app

# Switch to working directory
WORKDIR /usr/src/app

# Copy contents of local folder to `WORKDIR`
# You can pick individual files based on your need
COPY . .

# Install nodemon globally
RUN npm install -g nodemon

# Install dependencies (if any) in package.json
RUN npm install

# Expose port from container so host can access 3000
EXPOSE 3000

# Start the Node.js app on load
CMD [ "npm", "start" ]
On line 2 : we set the base image in which our app is going to run. Which is a Node image with version 8 installed.
On line 8: we create a directory where our source code would reside in the container
On line 11: we switch to the working directory
On line 15: we copy our current source code to the image
On line 18: we install nodemon globally.
On line 21: we install any other dependencies defined in 
package.json
On line 24: we expose port 3000 so the host machine can access the app
On line 27: we start the Node.js app on boot of the container
Simple right?
Now to make sure we copy only what we need, we will create a file named 
.dockerignore
 at the root of 
docker-node-app
 and update it as shown below
node_modules
npm-debug.log
Now that we are done with setup, we will build the docker image. From inside  
docker-node-app
  folder, run
➜ docker-node-app docker build -t arvindr21/docker-node-app:0.1.0 .
Do note that 
arvindr21
from the above command is my Dockerhub username. If you are planning to push this image to Dockerhub, it needs to be with your Dockerhub username.
Once the build kicks off, you should see logs as shown below
Sending build context to Docker daemon   7.68kB
Step 1/9 : FROM node:8
8: Pulling from library/node
1c7fe136a31e: Pull complete 
ece825d3308b: Pull complete 
06854774e2f3: Pull complete 
f0db43b9b8da: Pull complete 
aa50047aad93: Pull complete 
42b3631d8d2e: Pull complete 
93c1a8d9f4d4: Pull complete 
5fe5b35e5c3f: Pull complete 
Digest: sha256:420104c1267ab7a035558b8a2bc13539741831ac4369954031e0142b565fb7b5
Status: Downloaded newer image for node:8
 ---> ba6ed54a3479
Step 2/9 : MAINTAINER [email protected]
 ---> Running in 90a25907fd58
Removing intermediate container 90a25907fd58
 ---> 260d8c31364f
Step 3/9 : RUN mkdir -p /usr/src/app
 ---> Running in ace890e16232
Removing intermediate container ace890e16232
 ---> 4651ae6b3eb8
Step 4/9 : WORKDIR /usr/src/app
Removing intermediate container 775bf6bf0c6e
 ---> 9123d4de2b66
Step 5/9 : COPY . .
 ---> 2213441f094e
Step 6/9 : RUN npm install -g nodemon
 ---> Running in c73bb8b92ea9
/usr/local/bin/nodemon -> /usr/local/lib/node_modules/nodemon/bin/nodemon.js

> [email protected] postinstall /usr/local/lib/node_modules/nodemon
> node bin/postinstall || exit 0

Love nodemon? You can now support the project via the open collective:
 > https://opencollective.com/nodemon/donate

npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] (node_modules/nodemon/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

+ [email protected]
added 232 packages in 14.401s
Removing intermediate container c73bb8b92ea9
 ---> 6f56d36ee417
Step 7/9 : RUN npm install
 ---> Running in b99429de7651
up to date in 0.079s
Removing intermediate container b99429de7651
 ---> c0437cefd284
Step 8/9 : EXPOSE 3000
 ---> Running in 66b80b41343b
Removing intermediate container 66b80b41343b
 ---> 2011d0ceb321
Step 9/9 : CMD [ "npm", "start" ]
 ---> Running in 330202b704db
Removing intermediate container 330202b704db
 ---> 39a06e589954
Successfully built 39a06e589954
Successfully tagged arvindr21/docker-node-app:0.1.0
Now, if we run 
➜ docker-node-app docker images
 we should see
➜  docker-node-app docker images                                    
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
arvindr21/docker-node-app   0.1.0               39a06e589954        35 seconds ago      681MB
Awesome! Now our image is ready. We need to create a container from this image and voila our app  will be running from inside an image.

Create a conatiner & Run the app

This is the most important step in this process.
To run our Node.js app in a container, we are going to
  1. Download the code base on our local machine
  2. Point the volume of the container to the folder where the code is downloaded and run the image
  3. Open the local copy of code on your host machine text editor
  4. Start making changes
So, let’s move on. Since the codebase is already on our machine, we are not going to download it. If you project files are hosted on a remote server, download them to your local.
Next, we are going to create a container from the image and point the volume to
docker-node-app
  folder, as shown below
➜ docker-node-app docker run -it -p 3000:3000 -v ${PWD}:/usr/src/app arvindr21/docker-node-app:0.1.0
/usr/src/app
  : is the folder in the container
${PWD}
  : is the folder on the host machine where the code is present. I am running the above command from the same folder, hence I am passing in the present working directory variable
3000
 : is the port mapping between host machine and container.
Note: If you want to run this app as a background process pass a 
-d
 or a deamon flag as shown below
➜ docker-node-app docker run -itd -p 3000:3000 -v ${PWD}:/usr/src/app arvindr21/docker-node-app:0.1.0
If everything goes well, we should see
➜  docker-node-app docker run -it -p 3000:3000 -v ${PWD}:/usr/src/app arvindr21/docker-node-app:0.1.0

> [email protected] start /usr/src/app
> nodemon index.js

[nodemon] 1.17.5
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
Server running on port 3000
Now, navigate to http://localhost:3000/ and we should see something like
Do notice that the values now have updated to the environment variables from the container. Do notice the 
HOSTNAME
 property, it is displaying a value of 
cb0852847690
. Now, from the host machine, run 
docker ps
 and we should see something like
➜  docker-node-app docker ps               
CONTAINER ID        IMAGE                             COMMAND             CREATED             STATUS              PORTS                    NAMES
cb0852847690        arvindr21/docker-node-app:0.1.0   "npm start"         3 minutes ago       Up 3 minutes        0.0.0.0:3000->3000/tcp   blissful_gates
Do not the 
HOSTNAME
 matches the 
CONTAINER ID
.
So, ya, it works.
Now to the awesome part, live reloading.
Live reload the container app
Now, we have our app running in a container and the code base on our host machine, let’s add a change to the code base on our local machine and see the changes reflect in the container app.
Open 
index.js
 and update it as shown below
const http = require('http');
const port = process.env.PORT || 3000;

let server = http.createServer((req, res) => {
    let html = `<h1>Environment Variables</h1>`;
    // Add new code to test the live reload
    html += `<h2>As of: ${new Date()}</h2><br>`;
    // Iterate over `process.env` object and
    // print its values.
    Object.keys(process.env).forEach((k) => {
        html += `${k} = ${process.env[k]} <br>`
    });
    // Set the response status and response content 
    // type header
    res.writeHead(200, {
        'content-type': 'text/html'
    });
    return res.end(html);
});

// start listening
server.listen(port);
console.log(`Server running on port ${port}`);
I have added a new piece of code to display the time.
Save the file on your host machine and we should see the following in the terminal/prompt
➜  docker-node-app docker run -it -p 3000:3000 -v ${PWD}:/usr/src/app arvindr21/docker-node-app:0.1.0

> [email protected] start /usr/src/app
> nodemon index.js

[nodemon] 1.17.5
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
Server running on port 3000
[nodemon] restarting due to changes...
[nodemon] starting `node index.js`
Server running on port 3000
Now, back to http://localhost:3000/ and we should see
Voila! The time appear-th! Keep refreshing to see the value change.
Now, when you are done with the development, you can push the code to the remote repository and shutdown your Docker image. And once when you are ready to code, you can bring up the Docker image.

Adding code files

Okay, so we are able to make changes to an existing file, what about adding a new file. Let’s try that out.
Inside 
docker-node-app
 folder, create a file called as 
date.js
 and update it as shown below
module.exports = function getDate() {
    return new Date();
}
All we have done is externalized the logic to get current date. Now, open 
index.js
 and update it as shown below
const http = require('http');
const port = process.env.PORT || 3000;

const getDate = require('./date.js');

let server = http.createServer((req, res) => {
    let html = `<h1>Environment Variables</h1>`;
    // Add new code to test the live reload
    html += `<h2>As of: ${getDate()}</h2><br>`;
    // Iterate over `process.env` object and
    // print its values.
    Object.keys(process.env).forEach((k) => {
        html += `${k} = ${process.env[k]} <br>`
    });
    // Set the response status and response content 
    // type header
    res.writeHead(200, {
        'content-type': 'text/html'
    });
    return res.end(html);
});

// start listening
server.listen(port);
console.log(`Server running on port ${port}`);
And you should see a message in the prompt/terminal that the server restarted due to changes. Now if we go back to browser and refresh, we should see that the output does not change but we are loading the date from an external file.
Simple and powerful development environment that is development and deployment friendly.
You can find the code used in this post here: arvindr21/docker-node-app

Thanks for reading! Do comment.
@arvindr21
The post Developing Node.js applications in Docker appeared first on The Jackal of Javascript.