Instant Node.js development environment with Docker

OK, let’s suppose you need to run some Node.js code in a Linux machine (could be a MacOS or Windows) but you decided you don’t want to perform a local install of Node.js, you want to run all your code in a Docker container. So, the question is: why would I do that instead of installing a pure Node.js locally using, for example, an excellent tool like NVM?

Well, there’s some advantages and disavantages in taking this approach. The main advantages are:

  • With Docker installed, users can start up your application with one single command. This include downloading all necessary libraries and dependencies.
  • When sending your application to production, you can use the same container that you created in your development environment, you only need to configure things like user permissions and environment variables. This can avoid some common problemns like “oh my God, I swear it worked on my machine”.
  • You don’t need to install some specific version of Node that you certainly will not use in the future. Sometimes you just want to run a server, do something quick and shutdown, or maybe you are testing some open source code that you just got from Github.

Some disavantages are:

  • You need to learn Docker and all the container world. But Docker is a cutting edge technology, so why don’t we start to learn it right now?
  • There are more complexity running code in containers comparing to running the code in your local machine. Containers are a very good thing but you need to master some details to feel comfortable in using them.
  • Sending your application to production is a bit more complicated. You need to configure things like a Docker Registry, orchestration tools, container monitoring and some network details.

A “hello world” Express application

Let’s build a simple Express application. In this example, we will skip unit testing just for save some time, but the same concepts can (and must) be applied to testing and also linting.

Create a package.json file in some folder (you can use npm init) and add the Express dependency:

{
"name": "docker-helloworld",
"version": "1.0.0",
"description": "A simple Express application",
"main": "app.js",
"scripts": {
"start": "nodemon app.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.14.0"
}
}

Now, a simple app.js file:

const express = require('express');
const PORT = 3000;
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});

That’s all for the code. It’s time to run this file in a Docker container. If you didn’t install Docker yet, you can follow the official documentation for Installing Docker Engine.

First of all, you need a Dockerfile:

FROM node:7.4.0
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json /usr/src/app/
RUN npm install
RUN npm install -g nodemon
EXPOSE 3000
CMD [ "npm", "start" ]

A Dockerfile is a file containing Docker instructions that will result, after processed, in a new Docker image. All Docker containers need a base image in order to be executed. You can think a container is an instance of an image. You can’t modify existing images, but you can create a new image based in another image. That’s how Dockerfile help us, you specify a base image, together with some instructions that you need in order to run your container, and you will have another new image ready for be run as a container.

In our Dockerfile, we begin specifying the base image:

FROM node:7.4.0

In this case, we are using the Official Node.js Docker Image hosted on Docker Hub, the official Docker images repository. You also need to specify a tag that corresponds to the Node version. In our file we are using Node version 7.4.0, but feel free to use another one.

Next, we create a folder for our app and set it to our working directory:

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

Now we copy our package.json file, install all dependencies using npm and install nodemon globally:

COPY package.json /usr/src/app/
RUN npm install
RUN npm install -g nodemon

But wait. Why did we copy only package.json file instead all our project files? That’s a good question and the answer is: Docker cache. When Docker is processing Dockerfile, it searches in its cache for the current line being processed. If the instruction is found in cache, it gets the corresponding layer and adds to the image. For a better explanation about Docker layers, read the official documentation.

It’s a good practice to copy only package.json because this file usually don’t change and downloading all dependencies can be a time consuming task.

The last two lines work as expected, exposes port 3000 for the host and runs the npm start command:

EXPOSE 3000
CMD [ "npm", "start" ]

Now we can create our image based on our Dockerfile. To do this, run this command:

docker build . -t docker-node

This command creates an image named docker-node that is ready for be instantiated as containers. Now this image is saved in your local machine and you can see all images with the docker images command.

To create our container, run this command:

docker run --rm -v $(pwd):/usr/src/app -p 3000:3000 docker-node

This command create our container based on image docker-node previously created, exposes the container port 3000 to localhost and shares our current folder to /user/src/app folder. This sharing is useful because we will not need stop our container every time we change some file in our project. You must see an output like this:

[nodemon] 1.11.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node app.js`
Server listening on port 3000

Congratulations, our server is running in a Docker container. You can access http://localhost:3000 to test. Now try to change the app.js file and you will notice that nodemon automatically restarts the server. You only need to create or update the docker-node image if you add more dependencies.

This was a very basic tutorial showing the benefits of using Docker in a development environment with Node.js. I hope you enjoy.

Like what you read? Give Fabio Stapait a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.