Lab 13: Containerizing Node.js

Time: 30 minutes | Level: Advanced | Docker: docker run -it --rm node:20-alpine sh

Overview

Best practices for Docker with Node.js: multi-stage builds, .dockerignore, non-root user, layer caching, health checks, graceful shutdown, and Docker Compose.


Step 1: Basic Dockerfile (and Why It's Bad)

# BAD: Don't do this
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]

# Problems:
# 1. Uses full node image (1GB+) instead of alpine (~150MB)
# 2. COPY . . before npm install breaks layer caching
# 3. Runs as root — security risk
# 4. Dev dependencies included in production
# 5. No health check
# 6. No graceful shutdown handling

Step 2: Production Dockerfile


Step 3: .dockerignore

💡 A good .dockerignore can reduce build context by 90%+ and prevent secrets from entering the build.


Step 4: Health Check Script


Step 5: Graceful Shutdown in Containerized Apps


Step 6: Docker Compose for Full Stack


Step 7: Layer Caching Optimization


Step 8: Capstone — Build & Run Demo

📸 Verified Output:


Summary

Best Practice
Implementation
Benefit

Alpine base image

FROM node:20-alpine

~150MB vs ~1GB

Multi-stage build

FROM ... AS builder

Smaller prod image

Layer caching

COPY package*.json first

Faster rebuilds

Non-root user

adduser + USER nodeuser

Security

.dockerignore

Exclude node_modules etc

Smaller context

Health check

HEALTHCHECK instruction

Container orchestration

Graceful shutdown

SIGTERM handler

Zero-downtime deploys

dumb-init

PID 1 replacement

Proper signal forwarding

Resource limits

deploy.resources.limits

Prevent OOM in compose

Last updated