Optimize your development workflow with Docker. Learn multi-stage builds, Docker Compose for local dev, bind mounts, hot reload, and production-grade Docker patterns.
Docker Development Workflow
Multi-Stage Dockerfile
# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Stage 3: Production (minimal image)
FROM node:20-alpine AS production
WORKDIR /app
# Non-root user
RUN addgroup -S app && adduser -S -G app app
USER app
COPY --chown=app:app --from=builder /app/dist ./dist
COPY --chown=app:app --from=deps /app/node_modules ./node_modules
COPY --chown=app:app package.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]
# Stage 4: Development (with devDependencies and hot reload)
FROM node:20-alpine AS development
WORKDIR /app
# Install all deps including devDependencies
COPY package*.json ./
RUN npm install # Not npm ci - allows updates
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
Docker Compose for Development
# docker-compose.yml
version: '3.9'
services:
app:
build:
context: .
target: development # Use dev stage
ports:
- "3000:3000"
- "9229:9229" # Debug port
volumes:
- .:/app # Bind mount for hot reload
- /app/node_modules # Anonymous volume (prevents host overwrite)
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://user:pass@postgres:5432/devdb
- REDIS_URL=redis://redis:6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
command: npm run dev
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: devdb
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d devdb"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
adminer:
image: adminer
ports:
- "8080:8080"
depends_on:
- postgres
volumes:
postgres_data:
redis_data:
Docker Compose Override for Production
# docker-compose.prod.yml
version: '3.9'
services:
app:
build:
target: production
restart: unless-stopped
environment:
- NODE_ENV=production
volumes: [] # No bind mounts in production
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
# Use override file
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Debugging in Docker
# Exec into running container
docker exec -it myapp-app-1 sh
# Debug Node.js in container
# In docker-compose.yml:
# command: node --inspect=0.0.0.0:9229 dist/server.js
# Then connect VS Code debugger to port 9229
# View logs with timestamps
docker compose logs -f --timestamps app
# Inspect container state
docker inspect myapp-app-1 | jq '.[0].State'
docker stats --no-stream
# Copy files from container
docker cp myapp-app-1:/app/logs ./local-logs
BuildKit Advanced Features
# Enable BuildKit cache mounts
# syntax=docker/dockerfile:1
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
# Cache npm cache between builds
RUN --mount=type=cache,target=/root/.npm npm ci --prefer-offline
COPY . .
RUN npm run build
# Build with BuildKit
DOCKER_BUILDKIT=1 docker build .
# Or set in daemon.json
{
"features": { "buildkit": true }
}
# Build with multiple platforms
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .
# Layer inspection
docker buildx imagetools inspect myapp:latest
docker history myapp:latest
Docker Networking
# Create custom network
docker network create myapp-network --driver bridge --subnet 172.20.0.0/16
# Connect containers
docker run --network myapp-network --name backend myapp-backend
docker run --network myapp-network --name frontend myapp-frontend
# DNS resolution: containers can reach each other by name
# http://backend:3000 works from frontend container
# Host networking (Linux only, for performance)
docker run --network host myapp
# Port inspection
docker port myapp-app-1
Useful Docker Commands
# Clean up
docker system prune -af --volumes # Remove everything unused
docker image prune -a # Remove all unused images
# Inspect build
docker buildx build --progress=plain . 2>&1 | head -50
# Size analysis
dive myapp:latest # Interactive layer explorer
# Resource usage
docker stats --format "table {{.Name}} {{.CPUPerc}} {{.MemUsage}}"