Docker & Containerization
📖 Concept
Docker packages your Node.js application and all its dependencies into a portable container — ensuring it runs identically everywhere (development, staging, production).
Why Docker for Node.js?
- Consistency — "Works on my machine" problem eliminated
- Isolation — Each app gets its own environment
- Scalability — Containers start in seconds, scale horizontally
- CI/CD — Build once, deploy everywhere
Dockerfile best practices for Node.js:
- Use specific Node.js version tags (
node:20-alpine, notnode:latest) - Use multi-stage builds to reduce image size
- Copy
package*.jsonfirst, thennpm ci, then copy source (layer caching) - Run as non-root user (
USER node) - Use
.dockerignoreto excludenode_modules,.git, etc. - Use
npm ciinstead ofnpm installfor deterministic installs - Set
NODE_ENV=productionto skip dev dependencies
Alpine vs. Debian images:
| Image | Size | Use Case |
|---|---|---|
node:20-alpine |
~180MB | Production (smallest) |
node:20-slim |
~240MB | When Alpine causes issues |
node:20 |
~1GB | Development, debugging |
🏠 Real-world analogy: Docker is like a shipping container. Your application (cargo) is packed with everything it needs (dependencies). The container fits on any ship (server) regardless of what other containers are onboard. The container specification (Dockerfile) ensures identical packing every time.
💻 Code Example
1// Docker Configuration for Node.js23// === Dockerfile (production-ready, multi-stage) ===4const dockerfile = `5# Stage 1: Dependencies6FROM node:20-alpine AS deps7WORKDIR /app8COPY package.json package-lock.json ./9RUN npm ci --only=production && \10 cp -R node_modules /tmp/prod_modules && \11 npm ci # Install all deps for build stage1213# Stage 2: Build (if you have a build step)14FROM node:20-alpine AS builder15WORKDIR /app16COPY --from=deps /app/node_modules ./node_modules17COPY . .18RUN npm run build # TypeScript compile, etc.1920# Stage 3: Production (minimal image)21FROM node:20-alpine AS runner22WORKDIR /app2324# Security: run as non-root25RUN addgroup --system --gid 1001 nodejs && \26 adduser --system --uid 1001 appuser2728# Copy only production dependencies29COPY --from=deps /tmp/prod_modules ./node_modules30COPY --from=builder /app/dist ./dist31COPY package.json ./3233# Set environment34ENV NODE_ENV=production35ENV PORT=30003637# Health check38HEALTHCHECK --interval=30s --timeout=10s --retries=3 \39 CMD wget -q --spider http://localhost:3000/health || exit 14041USER appuser42EXPOSE 30004344CMD ["node", "dist/server.js"]45`;4647// === .dockerignore ===48const dockerignore = `49node_modules50npm-debug.log51.git52.gitignore53.env54.env.*55coverage56tests57docs58*.md59.vscode60.idea61Dockerfile62docker-compose*.yml63`;6465// === docker-compose.yml ===66const dockerCompose = `67version: "3.8"6869services:70 api:71 build: .72 ports:73 - "3000:3000"74 environment:75 - NODE_ENV=production76 - DATABASE_URL=postgresql://postgres:password@db:5432/myapp77 - REDIS_URL=redis://cache:637978 depends_on:79 db:80 condition: service_healthy81 cache:82 condition: service_healthy83 restart: unless-stopped84 deploy:85 resources:86 limits:87 memory: 512M88 cpus: "1.0"8990 db:91 image: postgres:16-alpine92 environment:93 POSTGRES_DB: myapp94 POSTGRES_USER: postgres95 POSTGRES_PASSWORD: password96 volumes:97 - pgdata:/var/lib/postgresql/data98 healthcheck:99 test: ["CMD-SHELL", "pg_isready -U postgres"]100 interval: 5s101 timeout: 5s102 retries: 5103104 cache:105 image: redis:7-alpine106 command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru107 healthcheck:108 test: ["CMD", "redis-cli", "ping"]109 interval: 5s110 timeout: 5s111 retries: 5112113volumes:114 pgdata:115`;116117module.exports = { dockerfile, dockerignore, dockerCompose };
🏋️ Practice Exercise
Exercises:
- Write a production Dockerfile with multi-stage build — compare image sizes: full vs alpine vs multi-stage
- Create a docker-compose.yml with Node.js API, PostgreSQL, and Redis
- Optimize Docker layer caching — ensure
npm cilayer is cached when only source code changes - Implement Docker health checks that test the
/healthendpoint - Set up volume mounts for local development with hot-reloading inside Docker
- Compare
docker buildtimes with and without.dockerignoreoptimization
⚠️ Common Mistakes
Using
node:latest— version can change unexpectedly; always pin a specific version likenode:20-alpineCopying
node_modulesinto the image instead of runningnpm ci— host modules may not match the container's OS or architectureNot using
.dockerignore— without it,node_modules,.git, and test files are copied, making the image huge and builds slowRunning containers as root — if the app is compromised, the attacker has root access; use
USER nodeor create a dedicated userNot using multi-stage builds — the final image includes build tools, devDependencies, and source code; multi-stage reduces image size by 50-80%
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Docker & Containerization. Login to unlock this feature.