Back to Blog

Docker for Developers: Complete Guide to Containerization

Anand Jaiswal
January 3, 2024
6 min read
DockerDevOpsContainerizationDevelopment

Docker for Developers: Complete Guide to Containerization

Docker has revolutionized how we develop, ship, and run applications. This comprehensive guide will take you from Docker basics to advanced containerization strategies.

Why Docker?

Problems Docker Solves

  • "It works on my machine" syndrome
  • Environment inconsistencies
  • Complex dependency management
  • Deployment complications
  • Resource isolation issues

Benefits

  • Consistency: Same environment everywhere
  • Portability: Run anywhere Docker runs
  • Efficiency: Lightweight compared to VMs
  • Scalability: Easy horizontal scaling
  • Isolation: Secure application separation

Docker Fundamentals

Core Concepts

  • Image: Read-only template for containers
  • Container: Running instance of an image
  • Dockerfile: Instructions to build an image
  • Registry: Storage for Docker images
  • Volume: Persistent data storage

Basic Commands

# Pull an image
docker pull nginx:latest

# Run a container
docker run -d -p 80:80 --name my-nginx nginx

# List running containers
docker ps

# Stop a container
docker stop my-nginx

# Remove a container
docker rm my-nginx

# List images
docker images

# Remove an image
docker rmi nginx:latest

Creating Your First Dockerfile

Node.js Application Example

# Use official Node.js runtime as base 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 . .

# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# Change ownership of app directory
RUN chown -R nextjs:nodejs /app
USER nextjs

# Expose port
EXPOSE 3000

# Define health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# Start the application
CMD ["npm", "start"]

Multi-stage Build

# 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 AS production

WORKDIR /app

# Copy only necessary files from builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./

# Install only production dependencies
RUN npm ci --only=production && npm cache clean --force

# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

USER nextjs

EXPOSE 3000

CMD ["npm", "start"]

Docker Compose for Development

Basic docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://user:password@db:5432/myapp
    volumes:
      - .:/app
      - /app/node_modules
    depends_on:
      - db
      - redis

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

Development with Hot Reload

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - CHOKIDAR_USEPOLLING=true
    command: npm run dev

Best Practices

Dockerfile Optimization

1. Use Specific Tags

# Bad
FROM node

# Good
FROM node:18.17.0-alpine

2. Minimize Layers

# Bad
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git

# Good
RUN apt-get update && \
    apt-get install -y curl git && \
    rm -rf /var/lib/apt/lists/*

3. Leverage Build Cache

# Copy package files first
COPY package*.json ./
RUN npm ci

# Then copy source code
COPY . .

4. Use .dockerignore

node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.nyc_output

Security Best Practices

1. Use Non-root User

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs

2. Scan for Vulnerabilities

# Scan image for vulnerabilities
docker scout cves my-app:latest

# Use Trivy
trivy image my-app:latest

3. Use Minimal Base Images

# Use Alpine or distroless images
FROM node:18-alpine
# or
FROM gcr.io/distroless/nodejs18-debian11

Development Workflows

Local Development Setup

# Start development environment
docker-compose up -d

# View logs
docker-compose logs -f app

# Execute commands in container
docker-compose exec app npm run test

# Stop environment
docker-compose down

Database Migrations

# Run migrations
docker-compose exec app npm run migrate

# Seed database
docker-compose exec app npm run seed

Testing in Containers

# Test stage in multi-stage build
FROM node:18-alpine AS test

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run test
RUN npm run lint

Production Deployment

Production docker-compose.yml

version: '3.8'

services:
  app:
    image: my-app:latest
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${DATABASE_URL}
    depends_on:
      - db
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - app

  db:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

CI/CD Pipeline

# GitHub Actions example
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Build Docker image
      run: docker build -t my-app:${{ github.sha }} .
    
    - name: Run tests
      run: docker run --rm my-app:${{ github.sha }} npm test
    
    - name: Push to registry
      run: |
        echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
        docker push my-app:${{ github.sha }}

Monitoring and Logging

Health Checks

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

Logging Configuration

services:
  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Resource Limits

services:
  app:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

Troubleshooting

Common Issues

1. Container Exits Immediately

# Check logs
docker logs container-name

# Run interactively
docker run -it my-app:latest /bin/sh

2. Permission Issues

# Check file ownership
docker run --rm -v $(pwd):/app alpine ls -la /app

# Fix permissions
docker run --rm -v $(pwd):/app alpine chown -R $(id -u):$(id -g) /app

3. Network Issues

# Inspect network
docker network ls
docker network inspect bridge

# Test connectivity
docker run --rm alpine ping container-name

Advanced Topics

Custom Networks

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

services:
  app:
    networks:
      - frontend
      - backend
  
  db:
    networks:
      - backend

Secrets Management

secrets:
  db_password:
    file: ./db_password.txt

services:
  db:
    secrets:
      - db_password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password

Conclusion

Docker transforms development workflows by providing:

  1. Consistent Environments: Eliminate environment-related bugs
  2. Easy Scaling: Scale applications horizontally
  3. Simplified Deployment: Deploy anywhere Docker runs
  4. Resource Efficiency: Better resource utilization
  5. Development Speed: Faster onboarding and development

Master these Docker concepts and practices to build more reliable, scalable applications. Start containerizing your projects today!


Ready to containerize your applications? Start with a simple Dockerfile and gradually adopt more advanced patterns.

About the Author

Anand Jaiswal is a full-stack developer passionate about creating exceptional digital experiences and sharing knowledge with the community.