Want to know how to build Node.js Application with Docker on Ubuntu? We can help you.
Docker allows us to run applications as containers, offering a lighter-weight alternative to virtual machines.
As part of our Docker Hosting Support, we assist our customers with several Node.js queries.
Today, let us see how our Support Techs build a Node.js Application with Docker on Ubuntu 20.04
Node.js Application with Docker on Ubuntu
In this article, 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 Techs suggest having:
- A Ubuntu 20.04 server with non-root Sudo user with key-based authentication
- Docker on the server
- Node.js and npm
- A Docker Hub account
How to build a Node.js Application with Docker on Ubuntu
The starting point is typically creating 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 Techs build a Node.js Application with Docker on Ubuntu 20.04.
-
Step 1 – Install Application Dependencies
To create our image, we need to make our application files.
First, create a directory in the non-root user’s home directory, and navigate to that directory:
$ mkdir node_project
$ cd node_project
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”
}
}
Eventually, save and close the file.
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.
-
Step 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}`);
-
Step 3 – Write the Dockerfile
Using a Dockerfile allows us to define our container environment and avoid discrepancies with dependencies or runtime versions.
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. 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.
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
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.
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.
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
Once done, we can run npm install:
~/node_project/Dockerfile
…
RUN npm install
Next, we copy the application code with the appropriate permissions to the application directory on the container:
~/node_project/Dockerfile
…
COPY –chown=node:node . .
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” ]
Eventually, save and close the file.
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.
Eventually, save and close the file.
The -t flag with docker build will allow us to tag the image with a memorable name. 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. 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
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
-
Step 4 – Using a Repository to Work with Images
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.
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.
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
Using the CONTAINER ID listed in the output, stop the running application container:
$ sudo docker stop e50ad27074a7
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
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.
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.
Few common errors
-
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.
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.
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
-
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 help with the procedures? We’d be happy to assist]
Conclusion
In this article, we saw how to build a Node.js Application with Docker on Ubuntu. Our Support Techs has given a detailed description of an effective way they employ.
0 Comments