Containerizing a Node.js App with Docker: A Step-by-Step Walkthrough

Turning application source code into a runnable container image follows a consistent five-step process in Docker. This article walks through writing a Dockerfile, building an image, pushing it to a registry, and running it as a container, using a simple Node.js app as the example.

DockerfileDocker BuildDocker Init

~3 min read · Updated Sep 5, 2026

Introduction

Containerization is the process of packaging an application and its dependencies into an image and running it as a container. The overall workflow follows five steps: write the app and list its dependencies, create a Dockerfile, build the image, optionally push it to a registry, and run a container from it.

Getting the Application Code

The example used here is a simple Node.js web app. After cloning it locally, the application directory becomes the build context — the set of files Docker uses when building the image.

git clone https://github.com/nigelpoulton/ddd-book.git

Creating the Dockerfile

Newer versions of Docker support the docker init command, which analyzes the application and automatically generates a Dockerfile following good practices:

docker init

The generated Dockerfile typically includes several key instructions: a FROM line pulling a base image, a WORKDIR setting the working directory, a RUN instruction installing dependencies, a USER instruction to avoid running as root, a COPY instruction to add source code, an EXPOSE instruction documenting the network port, and a CMD instruction specifying how to start the app.

Building the Image

Once the Dockerfile is ready, the following command builds it into an image, using the current directory as the build context:

docker build -t ddd-book:ch8.node .

Each Dockerfile instruction that adds content — such as FROM, RUN, or COPY — produces a new image layer. The resulting image can be confirmed with:

docker images

Running docker inspect on the image reveals its layers along with metadata like exposed ports and working directory.

Pushing the Image to Docker Hub

To share an image, it first needs to be logged in and re-tagged to include the target repository, since Docker uses the tag to determine where to push it:

docker login
docker tag ddd-book:ch8.node nigelpoulton/ddd-book:ch8.node
docker push nigelpoulton/ddd-book:ch8.node

Running and Testing the App

A container can be started from the pushed (or locally built) image with:

docker run -d --name c1 -p 5005:8080 nigelpoulton/ddd-book:ch8.node

The app becomes reachable in a browser through the mapped host port, and docker ps confirms the container is running with the correct port mapping.

Looking a Bit Closer

The docker build process reads the Dockerfile top to bottom. Instructions that add content — FROM, RUN, COPY, WORKDIR — create new layers, while instructions like EXPOSE, ENV, and CMD only add metadata. The docker history command shows exactly which instructions produced layers and their sizes:

docker history ddd-book:ch8.node

It's generally good practice to base new images on Docker Official Images or Verified Publisher images, since they are actively maintained and quickly patched for known vulnerabilities.

Conclusion

Containerizing an application comes down to a repeatable pattern: define the build in a Dockerfile, build it into an image, optionally share it via a registry, and run it as a container. Understanding which Dockerfile instructions create layers versus metadata helps in writing cleaner, more efficient images going forward.

Written & researched by Dr. Shahin Siami

Related Articles

How Docker Uses Linux Security Technologies: Namespaces, Cgroups, and More

Docker's security model is built on layered defense, combining well-established Linux kernel technologies with sensible defaults out of the box. This article explains how namespaces, control groups, capabilities, mandatory access control, and seccomp work together to isolate and secure containers.

Continue

Managing Persistent Data in Docker with Volumes

Containers get a temporary writable layer by default, which disappears the moment a container is deleted — fine for scratch data, but risky for anything worth keeping. This article explains how Docker volumes decouple persistent data from container lifecycles, and walks through creating, using, and sharing them safely.

Continue

How Docker Overlay Networks Work: VXLAN Under the Hood

Overlay networks let containers on different hosts communicate as if they shared a single flat network, forming the backbone of most cloud-native microservices apps. This article covers how to build, encrypt, and test a Docker overlay network across a Swarm cluster, and explains the VXLAN tunneling technology powering it behind the scenes.

Continue

Connecting Docker Containers to VLANs and Load Balancing with Swarm

Beyond basic bridge networks, Docker can connect containers directly to existing physical VLANs, resolve service names automatically, and distribute traffic across a Swarm cluster. This article covers the macvlan driver, Docker's built-in service discovery, and Swarm's ingress load balancing mesh.

Continue

Understanding Docker Networking: CNM, Libnetwork, and Bridge Networks

Docker networking is built on an open design called the Container Network Model, implemented through libnetwork and extended by pluggable drivers. This article covers the theory behind Docker networking and walks through creating and testing single-host bridge networks, including name resolution and port mapping.

Continue

Deploying and Managing Multi-Node Clusters with Docker Swarm

Docker Swarm turns a group of Docker nodes into a secure, highly available cluster with built-in application orchestration. This article covers building a multi-node swarm, deploying a microservices app declaratively, and performing rolling updates without losing desired-state consistency.

Continue