DEV Community

Alex Chen
Alex Chen

Posted on

Docker for Developers: The Only Guide You Actually Need (2026)

Docker for Developers: The Only Guide You Actually Need (2026)

Stop memorizing Docker commands. Understand how it works once, and you'll never be confused again.

Why Docker Matters

"It works on my machine" → No longer a valid excuse.

Docker ensures your app runs the same way:
→ On your laptop
→ On your teammate's Mac
→ On the Linux server
→ In CI/CD pipeline

One command to rule them all: docker compose up
Enter fullscreen mode Exit fullscreen mode

Docker vs VMs (The Key Insight)

Virtual Machine:
┌─────────────────────────────────┐
│         Host OS                 │
│  ┌─────────────────────────┐    │
│  │      Guest OS           │    │  ← Full OS (GBs of RAM)
│  │   ┌───────────────┐     │    │
│   │   │    Your App   │     │    │
│   │   └───────────────┘     │    │
│  └─────────────────────────┘    │
└─────────────────────────────────┘

Container:
┌─────────────────────────────────┐
│         Host OS (Linux)         │
│  ┌─────────────────────────┐    │
│  │    Docker Engine        │    │
│  │  ┌───────────────┐     │    │
│  │  │   Your App    │     │    │  ← Just your app + deps (MBs)
│  │  └───────────────┘     │    │
│  └─────────────────────────┘    │
└─────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Containers share the host OS kernel. That's why they're lightweight.

Essential Concepts (5 Minutes)

Image vs Container

# Image = Blueprint (like a class)
# Container = Running instance (like an object)

docker run node:20-alpine          # Pull image + create container
docker images                       # List all images
docker ps                           # List running containers
docker ps -a                        # List ALL containers (including stopped)
Enter fullscreen mode Exit fullscreen mode

Dockerfile = Your Build Recipe

# Every Dockerfile starts with a base image
FROM node:20-alpine

# Set working directory
WORKDIR /app

# Copy package files first (layer caching!)
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY . .

# Expose port
EXPOSE 3000

# Command to run
CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml = Multi-Service Orchestration

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://user:pass@db:5432/myapp
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    restart: unless-stopped

volumes:
  pgdata:
Enter fullscreen mode Exit fullscreen mode

The Commands I Use Every Day

# === Building & Running ===
docker compose up -d              # Start services in background
docker compose down               # Stop & remove containers
docker compose logs -f --tail 50  # Follow logs (last 50 lines)
docker compose restart app        # Restart one service

# === Debugging ===
docker exec -it app sh            # Shell into running container
docker exec -it app npm test      # Run command inside container
docker compose run --rm app npm run migrate  # Run one-off command

# === Cleanup ===
docker system df                   # See disk usage
docker system prune -a             # Remove unused images, containers, volumes
docker volume prune                # Remove unused volumes

# === Inspecting ===
docker inspect <container>         # Full JSON config of container
docker stats                       # Live resource usage (CPU, MEM, NET)
docker logs <container> -f         # Follow specific container logs
Enter fullscreen mode Exit fullscreen mode

Real-World Examples

1. Node.js API with PostgreSQL

# Dockerfile
FROM node:20-alpine AS builder

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

FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 app && adduser -u 1001 -G app -s /bin/sh -D app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
USER app
EXPOSE 3000
CMD ["node", "dist/server.js"]
Enter fullscreen mode Exit fullscreen mode
# docker-compose.yml
version: '3.8'
services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/appdb
      REDIS_URL: redis://redis:6379
    depends_on:
      - db
      - redis

  db:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: appdb
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  postgres_data:
Enter fullscreen mode Exit fullscreen mode

2. Static Site with Nginx

version: '3.8'
services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./public:/usr/share/nginx/html:ro
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    restart: unless-stopped
Enter fullscreen mode Exit fullscreen mode

3. Development Environment (Hot Reload)

version: '3.8'
services:
  dev:
    image: node:20-alpine
    working_dir: /app
    command: sh -c "npm install && npm run dev"
    ports:
      - "3000:3000"
      - "9229:9229"  # Node.js debugger
    volumes:
      - .:/app       # Live reload source changes!
      - node_modules:/app/node_modules  # Persist installed modules
    environment:
      - NODE_ENV=development

volumes:
  node_modules:
Enter fullscreen mode Exit fullscreen mode

Performance Tips

1. Optimize Your Dockerfile

# ❌ BAD — Rebuilds everything on any code change
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]

# ✅ GOOD — Leverages layer caching
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci                    # Only re-runs if package.json changes
COPY . .
CMD ["node", "server.js"]

# ✅ BETTER — Multi-stage build (smaller final image)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

2. Use .dockerignore

# .dockerignore — like .gitignore for Docker
node_modules
npm-debug.log
.git
.env
coverage
.next
build
.DS_Store
*.md
!README.md
Enter fullscreen mode Exit fullscreen mode

This can cut build time from minutes to seconds.

3. Smaller Base Images

Image Size When to use
node:20 ~1 GB Development
node:20-alpine ~180 MB Production (most cases)
node:20-slim ~200 MB Need glibc compatibility

Common Problems & Solutions

"Permission denied" in mounted volumes

# Add this to your service
user: "${UID:-1000}:${GID:-1000}"
Enter fullscreen mode Exit fullscreen mode

Then run: UID=$(id -u) GID=$(id -g) docker compose up -d

Container eats all memory

# Add memory limits
services:
  app:
    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M
Enter fullscreen mode Exit fullscreen mode

Port already in use

# Find what's using the port
lsof -i :3000

# Kill it or change your mapping
ports:
  - "3001:3000"  # Map host 3001 → container 3000
Enter fullscreen mode Exit fullscreen mode

Images getting too large

# Check what's taking space
docker history your-image:latest

# Clean up in Dockerfile
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
Enter fullscreen mode Exit fullscreen mode

Docker for Side Projects (My Setup)

I run 4 projects on one VPS using Docker Compose:

# docker-compose.yml (production)
version: '3.8'

services:
  agentvote:
    build: ./agentvote
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production

  signal:
    build: ./crypto-signal
    restart: unless-stopped
    ports:
      - "3001:3001"
    volumes:
      - signal_data:/app/data

  blog:
    image: nginx:alpine
    restart: unless-stopped
    volumes:
      - ./blog/public:/usr/share/nginx/html:ro
    ports:
      - "8080:80"

  formatter:
    build: ./text-formatter
    restart: unless-stopped
    ports:
      - "3099:3099"

volumes:
  signal_data:
Enter fullscreen mode Exit fullscreen mode
# Deploy everything
docker compose up -d --build

# Update one service
docker compose up -d --build agentvote

# Check everything's running
docker compose ps
Enter fullscreen mode Exit fullscreen mode

Quick Reference Card

# Lifecycle
docker compose up -d        # Start
docker compose down         # Stop
docker compose restart      # Restart

# Debugging
docker exec -it <svc> sh    # Shell in
docker compose logs -f      # Logs
docker compose ps           # Status

# Maintenance
docker system prune -a      # Cleanup
docker compose pull         # Update images
docker compose up -d --build # Rebuild & restart
Enter fullscreen mode Exit fullscreen mode

What's your Docker setup look like? Still avoiding it?

Follow @armorbreak for more practical DevOps guides.

Top comments (0)