Docker has revolutionized the way we develop, ship, and run applications. In this comprehensive guide, we'll walk through containerizing a Node.js application from scratch to production-ready deployment.

Why Docker?

Docker solves the classic "it works on my machine" problem by packaging your application with all its dependencies into a standardized unit called a container. Benefits include:

  • Consistency - Same environment everywhere
  • Isolation - No conflicts between applications
  • Portability - Run anywhere Docker is installed
  • Scalability - Easy to replicate and scale

Basic Dockerfile

Let's start with a simple Dockerfile for a Node.js application:

# Use official Node.js image
FROM node:18-alpine

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy application code
COPY . .

# Expose port
EXPOSE 3000

# Start application
CMD ["node", "server.js"]

Multi-stage Builds

For production, use multi-stage builds to reduce image size:

# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]

Docker Compose

For applications with multiple services, use docker-compose.yml:

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://db:5432/myapp
    depends_on:
      - db

  db:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_PASSWORD=secret

volumes:
  postgres_data:

Useful Commands

  • docker-compose up -d - Start services in background
  • docker-compose logs -f - Follow logs
  • docker-compose down - Stop and remove containers
  • docker-compose build --no-cache - Rebuild images
Pro tip: Use .dockerignore to exclude unnecessary files like node_modules, .git, and .env from the build context.

Conclusion

Docker simplifies deployment and ensures consistency across environments. Start with simple Dockerfiles and gradually adopt more advanced patterns as your needs grow.

Back to Blog