Docker Cheat Sheet: Commands Every Developer Should Know

Every Docker command worth memorizing — from basic container management to multi-stage builds, volume strategies, network configuration, and the security practices that keep production safe.

Image Commands

Images are the building blocks of Docker. Every container starts from an image, and every deployment begins with building or pulling one. These are the commands you will use most.

Building Images

bash — Build commands
# Build from Dockerfile in current directory
docker build -t myapp:latest .

# Build with a specific Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .

# Build with build arguments
docker build --build-arg NODE_ENV=production -t myapp:prod .

# Build without using cache (force rebuild all layers)
docker build --no-cache -t myapp:latest .

# Build for a specific platform
docker build --platform linux/amd64 -t myapp:amd64 .

# Build for multiple platforms (requires buildx)
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:multi .

Managing Images

bash — Image management
# List all local images
docker images

# List images with size and details
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"

# Pull an image from a registry
docker pull node:22-alpine

# Push an image to a registry
docker push myregistry.com/myapp:latest

# Tag an image (create an alias)
docker tag myapp:latest myregistry.com/myapp:v1.2.3

# Remove an image
docker rmi myapp:latest

# Remove all unused images (dangling)
docker image prune

# Remove ALL unused images (not just dangling)
docker image prune -a

# Inspect image layers and metadata
docker inspect node:22-alpine

# View the history of an image (see each layer)
docker history myapp:latest
Image Naming Convention

Use the format registry/namespace/name:tag. For production, always use specific version tags (node:22.2-alpine) instead of latest. The latest tag is mutable and can point to different images at different times, making builds non-reproducible.

Container Commands

Containers are running instances of images. These commands cover the full lifecycle from creation to removal.

Running Containers

bash — Run commands
# Run a container in the foreground
docker run myapp:latest

# Run in detached mode (background)
docker run -d --name myapp myapp:latest

# Run with port mapping (host:container)
docker run -d -p 3000:3000 myapp:latest

# Run with multiple port mappings
docker run -d -p 3000:3000 -p 9229:9229 myapp:latest

# Run with environment variables
docker run -d -e NODE_ENV=production -e DB_HOST=db myapp:latest

# Run with an env file
docker run -d --env-file .env myapp:latest

# Run with a volume mount
docker run -d -v mydata:/app/data myapp:latest

# Run interactively with a shell
docker run -it node:22-alpine /bin/sh

# Run with automatic removal when stopped
docker run --rm myapp:latest

# Run with resource limits
docker run -d --memory=512m --cpus=1.5 myapp:latest

# Run with a restart policy
docker run -d --restart=unless-stopped myapp:latest

When managing environment variables across multiple containers and environments, the .env File Editor helps you create and validate .env files without syntax errors.

Managing Running Containers

bash — Container lifecycle
# List running containers
docker ps

# List ALL containers (including stopped)
docker ps -a

# Stop a container gracefully (SIGTERM, then SIGKILL after 10s)
docker stop myapp

# Stop with a custom timeout
docker stop -t 30 myapp

# Start a stopped container
docker start myapp

# Restart a container
docker restart myapp

# Kill a container immediately (SIGKILL)
docker kill myapp

# Remove a stopped container
docker rm myapp

# Force remove a running container
docker rm -f myapp

# Remove all stopped containers
docker container prune

# Rename a container
docker rename old_name new_name

Volumes and Data Persistence

Containers are ephemeral. When a container is removed, its writable layer is gone. Volumes solve this by storing data outside the container filesystem.

bash — Volume commands
# Create a named volume
docker volume create mydata

# List all volumes
docker volume ls

# Inspect a volume (see mount point, driver, etc.)
docker volume inspect mydata

# Remove a volume
docker volume rm mydata

# Remove all unused volumes
docker volume prune

# Run a container with a named volume
docker run -d -v mydata:/app/data myapp:latest

# Run with a bind mount (host directory)
docker run -d -v $(pwd)/src:/app/src myapp:latest

# Run with a read-only bind mount
docker run -d -v $(pwd)/config:/app/config:ro myapp:latest

# Copy files between host and container
docker cp myapp:/app/logs ./logs
docker cp ./config.json myapp:/app/config.json

Volume Types Compared

Type Syntax Best For
Named Volume -v mydata:/app/data Database storage, persistent app data
Bind Mount -v /host/path:/container/path Development (live code reloading)
tmpfs Mount --tmpfs /tmp Temporary data, secrets in memory
Anonymous Volume -v /app/data Avoid (hard to manage, no name)

Network Commands

Docker networks let containers communicate with each other and with the outside world. By default, containers on the same network can reach each other by container name.

bash — Network commands
# List all networks
docker network ls

# Create a custom bridge network
docker network create mynetwork

# Create a network with a specific subnet
docker network create --subnet=172.20.0.0/16 mynetwork

# Run a container on a specific network
docker run -d --network mynetwork --name api myapp:latest

# Connect a running container to a network
docker network connect mynetwork myapp

# Disconnect a container from a network
docker network disconnect mynetwork myapp

# Inspect a network (see connected containers, IPs)
docker network inspect mynetwork

# Remove a network
docker network rm mynetwork

# Remove all unused networks
docker network prune
DNS Resolution in Custom Networks

Containers on the default bridge network can only reach each other by IP address. Containers on a custom bridge network get automatic DNS resolution by container name. Always create a custom network for multi-container applications.

Dockerfile Best Practices

A well-structured Dockerfile produces smaller images, builds faster, and is easier to maintain. These patterns apply to most languages and frameworks.

Dockerfile — Node.js production image
# Use specific version, not 'latest'
FROM node:22-alpine AS base

# Set working directory
WORKDIR /app

# Copy dependency files first (cache optimization)
COPY package.json package-lock.json ./

# Install production dependencies only
RUN npm ci --only=production && npm cache clean --force

# Copy application code
COPY . .

# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Switch to non-root user
USER appuser

# Expose port (documentation, not enforcement)
EXPOSE 3000

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

# Use exec form for CMD (PID 1 signal handling)
CMD ["node", "server.js"]

Layer Caching Rules

Docker caches each layer. When a layer changes, all subsequent layers are rebuilt. This means order matters:

  1. System dependencies first (rarely change)
  2. Language dependencies second (change occasionally)
  3. Application code last (changes frequently)

By copying package.json and running npm ci before copying the rest of the code, Docker can reuse the cached dependency layer whenever only your application code changes.

The .dockerignore File

.dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
.env.*
Dockerfile
docker-compose*.yml
.dockerignore
README.md
.vscode
.idea
coverage
dist
*.md

Without .dockerignore, the entire directory (including node_modules, .git, and other large directories) is sent to the Docker daemon as build context. This slows builds dramatically and can leak secrets into image layers.

Multi-Stage Builds

Multi-stage builds use multiple FROM statements in a single Dockerfile. Each stage can use a different base image and only the final stage becomes the output image. This separates build tools from the runtime, producing much smaller images.

Dockerfile — Multi-stage build
# 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
FROM node:22-alpine AS production
WORKDIR /app

# Only install production dependencies
COPY package.json package-lock.json ./
RUN npm ci --only=production && npm cache clean --force

# Copy only the build output from stage 1
COPY --from=builder /app/dist ./dist

# Non-root user
RUN addgroup -S app && adduser -S app -G app
USER app

EXPOSE 3000
CMD ["node", "dist/server.js"]

The builder stage includes TypeScript, Webpack, and all dev dependencies. The production stage contains only the compiled JavaScript and runtime dependencies. A typical Node.js image drops from 400MB+ to under 100MB with this pattern.

Target Specific Stages

Build a specific stage with docker build --target builder -t myapp:build .. This is useful for CI pipelines where you want to run tests in the builder stage before building the production image.

Generate Docker Compose Configs Instantly

The QTool Docker Compose Generator builds complete compose files for common stacks. Select your services, configure ports and volumes, and copy the result.

Open Compose Generator YAML Formatter

Docker Compose

Docker Compose defines multi-container applications in a single YAML file. Instead of running five docker run commands with complex arguments, you declare everything once and run docker compose up.

compose.yaml — Full-stack application
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    restart: unless-stopped
    networks:
      - backend

  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - backend

  cache:
    image: redis:7-alpine
    volumes:
      - redisdata:/data
    command: redis-server --appendonly yes
    networks:
      - backend

volumes:
  pgdata:
  redisdata:

networks:
  backend:
    driver: bridge

Compose Commands

bash — Docker Compose commands
# Start all services (foreground)
docker compose up

# Start in detached mode
docker compose up -d

# Start and rebuild images
docker compose up -d --build

# Stop all services
docker compose down

# Stop and remove volumes (deletes data!)
docker compose down -v

# View logs from all services
docker compose logs

# Follow logs from a specific service
docker compose logs -f app

# Scale a service to multiple instances
docker compose up -d --scale app=3

# Execute a command in a running service
docker compose exec app /bin/sh

# View service status
docker compose ps

# Pull latest images for all services
docker compose pull

# Restart a single service
docker compose restart app

When validating your compose files, the YAML Formatter catches syntax errors and inconsistent indentation before Docker does.

Debugging and Inspection

When containers misbehave, these commands help you understand what is happening inside them.

bash — Debugging commands
# View container logs
docker logs myapp
docker logs myapp --tail 100
docker logs myapp -f              # Follow in real time
docker logs myapp --since 1h      # Last hour only

# Execute a shell inside a running container
docker exec -it myapp /bin/sh
docker exec -it myapp /bin/bash

# Run a specific command inside a container
docker exec myapp cat /etc/hosts
docker exec myapp env              # View environment variables

# Inspect container details (JSON)
docker inspect myapp

# View resource usage (live)
docker stats
docker stats myapp                 # Single container

# View filesystem changes since container started
docker diff myapp

# View running processes inside a container
docker top myapp

# View Docker system-wide events
docker events
docker events --filter type=container

# Export a container's filesystem as a tar archive
docker export myapp > myapp-fs.tar

# View system disk usage
docker system df
docker system df -v                # Verbose, per-image breakdown
Debugging Crashed Containers

If a container exits immediately, docker exec will not work because there is no running process to attach to. Instead, start a new container from the same image with an interactive shell: docker run -it myapp:latest /bin/sh. This lets you inspect the filesystem, test commands, and find the root cause.

Image Optimization

Smaller images mean faster pulls, faster deploys, and less attack surface. Here are the techniques that make the biggest difference.

Choose the Right Base Image

Base Image Size Use Case
alpine ~5 MB Minimal Linux with apk package manager
node:22-alpine ~50 MB Node.js apps (smallest official Node image)
node:22-slim ~80 MB Node.js when alpine causes compatibility issues
node:22 ~350 MB Node.js with full Debian (build tools included)
gcr.io/distroless/nodejs ~40 MB No shell, no package manager (maximum security)
scratch 0 MB Statically compiled binaries (Go, Rust)

Reduce Layer Count

Dockerfile — Combining commands
# Bad: 3 separate layers
RUN apt-get update
RUN apt-get install -y curl wget
RUN rm -rf /var/lib/apt/lists/*

# Good: 1 layer, smaller image
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl wget && \
    rm -rf /var/lib/apt/lists/*

Each RUN instruction creates a new layer. If you install packages in one layer and delete caches in another, the cache still exists in the first layer — deletion in a later layer only hides it with a whiteout file. Combine related operations in a single RUN statement.

When you need to inspect the JSON configuration files that Docker commands produce, the JSON Formatter makes docker inspect output readable.

Security Best Practices

Container security is not optional. A single misconfigured container can expose your entire infrastructure. These practices should be the default for every production deployment.

Run as Non-Root

Dockerfile — Non-root user
# Create a dedicated user and group
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Set ownership of application files
COPY --chown=appuser:appgroup . /app

# Switch to non-root user
USER appuser

Security Flags at Runtime

bash — Security-hardened run
# Drop all capabilities, add only what's needed
docker run -d \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --read-only \
  --tmpfs /tmp \
  --security-opt no-new-privileges \
  --user 1000:1000 \
  myapp:latest

Security Checklist

Never Do This in Production

Running docker run --privileged gives the container full access to the host kernel. It disables all security isolation. There is almost never a legitimate reason to use it in production. If you think you need it, you probably need a specific Linux capability (--cap-add) instead.

For reverse proxy configuration in front of your Docker containers, the Nginx Config Generator produces production-ready configs with SSL, caching, and upstream blocks.

Docker Developer Tools


Frequently Asked Questions

A Docker image is a read-only template that contains the application code, runtime, libraries, and configuration needed to run software. Think of it as a blueprint or a snapshot. A container is a running instance of an image. When you run docker run, Docker creates a writable layer on top of the image and starts a process inside an isolated environment. You can run multiple containers from the same image, and each container has its own filesystem, network, and process space. Images are built with docker build and stored in registries. Containers are created with docker run and managed with docker start, docker stop, and docker rm.

Use multi-stage builds to separate the build environment from the runtime image. Start FROM a large image with build tools, compile your application, then COPY only the compiled output into a minimal base image like alpine or distroless. Combine RUN commands with && to reduce layers. Use .dockerignore to exclude node_modules, .git, and other unnecessary files from the build context. Choose the smallest base image that works: alpine images are typically 5-10MB compared to 100MB+ for ubuntu-based images. Remove package manager caches in the same layer that installs packages (for example, rm -rf /var/lib/apt/lists/* in the same RUN command as apt-get install). Order Dockerfile instructions from least to most frequently changed so Docker can cache intermediate layers effectively.

Docker Compose is a tool for defining and running multi-container applications using a YAML file (docker-compose.yml or compose.yaml). Instead of running multiple docker run commands with long argument lists, you declare all your services, networks, and volumes in one file and start everything with docker compose up. Use Compose when your application has multiple services that need to communicate (for example, a web server, a database, and a cache), when you want reproducible development environments that match across team members, or when you need to define environment variables, port mappings, volume mounts, and health checks in a version-controlled file. Compose is standard for local development and testing. For production orchestration at scale, teams typically use Kubernetes or Docker Swarm, though Compose is increasingly used in production for simpler deployments.

Use Docker volumes. Containers have writable layers, but any data written inside a container is lost when the container is removed. Volumes store data outside the container's filesystem and persist across container restarts and removals. There are three approaches: named volumes (docker volume create mydata, then mount with -v mydata:/app/data) are managed by Docker and stored in /var/lib/docker/volumes; bind mounts (-v /host/path:/container/path) map a specific host directory into the container; and tmpfs mounts (--tmpfs /tmp) store data in memory only. Named volumes are preferred for databases and persistent application data because they work across platforms and can be backed up with docker volume commands. Bind mounts are useful for development when you want live code reloading from your host filesystem.

Start with docker logs <container> to see stdout and stderr output, adding --tail 100 to see the last 100 lines or -f to follow logs in real time. If the container exits immediately, run it interactively with docker run -it <image> /bin/sh to get a shell and inspect the environment. Use docker inspect <container> to see the full configuration including environment variables, mounts, and network settings. Check docker events for system-level events. If the container runs but the application is not working, use docker exec -it <container> /bin/sh to open a shell inside the running container. For resource issues, docker stats shows live CPU, memory, and I/O usage. To see what changed in the filesystem since the container started, use docker diff <container>. For networking issues, docker network inspect <network> shows the network configuration and connected containers.

Always run containers as a non-root user in production. By default, the process inside a Docker container runs as root, which means if an attacker escapes the container, they have root access to the host. Add a USER instruction to your Dockerfile to switch to a non-root user after installing dependencies. For example: RUN addgroup -S appgroup && adduser -S appuser -G appgroup, then USER appuser. Install packages and copy files before switching users since those operations need root. You can also enforce this at runtime with docker run --user 1000:1000. Many official images like node and python already include a non-root user. Additionally, use read-only file systems (--read-only flag) where possible, drop all Linux capabilities (--cap-drop ALL) and add back only what is needed, and never store secrets in environment variables or image layers.

NT

Christian Bucher

We build free developer tools for Docker, DevOps, and web development. 269 tool pages, all browser-based, no signup required.

269 Developer Tools, One Place

Browse 269 indexed tool pages with no QTool account required, and inspect the source on GitHub.

Open Source — Free Forever Try Free Tools

Related Tools

CSS Box Shadow Generator · Emoji Picker & Search · Free Git Diff Viewer

Related Tools

Free API Mock Server · Code Screenshot Generator - Beautiful Code Images · Free Color Palette Generator

Related Articles

Built by Miguel

Need a custom tool or website?

From . Delivered in 24-48h. You own the code.

View Services →