Join our Discord Server
Ajeet Raina Ajeet Singh Raina is a former Docker Captain, Community Leader and Arm Ambassador. He is a founder of Collabnix blogging site and has authored more than 570+ blogs on Docker, Kubernetes and Cloud-Native Technology. He runs a community Slack of 8900+ members and discord server close to 2200+ members. You can follow him on Twitter(@ajeetsraina).

How To Build a Node.js Application with Docker in 5 Minutes

5 min read

If you’ve ever searched for Docker + NodeJS, you probably encountered a plethora of resources explaining how to containerize a Node.js application. While following those guides step by step can be effective, I’m here to show you the quickest route to containerize your Node.js app.

Let’s start with the Node.js code you have. It’s a straightforward script that creates an HTTP server listening on port 8080. When a request hits this server, it responds with a simple message and an ASCII art of a whale cheerfully saying, “Hello from Docker!”

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.write(`
          ##         .
    ## ## ##        ==
 ## ## ## ## ##    ===
/""""""""""""""""\\___/ ===
{                       /  ===-
\\______ O           __/
 \\    \\         __/
  \\____\\_______/


Hello from Docker!
`);
  res.end();
});

server.listen(8080, () => {
  console.log('Server started!');
});

Let’s break it down further:

  1. Importing the HTTP Module:
const http = require('http');

Here, we’re importing the built-in NodeJS http module, which allows us to create an HTTP server.

  1. Creating the Server:
const server = http.createServer((req, res) => { // Server logic goes here });

We create an HTTP server using the createServer method provided by the http module. This method takes a callback function as an argument, which is called every time the server receives a request. The req parameter represents the request object, and res represents the response object.

  1. Defining Request Handling Logic:
res.writeHead(200, {'Content-Type': 'text/plain'});
  res.write(`
          ##         .
    ## ## ##        ==
 ## ## ## ## ##    ===
/""""""""""""""""\\___/ ===
{                       /  ===-
\\______ O           __/
 \\    \\         __/
  \\____\\_______/


Hello from Docker!
`);
  res.end();

Inside the request handler callback function, we set the response headers using res.writeHead, specifying a status code of 200 (which means “OK”) and the Content-Type as text/plain. Then, we use res.write to write the response body. Here, we’re sending a multi-line string containing an ASCII art of a whale along with the text “Hello from Docker!”. Finally, we end the response using res.end().

  1. Starting the Server:
server.listen(8080, () => { console.log('Server started!'); });

We use the listen method to start the server listening on port 8080. When the server starts successfully, the callback function is executed, and it logs a message to the console saying “Server started!”.

Generating package.json

Let’s first generate package.json

npm install

The package.json file serves a crucial purpose in Node.js development, particularly when working on larger projects or when collaborating with other developers. While it may seem unnecessary when running a simple script like app.js, it provides several benefits:

  1. Dependency Management: package.json allows you to list all the dependencies your project needs. When you run npm install or yarn install, Node.js will automatically install all the dependencies listed in package.json. This is extremely helpful when your project grows and relies on multiple external libraries.
  2. Version Control: By specifying the exact versions of your dependencies in package.json, you ensure that everyone working on the project uses the same versions. This helps prevent compatibility issues between different versions of dependencies.
  3. Script Management: package.json lets you define custom scripts that can be executed using npm run <script-name>. For example, you could have scripts for running tests, building your project, or deploying it to a server.
  4. Metadata: package.json contains metadata about your project, such as its name, description, version, author, and license. This information is helpful for developers who want to understand your project quickly or for automated tools that may need to interact with it.

Running the Script

So, when you run this script, it creates an HTTP server that listens on port 8080. Whenever you make a request to http://localhost:8080 in your browser or using a tool like a curl, the server responds with the specified text along with the ASCII art of a whale saying “Hello from Docker!”.

node app.js
Server started!

You can now access the app using the curl command:

curl localhost:8080

          ##         .
    ## ## ##        ==
 ## ## ## ## ##    ===
/""""""""""""""""\___/ ===
{                       /  ===-
\______ O           __/
 \    \         __/
  \____\_______/


Hello from Docker!

Containerising the Node.js Application

Now, let’s move on to containerizing our Node.js application. Traditionally, developers write a Dockerfile for this purpose. However, Docker offers a nifty tool called docker init, automating much of the process.

Running docker init guides us through creating essential Docker files tailored to our application. It even detects our application’s platform—Node.js in this case.

After specifying some details like Node.js version, package manager (npm in our case), startup command (node app.js), and the port (8080), docker init generates Docker assets for us, including the Dockerfile and Docker Compose file.

Let’s test drive the tool:

docker init

Welcome to the Docker Init CLI!

This utility will walk you through creating the following files with sensible defaults for your project:
  - .dockerignore
  - Dockerfile
  - compose.yaml
  - README.Docker.md

Let's get started!

? What application platform does your project use?  [Use arrows to move, type to filter]
> Node - (detected) suitable for a Node server application
  Go - suitable for a Go server application
  Python - suitable for a Python server application
  Rust - suitable for a Rust server application
  ASP.NET Core - suitable for an ASP.NET Core application
  PHP with Apache - suitable for a PHP web application
  Java - suitable for a Java application that uses Maven and packages as an uber jar
  Other - general purpose starting point for containerizing your application
  Don't see something you need? Let us know!
  Quit

The beauty of docker init is that it automatically detects your underlying application platform. In our demonstration, it detected that the application is based on Node.js.

? What version of Node do you want to use? (21.6.2)

The tool picked up 21.6.2 as per the best practices.

Next, it ask for which package manager do you want to leverage. Let’s pick up npm.

? Which package manager do you want to use?  [Use arrows to move, type to filter]
> npm - (detected)
  yarn
  pnpm

Next, specify the command that you want to use. As we saw earlier, we used node <script_name> to bring up the HTTP server.

? What command do you want to use to start the app? [tab for suggestions] (node app.js)

Next, choose your preferred port. For our demonstration, we will pick up 8080.

? What port does your server listen on? 8080

Ensure that port 8080 is free and not occupied.

docker init

Welcome to the Docker Init CLI!

This utility will walk you through creating the following files with sensible defaults for your project:
  - .dockerignore
  - Dockerfile
  - compose.yaml
  - README.Docker.md

Let's get started!

? What application platform does your project use? Node
? What version of Node do you want to use? 21.6.2
? Which package manager do you want to use? npm
? What command do you want to use to start the app? node app.js
? What port does your server listen on? 8080

CREATED: .dockerignore
CREATED: Dockerfile
CREATED: compose.yaml
CREATED: README.Docker.md

✔ Your Docker files are ready!

Take a moment to review them and tailor them to your application.

When you're ready, start your application by running: docker compose up --build

Your application will be available at http://localhost:8080

Consult README.Docker.md for more information about using the generated files.

Verifying the Docker Assets

cat Dockerfile
# syntax=docker/dockerfile:1

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/

# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7

ARG NODE_VERSION=21.6.2

FROM node:${NODE_VERSION}-alpine

# Use production node environment by default.
ENV NODE_ENV production


WORKDIR /usr/src/app

# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.npm to speed up subsequent builds.
# Leverage a bind mounts to package.json and package-lock.json to avoid having to copy them into
# into this layer.
RUN --mount=type=bind,source=package.json,target=package.json \
    --mount=type=bind,source=package-lock.json,target=package-lock.json \
    --mount=type=cache,target=/root/.npm \
    npm ci --omit=dev

# Run the application as a non-root user.
USER node

# Copy the rest of the source files into the image.
COPY . .

# Expose the port that the application listens on.
EXPOSE 8080

# Run the application.
CMD node app.js

Verifying the Compose File

services:
  server:
    build:
      context: .
    environment:
      NODE_ENV: production
    ports:
      - 8080:8080

Running the Compose Services

docker compose up -d --build
docker compose up -d --build
[+] Building 11.3s (13/13) FINISHED                                                                                                                                                              docker:desktop-linux
 => [server internal] load build definition from Dockerfile                                                                                                                                                      0.0s
 => => transferring dockerfile: 1.24kB                                                                                                                                                                           0.0s
 => [server] resolve image config for docker-...
1909ba6ef92861146998bc2b55c63e7ee85b7b52bc2d017f                                                                                                                        0.0s
ing to docker.io/library/test-server:latest                                                                                                                                                            0.0s
 => => unpacking to docker.io/library/test-server:latest                                                                                                                                                         0.0s
[+] Running 1/2
 ⠹ Network test_default     Created                                                                                                                                                                              0.3s
 ✔ Container test-server-1  Started

Did you notice that we didn’t even write Dockerfile or Docker Compose to bring up the Node.js application? That’s the power of docker init command.

Conclusion

We’ve embarked on a journey to containerize a Node.js application using Docker. While traditional methods involve manual crafting of Dockerfiles and Docker Compose files, we explored a faster route with Docker’s docker init command.

By leveraging docker init, we automated much of the containerization process, allowing us to focus more on our application’s development and less on Docker configurations. This streamlined approach not only saves time but also simplifies the containerization workflow, making it accessible to developers of all skill levels.

As we navigate the seas of software development, tools like Docker continue to evolve, offering new capabilities and efficiencies. Whether you’re containerizing a simple Node.js script or a complex application, embracing automation tools like docker init can help you sail smoothly towards your deployment goals.

So, as you continue your Docker voyage, remember the power of automation at your fingertips. And may your containers always sail swiftly and securely across the vast expanse of the digital ocean. Happy coding!

Latest Posts

Have Queries? Join https://launchpass.com/collabnix

Ajeet Raina Ajeet Singh Raina is a former Docker Captain, Community Leader and Arm Ambassador. He is a founder of Collabnix blogging site and has authored more than 570+ blogs on Docker, Kubernetes and Cloud-Native Technology. He runs a community Slack of 8900+ members and discord server close to 2200+ members. You can follow him on Twitter(@ajeetsraina).

“sh: next: command not found” in Next.js Development? Here’s…

Struggling with the "sh: next: command not found" error in your Next.js project? Learn how to fix it by running Next.js locally or globally....
Avinash Bendigeri
1 min read
Join our Discord Server
Index