Welcome to the interactive Docker containerization tutorial!
Docker is a containerization tool that allows you to test and fast deploy applications without worrying about the underlying architecture of CPU, or what operating system is running. It isolates itself from pretty much everything and only includes the bare bones needed to run your dedicated application.
Today, we’ll learn how to containerize your own application using Docker!
For this tutorial, we'll containerize a Node.js backend application for a todo app. Let's start by examining our application structure.
ls -la
total 48
drwxr-xr-x 10 user user 320 Dec 4 10:30 .
drwxr-xr-x 3 user user 96 Dec 4 10:29 ..
drwxr-xr-x 3 user user 96 Dec 4 10:30 Controllers
drwxr-xr-x 3 user user 96 Dec 4 10:30 Database
drwxr-xr-x 3 user user 96 Dec 4 10:30 Middleware
drwxr-xr-x 3 user user 96 Dec 4 10:30 Models
drwxr-xr-x 3 user user 96 Dec 4 10:30 Services
drwxr-xr-x 3 user user 96 Dec 4 10:30 Utility
-rw-r--r-- 1 user user 847 Dec 4 10:30 package.json
drwxr-xr-x 150 user user 4800 Dec 4 10:30 node_modules
-rw-r--r-- 1 user user 1234 Dec 4 10:30 index.js
Perfect! We have a Node.js application with all the necessary components. Now let's check our package.json to understand the application dependencies.
cat package.json
{
"name": "todo-backend",
"version": "1.0.0",
"description": "Backend API for Todo application",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"dotenv": "^16.3.1"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
Great! Now we need to create a Dockerfile. This is the blueprint that tells Docker how to build our container image. Let's create it step by step.
touch Dockerfile
Now let's add the Dockerfile content. We'll build it step by step to understand each instruction.
cat > Dockerfile << 'EOF'
# Use Node.js 20 as base image
FROM node:20
Set working directory inside container
WORKDIR /app
Copy package files and install dependencies
COPY package*.json ./
RUN npm install
Copy source code
COPY . .
Expose the port your app runs on
EXPOSE 3000
Command to run the server
CMD [“node”, “index.js”]
EOF
Let's examine our Dockerfile to understand each instruction:
• FROM node:20 - Uses Node.js version 20 as the base image (includes Linux + Node.js + npm)
• WORKDIR /app - Sets the working directory inside the container
• COPY package*.json ./ - Copies package files first (for better caching)
• RUN npm install - Installs dependencies
• COPY . . - Copies all source code
• EXPOSE 3000 - Documents which port the app uses
• CMD - Defines the command to start the application
cat Dockerfile
# Use Node.js 20 as base image
FROM node:20
Set working directory inside container
WORKDIR /app
Copy package files and install dependencies
COPY package*.json ./
RUN npm install
Copy source code
COPY . .
Expose the port your app runs on
EXPOSE 3000
Command to run the server
CMD [“node”, “index.js”]
Perfect! Now let's build our Docker image. The -t flag tags our image with a name for easy reference.
docker build -t todo-backend .
[+] Building 23.4s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 285B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/node:20 2.1s
=> [1/5] FROM docker.io/library/node:20@sha256:abc123... 8.3s
=> [internal] load build context 0.1s
=> => transferring context: 12.45kB 0.1s
=> [2/5] WORKDIR /app 0.2s
=> [3/5] COPY package*.json ./ 0.1s
=> [4/5] RUN npm install 11.2s
=> [5/5] COPY . . 0.2s
=> exporting to image 1.2s
=> => exporting layers 1.2s
=> => writing image sha256:def456... 0.0s
=> => naming to docker.io/library/todo-backend 0.0s
Excellent! Our Docker image has been built successfully. Let's verify it exists in our local Docker registry.
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
todo-backend latest def456789abc 2 minutes ago 1.1GB
node 20 abc123456def 3 days ago 1.09GB
Great! Now let's run our containerized application. The -p flag maps port 3000 from the container to port 3000 on our host machine.
docker run -p 3000:3000 todo-backend
Server starting...
✅ Database connected successfully
✅ CORS middleware configured
✅ Routes loaded
🚀 Todo Backend API running on port 3000
📡 Ready to accept requests at http://localhost:3000
Perfect! Our application is now running inside a Docker container. Let's test it in another terminal to make sure it's working properly.
curl http://localhost:3000/health
{
"status": "healthy",
"timestamp": "2023-12-04T10:45:23.123Z",
"uptime": "00:02:15",
"service": "todo-backend"
}
Excellent! Our containerized application is working perfectly. Let's also run it in detached mode so it runs in the background.
docker run -d -p 3001:3000 --name todo-app todo-backend
a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890
Great! The container is now running in the background. Let's check the running containers.
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 todo-backend "docker-entrypoint.s…" 30 seconds ago Up 29 seconds 0.0.0.0:3001->3000/tcp todo-app
Perfect! You've successfully containerized your Node.js application with Docker.
Key takeaways:
✅ Docker isolates applications in lightweight containers
✅ Dockerfile defines how to build your container image
✅ Layer caching optimizes build times (copy package.json first)
✅ Port mapping connects container ports to host ports
✅ Containers can run in foreground or background (detached mode)
Your application is now portable and can run consistently across any environment that supports Docker!