Docker Deep Dive: Beyond docker run (2026) You know docker run and docker-compose up . Now let's unlock the real power of Docker for development and production. Multi-Stage Builds: Smaller, Faster, Safer Images # ❌ Single-stage: Everything in one image (HUGE) FROM node:20 WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Result: ~1.5GB image with devDependencies, source code, build tools # ✅ Multi-stage: Production image only has what it needs # Stage 1: Build FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only = production = false COPY . . RUN npm run build # Stage 2: Production runtime FROM node:20-alpine AS runner WORKDIR /app # Security: Run as non-root user RUN addgroup -g 1001 appgroup && \ adduser -u 1001 -G appgroup -D appuser USER appuser # Only copy what's needed from builder stage COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist COPY --from=builder /app/package.json ./package.json EXPOSE 3000 CMD ["node", "dist/index.js"] # Result: ~80MB image. No dev deps, no source code, no root user. Docker Compose: Full-Stack Development Setup # docker-compose.yml — Your entire stack in one file version : ' 3.8' services : # === App === app : build : context : . dockerfile : Dockerfile target : development # Use dev stage from multi-stage build ports : - " 3000:3000" environment : - NODE_ENV=development - DATABASE_URL=postgresql://app:secret@db:5432/myapp - REDIS_URL=redis://redis:6379 volumes : - .:/app # Live reload: host changes appear in container - /app/node_modules # Prevent host node_modules overwriting container's depends_on : db : condition : service_healthy redis : condition : service_started restart : unless-stopped # === Database === db : image : postgres:16-alpine environment : POSTGRES_USER : app POSTGRES_PASSWORD : secret POSTGRES_DB : myapp volumes : - pgdata:/var/lib/postgresql/data # Persist data across restarts - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro # Init script ports : - " 5432:5432" # Expose for local DB tools (pgAdmin, DBeaver) healthcheck : test : [ " CMD-SHELL" , " pg_isready -U app" ] interval : 5s timeout : 3s retries : 10 # === Redis === redis : image : redis:7-alpine command : redis-server --appendonly yes --maxmemory 128mb --maxmemory-policy allkeys-lru volumes : - redisdata:/data ports : - " 6379:6379" # === Adminer (DB management UI) === adminer : image : adminer:latest ports : - " 8080:8080" depends_on : [ db ] volumes : pgdata : # Named volume (Docker manages storage) redisdata : networks : default : name : myapp-network # Daily workflow with compose: docker compose up -d # Start everything in background docker compose logs -f app # Follow app logs docker compose exec app sh # Shell into app container docker compose exec db psql -U app # Connect to Postgres directly docker compose down # Stop everything docker compose down -v # Stop + delete volumes (!destroys data!) docker compose up -d --build # Rebuild images then start Advanced Dockerfile Techniques # Layer caching optimization FROM node:20-alpine AS base WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci # Cache layer: only invalidates when pkg files change # Separate dependency layers (cache optimization trick) FROM base AS deps COPY package.json package-lock.json ./ RUN npm ci FROM base AS dev-deps COPY package.json package-lock.json ./ RUN npm ci FROM base AS builder COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build # Build arguments (flexible builds) ARG NODE_ENV=production ENV NODE_ENV=NODEENVRUNif["{NODE_ENV} RUN if [ " NODE_ENV " = "production" ] ; then npm run build:prod ; else echo "Dev mode" ; fi # Non-root user (security best practice) FROM node:20-alpine AS production RUN apk add --no-cache tini # Proper init process (signal handling) ENTRYPOINT ["tini", "--"] USER node # Use built-in node user WORKDIR /app COPY --from=builder /app/dist ./dist CMD ["node", "dist/index.js"] # Health check (for orchestration) HEALTHCHECK --interval=30s --timeout=3s \ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1 Networking Deep Dive # Docker network types: # 1. Bridge (default) — Containers on same network can communicate docker network create my-net docker run --network my-net nginx docker run --network my-net alpine wget nginx # Can reach nginx by name! # 2. Host networking — Container shares host's network stack docker run --network host nginx # Nginx listens on host's port 80 directly (no port mapping needed!) # 3. None — No network (isolated) docker run --network none alpine # Debugging network issues: docker network inspect bridge # See network details including IP addresses docker exec cat /etc/hosts # Check DNS resolution docker exec ip addr # Check interfaces docker exec ping db # Test connectivity between containers # Port publishing vs exposing: EXPOSE 3000 # Documents the port (metadata only, doesn't publish!) -p 3000:3000 # Actually publishes to host (localhost:3000 → container:3000) -p 0.0.0.0:3000:3000 # Bind to all interfaces (accessible from other machines!) -p 127.0.0.1:3000:3000 # Bind to localhost only (safer!) # DNS round-robin (load balancing without extra tools): docker run -d --name app1 --network my-net -e PORT = 3001 myapp docker run -d --name app2 --network my-net -e PORT = 3002 myapp # Both containers register as "myapp" in DNS # Requests to "myapp" are load-balanced across them! Performance & Optimization # Image size analysis: docker history myimage:v1 # See each layer's size docker build --no-cache -t myimage:v2 . # Force rebuild (no layer cache) # Slim images checklist: # ✅ Use alpine/slim base images # ✅ Multi-stage builds (discard build tools) # ✅ .dockerignore (exclude unnecessary files) # ⚠️ .dockerignore example: node_modules npm-debug.log .git .env coverage .next dist Dockerfile docker-compose.yml README.md # Build cache optimization: # Order Dockerfile instructions from LEAST to MOST frequently changing: # 1. Base image (rarely changes) # 2. System dependencies (rarely changes) # 3. package.json/lock files (changes when deps change) # 4. Source code (changes often) # Speed up builds with BuildKit: DOCKER_BUILDKIT = 1 docker build -t myapp . # Parallel multi-platform builds: docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest . # Resource limits (prevent containers from eating your machine): docker run -m 512m --cpus = "1.5" myapp # Max 512MB RAM, 1.5 CPU cores Production Tips # Logging (structured JSON logs): docker run --log-driver = json-file --log-opt max-size = 10m --log-opt max-file = 3 myapp # Restart policies: docker run --restart = unless-stopped myapp # Always restart unless manually stopped docker run --restart = on-failure:5 myapp # Restart on failure, max 5 times # Read-only filesystem (security!): docker run --read-only --tmpfs /tmp myapp # Can't write anywhere except /tmp # Drop capabilities (principle of least privilege): docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp # Secrets management (never hardcode!): echo "my_secret_password" | docker secret create db_password - # In compose: # secrets: # db_password: # external: true # Then use in service as: /run/secrets/db_password # Cleanup routine (run weekly): docker system prune -af --volumes # Remove EVERYTHING unused # Free space: can reclaim GBs of old images, stopped containers, unused volumes What's your favorite Docker tip? What Docker topic do you want to learn more about? Follow @armorbreak for more practical developer guides.