Node.js Docker

Docker packages your app and everything it needs into one container. The container runs the same way on your laptop, a teammate's computer, and a cloud server. The phrase "it works on my machine" disappears.

The Shipping Container Idea

Cargo ships carry standard steel containers. The ship does not care whether a container holds toys or fruit. Docker applies this idea to software. Every app travels in a standard box that runs anywhere Docker runs.

+-----------------------------+
|        Container            |
|  +-----------------------+  |
|  |  Your Node.js code    |  |
|  |  node_modules         |  |
|  |  Node.js runtime      |  |
|  |  System libraries     |  |
|  +-----------------------+  |
+-----------------------------+
         runs on
+-----------------------------+
|   Docker on any computer    |
+-----------------------------+

Key Terms

  • Image is a read-only recipe that holds your app and its tools.
  • Container is a running copy of an image.
  • Dockerfile is a text file that lists the steps to build an image.
  • Registry is an online store for images, such as Docker Hub.
 Dockerfile --build--> Image --run--> Container
 (recipe)              (packaged)     (running app)

Prepare a Sample App

// index.js
const express = require("express");
const app = express();

app.get("/", (req, res) => res.send("Hello from Docker!"));

const port = process.env.PORT || 3000;
app.listen(port, () => console.log("Listening on " + port));

Write the Dockerfile

Create a file named Dockerfile with no extension.

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY . .

EXPOSE 3000
CMD ["node", "index.js"]

Read Each Line

  • FROM picks the base image. Alpine versions stay small.
  • WORKDIR sets the folder inside the container.
  • COPY package*.json copies the dependency lists.
  • RUN npm ci installs the libraries.
  • COPY . . copies the rest of your code.
  • EXPOSE documents the port.
  • CMD sets the start command.

Why the Order Matters

Docker saves each step as a layer and reuses unchanged layers. Copying package files before the code lets Docker skip the install step when only your code changes. Builds finish faster.

Layer 1: node base image      (cached)
Layer 2: package files        (cached)
Layer 3: npm install          (cached)
Layer 4: your code            (rebuilt after each code change)

Ignore Unneeded Files

Create a .dockerignore file.

node_modules
npm-debug.log
.env
.git

This file keeps secrets and heavy folders out of the image.

Build and Run

docker build -t my-node-app .
docker run -d -p 8080:3000 --name web my-node-app

The flag -p 8080:3000 maps port 8080 on your computer to port 3000 in the container. Open http://localhost:8080 to see the message.

Browser --> localhost:8080 --> [ Docker ] --> container port 3000 --> app

Useful Commands

docker ps                 // list running containers
docker logs web           // read app logs
docker stop web           // stop the container
docker rm web             // remove the container
docker images             // list images

Pass Settings

docker run -d -p 8080:3000 -e PORT=3000 -e NODE_ENV=production my-node-app

Pass secrets at run time. Never write them into the Dockerfile.

Run Several Services with Compose

Docker Compose starts an app and its database together.

services:
  web:
    build: .
    ports:
      - "8080:3000"
    environment:
      - MONGO_URL=mongodb://db:27017/shop
    depends_on:
      - db
  db:
    image: mongo:7
    volumes:
      - dbdata:/data/db

volumes:
  dbdata:
docker compose up -d
+------------------+       +------------------+
| web (Node.js)    | ----> | db (MongoDB)     |
| port 8080        |       | volume: dbdata   |
+------------------+       +------------------+

A volume keeps database files after the container stops.

Good Habits

  • Choose small base images.
  • Pin a version tag instead of using latest.
  • Run the app as a non-root user.
  • Keep one main process in each container.

Practice Tasks

  • Containerize a small Express app and open it in your browser.
  • Add a .dockerignore file and compare the image size.
  • Write a Compose file that starts your app and a database.