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 risksFROM node:20RUN apt-get update && apt-get install-y vim curl git
WORKDIR /appCOPY . .RUN npm installCMD ["node", "server.js"]# ✅ GOOD: Optimized for size, speed, and securityFROMnode:20-alpineASbase# Build stage — install deps only (layer caching!)FROMbaseASdepsWORKDIR /appCOPY package.json package-lock.json ./RUN npm ci --only=production && npm cache clean --force# Production stage — minimal final imageFROMbaseASproductionWORKDIR /app# Create non-root user (security best practice)RUN addgroup --system--gid 1001 appgroup &&\
 adduser --system--uid 1001 appuser
# Copy from build stageCOPY --from=deps /app/node_modules ./node_modulesCOPY . .# Set ownershipRUN chown-R appuser:appgroup /app
USER appuserEXPOSE 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 modeExit 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 modeExit fullscreen mode

Docker Compose for Local Development

# docker-compose.ymlversion:'3.9'services:app:build:context:.target:development# Use a different stage for devports:-"3000:3000"-"9229:9229"# Node debugger portenvironment:-NODE_ENV=development-DATABASE_URL=postgres://dev:dev@db:5432/myapp-REDIS_URL=redis://redis:6379volumes:-.:/app# Live reload! Code changes reflect immediately-/app/node_modules# Prevent host node_modules overwriting container'sdepends_on:db:condition:service_healthyredis:condition:service_healthyrestart:unless-stopped# Auto-restart on crashnetworks:-backenddb:image:postgres:16-alpineenvironment:POSTGRES_USER:devPOSTGRES_PASSWORD:devPOSTGRES_DB:myappvolumes:-pgdata:/var/lib/postgresql/dataports:-"5432:5432"healthcheck:test:["CMD-SHELL","pg_isready-Udev"]interval:5stimeout:3sretries:5networks:-backendredis:image:redis:7-alpinecommand:redis-server --appendonly yesvolumes:-redisdata:/dataports:-"6379:6379"healthcheck:test:["CMD","redis-cli","ping"]interval:5stimeout:3sretries:5networks:-backendvolumes:pgdata:# Named volume (persists across containers)redisdata:networks:backend:driver:bridge# Isolated network for services
Enter fullscreen modeExit 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 modeExit 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 modeExit 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 modeExit 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 modeExit fullscreen mode

Docker in CI/CD

# GitHub Actions examplename:Build & Push Docker Imageon:push:branches:[main]jobs:docker:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v4-name:Set up Docker Buildxuses:docker/setup-buildx-action@v3-name:Login to Docker Hubuses:docker/login-action@v3with:username:${{ secrets.DOCKERHUB_USERNAME }}password:${{ secrets.DOCKERHUB_TOKEN }}-name:Build and pushuses:docker/build-push-action@v5with:context:.push:truetags:|${{ secrets.DOCKERHUB_USERNAME }}/myapp:${{ github.sha }}${{ secrets.DOCKERHUB_USERNAME }}/myapp:latestcache-from:type=gha# GitHub Actions cachecache-to:type=gha,mode=max# Speed up future buildsplatforms:linux/amd64,linux/arm64# Multi-architecture support!
Enter fullscreen modeExit 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 -eTZ=Asia/Shanghai ...
# Or in Dockerfile: ENV TZ=Asia/Shanghai
Enter fullscreen modeExit fullscreen mode

What's your favorite Docker tip?

Follow @armorbreak for more practical developer guides.

Top comments (0)