Practical Docker reference: essential CLI commands, Dockerfile best practices, multi-stage builds, Docker Compose, networking, and cleanup one-liners.
Docker turned container technology from a kernel curiosity into the default unit of software packaging. Whether you’re building CI pipelines, running local development stacks, or shipping microservices to Kubernetes, the same small set of Docker primitives appears again and again. These notes are a field reference — not a tutorial — for the commands and patterns that actually matter in day-to-day work.
# Build an image from a Dockerfile in the current directorydocker build -t myapp:1.0.0 .# Build with a specific Dockerfile and build argsdocker build \ -f docker/Dockerfile.prod \ --build-arg APP_VERSION=1.0.0 \ --build-arg NODE_ENV=production \ -t myapp:1.0.0 .# List local imagesdocker imagesdocker image ls --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"# Pull / pushdocker pull nginx:1.25-alpinedocker push registry.example.com/myapp:1.0.0# Tag an existing imagedocker tag myapp:1.0.0 registry.example.com/myapp:latest# Remove an imagedocker rmi myapp:1.0.0# Remove all dangling (untagged) imagesdocker image prune# Remove ALL unused images (not just dangling)docker image prune -a
# Run a container (foreground, removed on exit)docker run --rm -it ubuntu:22.04 bash# Run detached (background), name it, map portsdocker run -d \ --name web \ -p 8080:80 \ -e APP_ENV=production \ nginx:1.25-alpine# List running containersdocker ps# List all containers including stopped onesdocker ps -a# Execute a command in a running containerdocker exec -it web sh# Stream logs (follow mode)docker logs -f web# Last 100 lines with timestampsdocker logs --tail 100 --timestamps web# Stop / start / restartdocker stop webdocker start webdocker restart web# Remove a stopped containerdocker rm web# Remove a running container forcefullydocker rm -f web# Remove all stopped containersdocker container prune
# Mount a host directory into the containerdocker run -v $(pwd)/data:/app/data myapp:1.0.0# Named volumedocker run -v app-data:/app/data myapp:1.0.0# List volumesdocker volume ls# Inspect a container (full JSON metadata)docker inspect web# Quick IP address lookupdocker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web# Copy a file out of a containerdocker cp web:/etc/nginx/nginx.conf ./nginx.conf# Show resource usage (live)docker stats# Show running processes inside a containerdocker top web
# ── Stage 1: Build ────────────────────────────────────────────────────────────FROM node:20-alpine AS builderWORKDIR /app# Copy dependency manifests first (better layer caching)COPY package.json package-lock.json ./RUN npm ci --omit=devCOPY . .RUN npm run build # outputs to /app/dist# ── Stage 2: Runtime ──────────────────────────────────────────────────────────FROM node:20-alpine AS runtime# Install only production OS depsRUN apk add --no-cache dumb-initWORKDIR /app# Non-root userRUN addgroup -S appgroup && adduser -S appuser -G appgroup# Copy ONLY the built artefact and production node_modulesCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesCOPY package.json ./USER appuserEXPOSE 3000ENTRYPOINT ["dumb-init", "--"]CMD ["node", "dist/server.js"]
The final image contains zero build tools (npm, compilers, dev dependencies). The AS builder stage is discarded — only its output is copied. This typically cuts image size by 60–80%.
Docker Compose is the right tool for local development stacks and single-host multi-container deployments.
# compose.yaml (preferred filename in Compose V2)name: myappservices: app: build: context: . target: runtime # target a specific stage in multi-stage Dockerfile image: myapp:dev ports: - "3000:3000" environment: DATABASE_URL: postgres://user:pass@db:5432/mydb REDIS_URL: redis://cache:6379 depends_on: db: condition: service_healthy # wait for the healthcheck to pass cache: condition: service_started volumes: - ./src:/app/src # hot-reload in development restart: unless-stopped db: image: postgres:16-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: mydb volumes: - pg-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d mydb"] interval: 5s timeout: 5s retries: 5 cache: image: redis:7-alpine command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lruvolumes: pg-data:
# Common Compose commandsdocker compose up -d # start all services detacheddocker compose up -d --build # rebuild images before startingdocker compose logs -f app # follow logs for the app servicedocker compose ps # status of all servicesdocker compose exec app sh # shell into the running app containerdocker compose down # stop and remove containersdocker compose down -v # also remove named volumes (destructive!)docker compose restart app # restart a single service
Docker creates three default networks. In practice you’ll use two of them:
Network
Driver
Use Case
bridge
bridge
Default for docker run. Containers are isolated from the host.
host
host
Container shares the host’s network stack. Maximum performance, zero isolation.
none
null
No networking. Useful for batch/offline jobs.
# Create a user-defined bridge network (recommended over default bridge)docker network create myapp-net# Containers on the same user-defined network can reach each other by namedocker run -d --name api --network myapp-net myapp:1.0.0docker run -d --name proxy --network myapp-net nginx:alpine# Inside the proxy container, "api" resolves to the api container's IP# (Docker's embedded DNS handles this automatically)# Inspect network topologydocker network inspect myapp-net# List all networksdocker network ls
Always use user-defined bridge networks instead of the default bridge. User-defined networks get automatic DNS resolution between containers by name, which the default bridge does not provide.
Container and image sprawl is a real issue on long-running build hosts. These commands keep things tidy:
# Remove all stopped containersdocker container prune -f# Remove all dangling images (untagged layers)docker image prune -f# Remove ALL unused images (not referenced by any container)docker image prune -a -f# Remove unused volumesdocker volume prune -f# Remove unused networksdocker network prune -f# Nuclear option: remove everything not in use# (stopped containers, dangling images, unused networks, unused volumes)docker system prune -a -f --volumes# Show disk usage breakdowndocker system df# Show verbose disk usage (per object)docker system df -v
docker system prune -a --volumes is destructive. On a production build host, be specific — prune only dangling images and stopped containers rather than running the nuclear option.