DEV Community

Alex Chen
Alex Chen

Posted on

Docker Deep Dive: Beyond docker run (2026)

Docker Deep Dive: Beyond docker run (2026)

You know docker run and docker build. But production Docker is so much more. Here's what you need to know for real-world container workflows.

Dockerfile Best Practices

# ✅ Production-ready Dockerfile pattern:

# 1. Use specific version tags (not "latest"!)
FROM node:22-alpine3.20 AS base

# 2. Set working directory early
WORKDIR /app

# 3. Install dependencies FIRST (layer caching!)
#    This layer only changes when package.json changes
COPY package.json package-lock.json ./
RUN npm ci --only=production && npm cache clean --force

# 4. Copy source code (changes frequently, placed after deps)
COPY . .

# 5. Non-root user for security!
RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -D appuser && \
    chown -R appuser:appgroup /app

USER appuser

# 6. Expose port
EXPOSE 3000

# 7. Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

# 8. Use ENTRYPOINT + CMD for flexibility
ENTRYPOINT ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode
# Multi-stage builds — smaller, more secure images:
# Stage 1: Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production image (ONLY contains built output!)
FROM node:22-alpine AS runner
WORKDIR /app

RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -D appuser

# Copy ONLY what's needed from builder stage
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/package.json .

USER appuser
EXPOSE 3000
HEALTHCHECK CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]

# Result: ~150MB instead of ~800MB+ with full dev toolchain!
Enter fullscreen mode Exit fullscreen mode

Essential Docker Commands Beyond the Basics

# === Image Management ===
docker images                          # List images
docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}"  # Compact view
docker history myimage:latest          # See all layers of an image
docker inspect myimage:latest          # Full JSON details of an image
docker image prune                    # Remove unused images
docker image prune -a                  # Remove ALL unused images (free significant space!)

# === Container Lifecycle Deep Dive ===
docker ps -a                           # All containers including stopped
docker stats                           # Live resource usage (CPU, MEM, NET, I/O)
docker top <container>                 # Processes running inside container
docker port <container>                # Port mappings
docker diff <container>                # File changes vs original image
docker update --memory="512m" --cpus=1 <container>  # Update resource limits live!

# === Inspecting & Debugging ===
docker logs -f --tail=100 <container>   # Follow last 100 lines
docker exec -it <container> sh          # Interactive shell inside running container
docker exec -it <container> node -e "console.log('hello')"  # Run command in container
docker cp <container>:/app/config ./local-config  # Copy files OUT of container
docker cp ./local-config <container>:/app/config  # Copy files INTO container

# === Network Deep Dive ===
docker network ls                       # List networks
docker network inspect bridge           # Inspect default network
docker network create app-net --driver bridge  # Custom network
docker run --network=app-net nginx     # Container on custom network
# Containers on same custom network can communicate by name!

# === Volume Management ===
docker volume ls                        # List volumes
docker volume create data-vol           # Create named volume
docker volume inspect data-vol          # Volume details (where it's stored)
docker run -v data-vol:/data alpine     # Mount named volume
docker run -v $(pwd)/config:/config:ro alpine  # Read-only bind mount
docker run --tmpfs /tmp alpine         # In-memory tmpfs (fast, ephemeral)
Enter fullscreen mode Exit fullscreen mode

Docker Compose for Development

# docker-compose.dev.yml — Developer-friendly setup
version: "3.8"

services:
  app:
    build:
      context: .
      target: development       # Use a different Dockerfile stage for dev
    volumes:
      - .:/app                  # Hot-reload: local changes reflect immediately
      - /app/node_modules       # Prevent host overwriting container's node_modules
      - npm-cache:/root/.npm    # Persist npm cache across rebuilds
    environment:
      NODE_ENV: development
      DEBUG: app:*
    ports:
      - "3000:3000"
      - "9229:9229"            # Node.js debugger port
    command: sh -c "npm install && npm run dev"  # Auto-install on start
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    ports:
      - "5432:5432"            # Expose locally for GUI tools (pgAdmin, DBeaver)
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev123
      POSTGRES_DB: myapp_dev
    volumes:
      - pg-data:/var/lib/postgresql/data
      - ./init-dev.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev"]
      interval: 5s
      timeout: 3s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes

  adminer:                      # Web-based DB admin UI (lighter than pgAdmin!)
    image: adminer:latest
    ports:
      - "8080:8080"

volumes:
  pg-data:
  npm-cache:
Enter fullscreen mode Exit fullscreen mode

Performance Optimization

# 1. Slim down images:
# Use Alpine-based images (~5MB base) instead of Debian/Ubuntu (~100MB+)
# Multi-stage builds remove all build tools from final image

# 2. Layer caching strategy:
# Order Dockerfile instructions from LEAST frequent change to MOST frequent:
# 1. Base image + OS packages (rarely changes)
# 2. System dependencies (rarely changes)
# 3. package.json + npm ci (changes when deps change)
# 4. Source code copy (changes every commit)
# 5. Build step (changes with source)

# 3. .dockerignore (like .gitgitignore but for docker context):
node_modules
dist
.git
.env*
*.log
Dockerfile
docker-compose*.yml
README.md
.nyc_output
coverage
.dockerignore

# 4. Resource limits:
docker run --memory="512m" --cpus="1.0" myimage  # Limit memory + CPU
# In compose.yml:
deploy:
  resources:
    limits:
      memory: 512M
      cpus: '1.0'

# 5. Clean up apt caches in same RUN instruction:
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 && rm -rf /var/lib/apt/lists/*
# Each RUN creates a layer! Clean in the same layer to keep image small.
Enter fullscreen mode Exit fullscreen mode

Troubleshooting Common Issues

# Container won't start?
docker logs <container>              # Always check this first!
docker inspect <container>           # Check config, state, health
docker events --since 5m            # Recent Docker daemon events

# Can't access localhost port?
docker ps                            # Is it running?
docker port <container>             # Is port mapped?
ss -tlnp | grep :3000               # Is something else using the port?
docker run --network=host ...        # Debug: use host networking to bypass port issues

# Out of disk space?
docker system df                     # Overview of Docker disk usage
docker system df -v                  # Detailed breakdown
docker system prune -a               # Remove ALL unused images, containers, networks, volumes
docker volume prune                  # Remove unused volumes

# Permission denied errors?
# Files created in container are owned by root by default
# Fix: Use non-root user in Dockerfile (see best practices above)
# Or: docker run -u $(id -u):$(id -g) ...

# DNS issues inside containers?
docker run --dns=8.8.8.8 myimage     # Override DNS
# Or configure in daemon.json

# Slow build times?
# 1. Use .dockerexclude to reduce context size
# 2. Leverage layer caching (order instructions properly!)
# 3. Use BuildKit (enabled by default in modern Docker):
DOCKER_BUILDKIT=1 docker build .
Enter fullscreen mode Exit fullscreen mode

What's your most useful Docker tip? What Docker problem took you forever to solve?

Follow @armorbreak for more practical developer guides.

Top comments (0)