Skip to content
Plurafy

Docker and Containerization

Docker enables consistent, reproducible application deployment across development, testing, and production environments. Learn to containerize Node.js applications and manage containers effectively.

Containerize a Node.js application with Docker, optimize images for production, and implement best practices for container management and orchestration.

Docker is a containerization platform that packages applications and their dependencies into isolated containers:

  • Images: Read-only templates containing application code and dependencies
  • Containers: Running instances of images with isolated filesystems
  • Registries: Centralized repositories for storing and sharing images
  • Layers: Stackable filesystem layers in Docker images for efficiency

Dockerfile: Instructions to build Docker images Docker Compose: Multi-container orchestration Container Registry: Storage for Docker images (Docker Hub, ECR, GCR) Volume: Persistent data storage for containers Network: Communication between containers

# Multi-stage build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Production stage
FROM node:18-alpine
WORKDIR /app
# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init
# Copy from builder
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
ENV NODE_ENV=production
# Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]
FROM node:18-alpine
WORKDIR /app
# Install development dependencies
RUN apk add --no-cache build-base python3 git
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
# Development server with hot reload
CMD ["npm", "run", "dev"]
version: '3.9'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: nodejs-app
ports:
- "3000:3000"
environment:
NODE_ENV: production
DATABASE_URL: postgresql://user:password@db:5432/appdb
REDIS_URL: redis://cache:6379
depends_on:
- db
- cache
volumes:
- ./logs:/app/logs
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres:15-alpine
container_name: postgres-db
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: appdb
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
cache:
image: redis:7-alpine
container_name: redis-cache
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
FROM node:18-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && \
npm cache clean --force
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
RUN apk add --no-cache \
dumb-init \
curl
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package*.json ./
ENV NODE_ENV=production
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/server.js"]
#!/bin/bash
# Build variables
IMAGE_NAME="myapp"
IMAGE_TAG="latest"
REGISTRY="docker.io"
DOCKER_USER="username"
# Build image
docker build -t $IMAGE_NAME:$IMAGE_TAG -f Dockerfile .
# Tag for registry
docker tag $IMAGE_NAME:$IMAGE_TAG $REGISTRY/$DOCKER_USER/$IMAGE_NAME:$IMAGE_TAG
# Login to registry (use --password-stdin for security)
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USER --password-stdin $REGISTRY
# Push image
docker push $REGISTRY/$DOCKER_USER/$IMAGE_NAME:$IMAGE_TAG
# Clean up
docker logout $REGISTRY
server.js
const express = require('express');
const app = express();
// Graceful shutdown handling
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
function gracefulShutdown() {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('HTTP server closed');
process.exit(0);
});
// Force shutdown after timeout
setTimeout(() => {
console.error('Could not close connections, forcefully shutting down');
process.exit(1);
}, 30000);
}
const server = app.listen(3000, () => {
console.log('Server running on port 3000');
});
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'healthy' });
});
app.get('/ready', (req, res) => {
// Check database connection, cache, etc.
res.status(200).json({ ready: true });
});
  1. Use Alpine Images

    • Smaller image size
    • Fewer security vulnerabilities
    • Faster download and startup times
  2. Multi-Stage Builds

    • Separate build and runtime stages
    • Reduce final image size
    • Improve layer caching efficiency
  3. Security

    • Use specific version tags, not latest
    • Run as non-root user
    • Scan images for vulnerabilities
    • Use secrets management for sensitive data
  4. Performance Optimization

    • Minimize layer count
    • Use .dockerignore to exclude unnecessary files
    • Leverage layer caching
    • Compress images
  5. Container Orchestration

    • Use health checks
    • Implement graceful shutdown
    • Set resource limits
    • Use restart policies
  • Microservices Deployment: Package each service independently
  • CI/CD Pipeline: Automate image building and deployment
  • Development Environment: Consistent environment across teams
  • Cloud Migration: Portable applications across cloud providers
  • Scaling: Easy horizontal scaling in Kubernetes