Working With Docker and NodeJS - Tutorial
The full source code for this tutorial can be found here: TutorialEdge/Docker/node-docker
In this tutorial, we are going to be looking at how you can dockerize an existing NodeJS application and ultimately leverage the benefits of Docker. We’ll be creating a Docker image that will dynamically pick up changes to a NodeJS application and automatically recompile and rerun our application without having to rebuild and re-run our docker image.
Video Tutorial
Advantages of Docker
Docker offers a number of massive advantages and can drastically reduce the friction of deploying your application to multiple platforms with minimal fuss.
New developers can easily pull down Docker-ized applications and run them with a
couple of commands on their local machine. Application developers explicitly
state within the Dockerfile
everything that application needs in order to run.
Our NodeJS Application
We are going to start off with a fairly simple, 6-line NodeJS Express.JS
application that will simply serve Hello World!
when it’s /
endpoint is hit
with a GET
request.
The application will listen on port 3000
for any incoming requests and will
map those requests against the corresponding route.
Create a new file called app.js
within a new node-docker
directory on your
local machine. Once this is created, add the following:
// app.js
const express = require("express");
const app = express();
app.get("/", (req, res) => res.send(`Hello World!`));
app.listen(3000, () => {
console.log(`Example app listening on port 3000!`);
});
As this uses the express.js
library, we’ll have to figure out a way of getting
this into the Docker container that will be running our application.
If you are familiar with Node.js development, you’ll know that the standard
practice for this is to declare that your application requires express.js
within the package.json
in your application’s directory. Create this now and
add the following to the newly created file:
// package.json
{
"name": "simple-rest-api",
"version": "1.0.0",
"main": "app.js",
"dependencies": {
"express": "^4.16.3"
},
"scripts": {
"start": "node app.js"
}
}
We can test if our application works locally by calling
npm install
within our project directory and then subsequently callingnpm run start
. You should then be able to navigate tohttp://localhost:3000/
and be greeted withHello World!
in your browser.
Dockerizing our Application
The aim of the game here is to get a docker image that is as thin as possible in terms of size and is still able to provide our application with everything it needs in order to run successfully.
We’ll choose a fairly lightweight alpine
based image from which to base our
own Docker image on top of.
FROM node:9-slim
## WORKDIR specifies the directory our
## application's code will live within
WORKDIR /app
## We copy our package.json file to our
## app directory
COPY package.json /app
## We then run npm install to install
## express for our application
RUN npm install
## We then copy the rest of our application
## to the app direcoty
COPY . /app
## We start our application by calling
## npm start.
CMD ["npm", "start"]
Now, we’ve done two distinct COPY
commands within our Dockerfile
, and the
reason for this is to reduce the time taken for us to continually rebuild our
application.
Docker automatically caches the results of each individual command so that they
don’t have to be fully run each time you wish to build a Docker image. By doing
it this way, we can cache the results of our npm install
command so that every
time we build our Docker image, it doesn’t constantly have to reinstall all our
dependencies. This may not take a lot of time for this particular project, but
for larger projects, this can become a massive time drain.
Building our Docker Image
Now that we have defined our Dockerfile
within our application’s directory, we
can go about building our Docker image. We can do this by running the following
docker
command within our terminal.
> docker build -t node-docker .
This will subsequently run through the 6 steps outlined within our Dockerfile
and build our complete Docker image.
Running our Docker Image
Once our Docker image has been successfully built, we can then go about running one or more Docker containers based off this image by running the following command:
> docker run -d -p 9000:3000 node-docker
This will start up a Docker container based off our node-docker
docker image
and expose it on port 9000
on our machine. The -d
flag specifies that we
wish to run this in a detached mode which means that the docker container will
run in the background on our host machine.
It’s important to note that whilst our Node.js application may be listening for requests on port
3000
within the code, by specifying-p 9000:3000
we essentially map any requests to port9000
from our host machine to the underlying docker container listening on port3000
.
Viewing running Docker Containers
In order to view all of the running containers on your local machine, type
docker ps
. This should show our node-docker
container running and the ports
that it’s listening to.
Automatically Picking up Changes
The first thing we’ll have to do in order for our running Docker container to
pick up any changes is mount it to a directory on my host machine. We can
achieve this by using the -v
flag and specifying the current directory of my
application and mapping that to the /app
directory our application lives
within in our docker container.
We can also install and use the nodemon
node_module in order to automatically
watch for any changes to our source files and subsequently kick off our server
with these incorporated changes.
$ npm install --save nodemon
Now that nodemon
has been added to our package.json
as a
dependency, we can update our start
command to run nodemon
:
{
"name": "simple-rest-api",
"version": "1.0.0",
"main": "app.js",
"dependencies": {
"express": "^4.16.3"
},
"scripts": {
"start": "nodemon"
}
}
With this change in place, we can now go ahead and rebuild our docker image:
$ docker build -t node-docker-tutorial .
We can then run this with the -v
flag as mentioned before like so:
$ docker run -it -p 9001:3000 -v $(pwd):/app node-docker-tutorial
If you now navigate to http://localhost:9001
you should see your application
up and running, if you then subsequently make any changes to the app, these
changes will be picked up and automatically reran within our container! Awesome!
Conclusion
In this tutorial, we learned how to implement a docker container that perfectly wraps our NodeJS application. This Docker container is deployable anywhere that can run docker which is a massive advantage! No more worrying about the fact that “it works on my machine”.
If you found this tutorial useful then please feel free to let me know in the comments section below or on twitter: @elliot_f.