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 -p 3000:3000. Here's everything else that makes Docker powerful in production.

Dockerfile Best Practices

# ❌ BAD: Bloated image, slow builds, security risks
FROM node:20

RUN apt-get update && apt-get install -y vim curl git
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]

# ✅ GOOD: Optimized for size, speed, and security
FROM node:20-alpine AS base

# Build stage — install deps only (layer caching!)
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production && npm cache clean --force

# Production stage — minimal final image
FROM base AS production
WORKDIR /app

# Create non-root user (security best practice)
RUN addgroup --system --gid 1001 appgroup && \
    adduser --system --uid 1001 appuser

# Copy from build stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Set ownership
RUN chown -R appuser:appgroup /app
USER appuser

EXPOSE 3000

# Health check (Docker can monitor your app!)
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

Key Dockerfile Rules

1. Layer caching is your friend
   → COPY package.json FIRST (before source code)
   → npm install runs only when package.json changes
   → Source code changes don't trigger reinstall!

2. Use .dockerignore (like .gitignore for Docker)
   node_modules          # Rebuild fresh each time
   .git                  # Don't copy repo history
   .env                  # Secrets!
   *.md                  # Documentation not needed at runtime
   .DS_Store             # macOS junk
   coverage/             # Test output
   dist/                 # If you're building inside Docker

3. Alpine images are tiny (~8MB vs ~180MB for full Node)
   Trade-off: Some tools missing (glibc compatibility issues)
   For most Node apps: alpine works perfectly fine

4. Multi-stage builds = smaller production images
   Build stage has all dev tools
   Production stage only gets what's needed
   Result: Image goes from 500MB+ to ~80MB

5. Never run as root
   USER appuser after setup
   Prevents container escape attacks
   Required by many Kubernetes clusters anyway
Enter fullscreen mode Exit fullscreen mode

Docker Compose for Local Development

# docker-compose.yml
version: '3.9'

services:
  app:
    build:
      context: .
      target: development       # Use a different stage for dev
    ports:
      - "3000:3000"
      - "9229:9229"            # Node debugger port
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgres://dev:dev@db:5432/myapp
      - REDIS_URL=redis://redis:6379
    volumes:
      - .:/app                  # Live reload! Code changes reflect immediately
      - /app/node_modules       # Prevent host node_modules overwriting container's
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped     # Auto-restart on crash
    networks:
      - backend

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - backend

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redisdata:/data
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - backend

volumes:
  pgdata:                       # Named volume (persists across containers)
  redisdata:

networks:
  backend:
    driver: bridge              # Isolated network for services
Enter fullscreen mode Exit fullscreen mode

Essential Compose Commands

# Start everything
docker compose up -d            # Detached mode (background)
docker compose up               # Foreground (see logs live)

# Development workflow
docker compose up --build       # Rebuild before starting
docker compose logs -f app      # Follow logs for one service
docker compose logs -f --tail=50  # Last 50 lines, then follow
docker compose exec app sh      # Shell into running container
docker compose exec app npm run migrate  # Run commands inside container

# Debugging
docker compose ps               # Show all services status
docker compose top              # Show processes in each container
docker compose stats            # Resource usage (CPU, memory, network)

# Cleanup
docker compose down             # Stop and remove containers + networks
docker compose down -v          # Also remove volumes (resets data!)
docker compose down --rmi all   # Also remove images (full reset)

# One-off tasks
docker compose run --rm app npm run test         # Run tests then exit
docker compose run --rm db psql -U dev -d myapp   # Connect to DB directly
Enter fullscreen mode Exit fullscreen mode

Docker Networks Explained

# Default networks:
# bridge   → Default, containers on same host can communicate
# host     → Container uses host's network stack (no isolation)
# none     → No networking (rarely used)

# Custom networks (recommended):
docker network create myapp-net
docker run --network myapp-net ...

# DNS resolution works automatically!
# Container names become hostnames:
# app container can reach db via: postgres://db:5432/myapp

# Network types:
bridge     # Most common, isolated per network
host       # Direct host access (good for performance monitoring)
overlay    # Multi-host (Docker Swarm/Kubernetes)
macvlan    # Container gets its own MAC address (appears as physical device)
ipvlan     # Like macvlan but shares host MAC
Enter fullscreen mode Exit fullscreen mode

Volumes vs Bind Mounts

# Bind mount: Host path → Container path
# Good for: Development (live code sync), config files
docker run -v $(pwd)/src:/app/src nginx

# Named volume: Managed by Docker, stored in /var/lib/docker/volumes/
# Good for: Data persistence, databases, caches
docker run -v mydata:/data/app nginx

# Read-only bind mount (security!)
docker run -v $(pwd)/config:/etc/app/config:ro nginx

# tmpfs: In-memory only (gone when container stops)
# Good for: Temporary files, secrets, sensitive processing
docker run --tmpfs /tmp:rw,noexec,nosuid,size=100m nginx

# Volume lifecycle:
docker volume create mydata           # Create
docker volume inspect mydata          # See details (where it lives)
docker volume ls                      # List all
docker volume prune                   # Remove unused volumes (frees disk space!)
Enter fullscreen mode Exit fullscreen mode

Useful Docker Commands You Might Not Know

# Container management
docker ps -a                          # All containers (including stopped)
docker stats                          # Real-time resource usage (like top)
docker top <container>                # Processes inside container
docker update --memory=512m <container>  # Update resource limits live!
docker rename old-name new-name       # Rename a container

# Inspecting
docker inspect <container> | jq '.[0].NetworkSettings.IPAddress'  # Get IP
docker inspect <container> --format='{{.State.Status}}'            # Quick status
docker logs <container> --since 1h         # Logs from last hour
docker logs <container> --tail 100 -f      # Tail last 100 lines

# Copying files
docker cp file.txt container:/path/inside/   # Host → Container
docker cp container:/path/file.txt ./local/  # Container → Host

# Cleaning up (run regularly!)
docker system prune                    # Remove stopped containers, unused networks, dangling images
docker system prune -a                 # ALSO remove unused images (not just dangling)
docker volume prune                    # Remove unused volumes
docker builder prune                   # Remove build cache (can be GBs!)

# Image management
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"  # Pretty table
docker history <image>                 # See each layer and its size
docker save myimage | gzip > image.tar.gz  # Export image for transfer
docker load < image.tar.gz             # Import exported image

# Debugging a failing container
docker run --rm -it --entrypoint sh myimage  # Override entrypoint to get shell
docker commit <container> myimage:debug      # Save container state as new image
Enter fullscreen mode Exit fullscreen mode

Docker in CI/CD

# GitHub Actions example
name: Build & Push Docker Image
on:
  push:
    branches: [main]

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ secrets.DOCKERHUB_USERNAME }}/myapp:${{ github.sha }}
            ${{ secrets.DOCKERHUB_USERNAME }}/myapp:latest
          cache-from: type=gha              # GitHub Actions cache
          cache-to: type=gha,mode=max        # Speed up future builds
          platforms: linux/amd64,linux/arm64 # Multi-architecture support!
Enter fullscreen mode Exit fullscreen mode

Common Docker Problems & Solutions

# Problem: Container exits immediately
# Solution: Check the process! Containers die when their main process exits.
docker logs <container>                     # Always first step
# Fix: Make sure your CMD/ENTRYPOINT runs a long-running process

# Problem: Permission denied errors
# Solution: UID mismatch between host and container
# In Dockerfile: RUN adduser --uid 1001 appuser; USER appuser
# Or: docker run -u $(id -u):$(id -g) ...

# Problem: Disk space full
# Solution: Regular cleanup
docker system df                           # See what's using space
docker system prune -a --volumes           # Nuclear option (frees most space)

# Problem: Networking issues (can't reach other containers)
# Solution: Check they're on the same network
docker network inspect <network-name>
# Ensure both services use the same network in compose

# Problem: Stale layers making rebuilds slow
# Solution: Clear build cache
docker builder prune
# Then rebuild with --no-cache once

# Problem: Timezone wrong inside container
# Solution: Pass TZ environment variable
docker run -e TZ=Asia/Shanghai ...
# Or in Dockerfile: ENV TZ=Asia/Shanghai
Enter fullscreen mode Exit fullscreen mode

What's your favorite Docker tip?

Follow @armorbreak for more practical developer guides.

Top comments (0)