×


Node js Application with Docker on Ubuntu

Are you trying to build Node.js Application with Docker on Ubuntu?

This guide is for you.


Docker allows you to package an application with its environment and all of its dependencies into a "box", called a container. Usually, a container consists of an application running in a stripped-to-basics version of a Linux operating system.

Here at Ibmi Media, as part of our Server Management Services, we regularly help our Customers to perform Node.js related queries.

In this context, we shall look into how to build a Node.js Application with Docker on Ubuntu 20.04.


How to Build Node.js Application with Docker on Ubuntu ?

Here, let us create an application image. We will then build a container and push it to Docker Hub for future use. 

Finally, we will pull the image from the Docker Hub repository and build another container. 

Hence demonstrating how we can recreate and scale our application.

Before we begin, our Support Experts suggest having:

i. A Ubuntu 20.04 server with non-root Sudo user with key-based authentication.

ii. Docker on the server.

iii. Node.js and npm.

iv. A Docker Hub account.


Now , we will look into how to build a Node.js Application with Docker on Ubuntu:

To begin, you need to create an image for our application, which we can then run in a container. 

The image includes our application code, libraries, configuration files, environment variables, and runtime.

Moving ahead, let us see in detail how our Support Experts build a Node.js Application with Docker on Ubuntu 20.04.


1. Install Application Dependencies

To create our image, we need to make our application files.

i. First, create a directory in the non-root user’s home directory, and navigate to that directory:

$ mkdir node_project
$ cd node_project

ii. Then, create a package.json file with the project's dependencies and other information about the project.

Npm recommends a short and descriptive project name and to avoid duplicates in the npm registry. 

Mention the MIT license in the license field, permitting the free use and distribution of the application code:

~/node_project/package.json
{
“name”: “docker_web_app”,
“version”: “1.0.0”,
“description”: “Node.js on Docker”,
“author”: “First Last <first.last@example.com>”,
“main”: “server.js”,
“scripts”: {
“start”: “node server.js”
},
“dependencies”: {
“express”: “^4.16.1”
}
}

iii. Eventually, save and close the file.


iv. To install our project’s dependencies, we run:

$ npm install

This will install the packages we have listed in our package.json file. 

If we use npm version 5 or later, this will generate a package-lock.json file and copy it to our Docker image.


2. Create the Application Files

Create a server.js file that defines a web app using the Express.js framework:

'use strict';
const express = require(‘express’);
// Constants
const PORT = 8080;
const HOST = ‘0.0.0.0’;
// App
const app = express();
app.get(‘/’, (req, res) => {
res.send(‘Hello World’);
});
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);


3. Write the Dockerfile

Using a Dockerfile allows us to define our container environment and avoid discrepancies with dependencies or runtime versions.


i. Initially, in the project’s root directory, we create the Dockerfile:

$ nano Dockerfile

Our first step is to add the base image for our application that will form the starting point of the application build.


Consider the node:12 image. 

ii. Add the following FROM instruction to set the application's base image:

~/node_project/Dockerfile

FROM node:12

This image includes Node.js and npm.

By default, the Docker Node image includes a non-root node user. We will use the node user’s home directory as the working directory and set it as our user inside the container.

To fine-tune the permissions on our application code in the container, let us create the node_modules subdirectory in /home/node along with the app directory.


iii. In addition, we will set ownership on them to our node user:

~/node_project/Dockerfile

RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app

iv. Then, we set the working directory of the application to /home/node/app:

~/node_project/Dockerfile

WORKDIR /home/node/app

If a WORKDIR is not set, Docker will create one by default, so it is a good idea to set it explicitly.


v. Next, copy the package.json and package-lock.json (for npm 5+) files:

~/node_project/Dockerfile

COPY package*.json ./

Adding this COPY instruction allows us to take advantage of Docker's caching mechanism.


vi. To ensure that all of the application files are owned by the non-root node user, we switch the user to the node before running npm install:

~/node_project/Dockerfile

USER node

vii. Once done, we can run npm install:

~/node_project/Dockerfile

RUN npm install

viii. Next, we copy the application code with the appropriate permissions to the application directory on the container:

~/node_project/Dockerfile

COPY –chown=node:node . .
ix. Finally, we expose port 8080 on the container and start the application:
~/node_project/Dockerfile

EXPOSE 8080
CMD [ “node”, “server.js” ]


The complete Dockerfile looks like this:

~/node_project/Dockerfile

FROM node:12

RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
WORKDIR /home/node/app
COPY package*.json ./
USER node
RUN npm install
COPY –chown=node:node . .
EXPOSE 8080
CMD [ “node”, “server.js” ]

x. Eventually, save and close the file.


xi. Before building the application image, let us add a .dockerignore file:

$ nano .dockerignore

Inside the file, add local node modules, npm logs, Dockerfile, and .dockerignore file:

~/node_project/.dockerignore
node_modules
npm-debug.log
Dockerfile
.dockerignore

If we are working with Git then we will also want to add our .git directory and .gitignore file.


xii. Eventually, save and close the file.


The -t flag with docker build will allow us to tag the image with a memorable name. 

xiii. Since we are going to push the image to Docker Hub, we will tag the image as nodejs-image-demo.

Make sure to also replace our_dockerhub_username with our own Docker Hub username:

$ sudo docker build -t our_dockerhub_username/node-web-app .

The . specifies that the build context is the current directory.

It will take a minute or two to build the image. 


xiv. Once done, check our images:

$ sudo docker images

Our output will be as follows:


Output
REPOSITORY TAG ID CREATED
node 12 1934b0b038d1 5 days ago
<our username>/node-web-app latest d64d3505b0d2 1 minute ago

xv. To build the container we run:

$ sudo docker run –name node-web-app -p 80:8080 -d our_dockerhub_username/node-web-app

Once done, we can inspect a list of our running containers with docker ps:

$ sudo docker ps

Output

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e50ad27074a7 our_dockerhub_username/node-web-app “node server.js” 8 seconds ago Up 7 seconds 0.0.0.0:80->8080/tcp node-web-app

We can now visit our application by navigating our browser to our server IP without the port:

http://our_server_ip

4. Using a Repository to Work with Images

i. The first step to pushing the image is to log in to the Docker Hub account:

$ sudo docker login -u our_dockerhub_username

Logging in this way will create a ~/.docker/config.json file in our user’s home directory with our Docker Hub credentials.


ii. Then we can now push the application image to Docker Hub:

$ sudo docker push our_dockerhub_username/node-web-app

Let us test the utility of the image registry by destroying our current application container and image. 

We will then rebuild them with the image in our repository.

iii. First, list the running containers:

$ sudo docker ps

Output

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e50ad27074a7 our_dockerhub_username/node-web-app “node server.js” 3 minutes ago Up 3 minutes 0.0.0.0:80->8080/tcp node-web-ap

iv. Using the CONTAINER ID listed in the output, stop the running application container:

$ sudo docker stop e50ad27074a7

v. List all the images with the -a flag:

$ docker images -a

Output

REPOSITORY TAG IMAGE ID CREATED SIZE
our_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 7 minutes ago 73MB
<none> <none> 2e3267d9ac02 4 minutes ago 72.9MB
<none> <none> 8352b41730b9 4 minutes ago 73MB
<none> <none> 5d58b92823cb 4 minutes ago 73MB
<none> <none> 3f1e35d7062a 4 minutes ago 73MB
<none> <none> 02176311e4d0 4 minutes ago 73MB
<none> <none> 8e84b33edcda 4 minutes ago 70.7MB
<none> <none> 6a5ed70f86f2 4 minutes ago 70.7MB
<none> <none> 776b2637d3c1 4 minutes ago 70.7MB
node 12 f09e7c96b6de 3 weeks ago 70.7MB

vi. We can remove the stopped container and all of the images with the following command:

$ docker system prune -a

Type y when prompted in the output to confirm. Make note that this will also remove the build cache.


vii. With all of the images and containers deleted, we can now pull the application image from Docker Hub:

$ docker pull our_dockerhub_username/node-web-app

List the images once again:

$ docker images

Output

REPOSITORY TAG IMAGE ID CREATED SIZE
our_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 11 minutes ago 73MB

We can now rebuild the container.

$ docker run –name node-web-app -p 80:8080 -d our_dockerhub_username/node-web-app

Then, list the running containers:

$ docker ps

Output

CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES
f6bc2f50dff6 our_dockerhub_username/nodejs-image-demo “node server.js” 4 seconds ago
Up 3 seconds 0.0.0.0:80->8080/tcp node-web-app

Finally, visit http://our_server_ip once again to view the running application.


Common errors encountered while building a Node.js Application with Docker on Ubuntu

1. node: not found

Recently one of our customers received the following error while running dockerfile that tries to run server.js:

sh: 1: node: not found

This might be due to the way node.js was installed from the Ubuntu repository. 

Ubuntu repository usually serves pretty old versions of packages and the whole node/nodejs package naming problem is pretty confusing.

In order to avoid this, rather than using the Ubuntu repository, use one of the official node images from the Docker repository.

However, if we go ahead with our own Ubuntu-based image with node.js, look at installing node.js directly from the source.


i. To get shell access to the container:

docker run -it –rm <image name or hash> /bin/bash

Once we run this command on the host, we will have a new bash shell prompt. 

Now we have shell access to a temporary container based on our image.

ii. Try node –version or nodejs –version to see if it is installed. 

If that works, try which node or which nodejs to find the path to the node binary.

If we can find the binary, we can edit our Dockerfile to include a link from somewhere in our path to that binary.

For example, assuming which nodejs gives /usr/bin/nodejs, we can use the link in our Dockerfile:

RUN ln -s /usr/bin/nodejs /usr/bin/node

2. npm: command not found

When we run the container and open a bash shell, executing the command npm -V we may come across:

root@server:/# npm -v
bash: npm: command not found
root@server:/# nvm ls
N/A
node -> stable (-> N/A) (default)
iojs -> N/A (default)

In order to solve this install Node without using the nvm tool:

# curl -sL https://deb.nodesource.com/setup_7.x | bash
# apt-get install -y nodejs

Now, while logging in to the container, it can find the Node executable.


[Need urgent help with Docker procedures? We'd be happy to assist. ]


Conclusion

This article will guide you on how to build a Node.js Application with Docker on Ubuntu. npm install downloads a package and it's dependencies. #npm install can be run with or without arguments. When run without arguments, npm install downloads dependencies defined in a package. json file and generates a node_modules folder with the installed modules.

The #docker build command builds Docker images from a Dockerfile and a “context”. 

A build's context is the set of files located in the specified PATH or URL . The build process can refer to any of the files in the context. 

With Dockerfile written, you can build the image using the following command: $ docker build .

Containerizing an #application is the process of making it able to run and deploy under Docker containers and similar technologies that encapsulate an application with its operating system environment (a full system image).

Some Docker #commands:

1. docker run – Runs a command in a new container.

2. docker start – Starts one or more stopped containers.

3. docker stop – Stops one or more running containers.

4. docker build – Builds an image form a Docker file.

5. docker pull – Pulls an image or a repository from a #registry.