The commands you reach for every day — and the ones you search every time. This reference covers everything from basic container management to multi-service Compose workflows.
Quick reference
The 20 commands that cover 90% of daily Docker work.
| Command | What it does |
|---|---|
docker run -it ubuntu bash |
Start container with interactive shell |
docker run -d -p 8080:80 nginx |
Run in background, map port |
docker ps |
List running containers |
docker ps -a |
List all containers (including stopped) |
docker stop <id> |
Stop a running container |
docker rm <id> |
Remove a stopped container |
docker images |
List local images |
docker pull nginx |
Download image from registry |
docker build -t myapp . |
Build image from Dockerfile |
docker exec -it <id> bash |
Open shell inside running container |
docker logs <id> |
View container output |
docker logs -f <id> |
Follow live logs |
docker inspect <id> |
Full container JSON metadata |
docker cp file.txt <id>:/app/ |
Copy file into container |
docker volume ls |
List volumes |
docker network ls |
List networks |
docker system prune |
Remove unused data |
docker compose up -d |
Start all services in background |
docker compose down |
Stop and remove containers |
docker compose logs -f |
Follow logs for all services |
Images
Pull, list, remove
docker pull node:20-alpine # Download a specific tag
docker pull node:20-alpine --quiet # Suppress output
docker images # List all images
docker images -q # Image IDs only
docker image ls --filter dangling=true # Dangling (untagged) images
docker rmi node:20-alpine # Remove image
docker image prune # Remove all dangling images
docker image prune -a # Remove all unused images (careful!)
Build
docker build -t myapp . # Build with tag from current dir
docker build -t myapp:1.0 . # Tag with version
docker build -t myapp -f Dockerfile.prod . # Use specific Dockerfile
docker build --no-cache -t myapp . # Force rebuild all layers
docker build --build-arg NODE_ENV=prod . # Pass build argument
Tag and push
docker tag myapp myregistry.io/myapp:1.0 # Tag for a registry
docker push myregistry.io/myapp:1.0 # Push to registry
docker login myregistry.io # Authenticate first
Save and load
docker save myapp | gzip > myapp.tar.gz # Export image to file
docker load < myapp.tar.gz # Import from file
Containers
Run
docker run nginx # Run (foreground, pulls if needed)
docker run -d nginx # Run detached (background)
docker run -it ubuntu bash # Interactive with pseudo-TTY
docker run --rm nginx # Auto-remove when stopped
docker run -p 8080:80 nginx # Map host:container port
docker run -p 127.0.0.1:8080:80 nginx # Bind to localhost only
docker run -e NODE_ENV=production node # Set environment variable
docker run --env-file .env node # Load vars from file
docker run -v /host/path:/container/path nginx # Bind mount
docker run -v myvolume:/data postgres # Named volume
docker run --name myapp nginx # Assign a name
docker run --network mynet nginx # Connect to network
docker run --restart=unless-stopped nginx # Restart policy
Inspect and debug
docker ps # Running containers
docker ps -a # All containers
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
docker logs myapp # All output
docker logs -f myapp # Follow (live)
docker logs --tail 100 myapp # Last 100 lines
docker logs --since 5m myapp # Last 5 minutes
docker inspect myapp # Full JSON metadata
docker inspect -f '{{.NetworkSettings.IPAddress}}' myapp # Extract field
docker exec -it myapp bash # Shell inside running container
docker exec myapp cat /etc/hosts # Run one command
docker exec -u root myapp bash # Run as specific user
docker top myapp # Processes inside container
docker stats # Live CPU/memory usage (all containers)
docker stats myapp # Stats for one container
Lifecycle
docker stop myapp # Graceful stop (SIGTERM then SIGKILL)
docker kill myapp # Immediate stop (SIGKILL)
docker start myapp # Start a stopped container
docker restart myapp # Stop then start
docker pause myapp # Freeze (suspend processes)
docker unpause myapp # Resume
docker rm myapp # Remove stopped container
docker rm -f myapp # Force remove running container
docker rm $(docker ps -aq) # Remove all stopped containers
docker rename myapp newname # Rename container
docker commit myapp myimage # Create image from container (avoid in prod)
Copy files
docker cp file.txt myapp:/app/file.txt # Host → container
docker cp myapp:/app/logs/out.log ./ # Container → host
Volumes
docker volume create mydata # Create named volume
docker volume ls # List volumes
docker volume inspect mydata # Details (mount point, etc.)
docker volume rm mydata # Remove volume
docker volume prune # Remove unused volumes
# Run with volume
docker run -v mydata:/data postgres
docker run -v $(pwd):/app node:20 # Bind-mount current dir (dev)
Networks
docker network create mynet # Bridge network (default)
docker network create --driver host mynet # Host network
docker network ls # List networks
docker network inspect mynet # Details
docker network rm mynet # Remove network
docker network connect mynet myapp # Connect running container
docker network disconnect mynet myapp # Disconnect
# Containers on same network reach each other by name
docker run --network mynet --name db postgres
docker run --network mynet myapp # myapp can reach "db:5432"
Dockerfile reference
A minimal but production-ready Dockerfile for a Node.js app:
# syntax=docker/dockerfile:1
FROM node:20-alpine AS base
WORKDIR /app
# Dependencies layer (cached unless package.json changes)
FROM base AS deps
COPY package*.json ./
RUN npm ci --omit=dev
# Build layer
FROM base AS build
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production layer (smallest final image)
FROM base AS prod
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
EXPOSE 3000
USER node
CMD ["node", "dist/index.js"]
Key Dockerfile instructions:
| Instruction | Purpose |
|---|---|
FROM image:tag |
Base image |
WORKDIR /path |
Set working directory |
COPY src dest |
Copy files from build context |
ADD src dest |
Copy + auto-extract archives |
RUN command |
Execute during build (creates layer) |
ENV KEY=value |
Set environment variable |
ARG KEY=default |
Build-time variable |
EXPOSE port |
Document port (informational only) |
USER name |
Switch user (don't run as root) |
CMD ["exe", "arg"] |
Default command (overridable) |
ENTRYPOINT ["exe"] |
Fixed executable |
VOLUME /path |
Declare mount point |
HEALTHCHECK CMD curl -f http://localhost/ |
Container health check |
.dockerignore
node_modules
.git
.env
*.log
dist
coverage
Docker Compose
docker-compose.yml for a typical web app:
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 5s
timeout: 5s
retries: 5
volumes:
pgdata:
Compose commands
docker compose up # Start (foreground)
docker compose up -d # Start detached
docker compose up --build # Rebuild images before starting
docker compose up --force-recreate # Recreate containers even if unchanged
docker compose down # Stop and remove containers + networks
docker compose down -v # Also remove named volumes
docker compose ps # Status of services
docker compose logs # All service logs
docker compose logs -f app # Follow one service
docker compose exec app bash # Shell into running service
docker compose build # Build images
docker compose pull # Pull latest images
docker compose restart app # Restart one service
docker compose stop app # Stop one service
docker compose run --rm app sh # One-off command (new container)
# Scale (use only for stateless services)
docker compose up -d --scale app=3
Environment files
# .env (auto-loaded by Compose)
POSTGRES_PASSWORD=secret
TAG=1.0.0
# Reference in docker-compose.yml
image: myapp:${TAG}
Cleanup
Docker accumulates disk usage quickly. Safe cleanup commands:
docker system df # Show disk usage breakdown
docker system prune # Remove stopped containers + dangling images + unused networks
docker system prune -a # Also remove images not used by any container
docker system prune -a --volumes # Also remove unused volumes (DATA LOSS possible)
docker container prune # Remove stopped containers only
docker image prune # Remove dangling images only
docker image prune -a # Remove all unused images
docker volume prune # Remove unused volumes
docker network prune # Remove unused networks
6 common mistakes
1. Running as root
Default user in many images is root. Always add USER node (or similar non-root user) in production Dockerfiles. An attacker who escapes the container has root on the host.
2. Copying .env or secrets into the image
Every COPY . . that runs before adding .dockerignore may bake secrets into image layers. Add .env to .dockerignore and pass secrets at runtime via --env-file or your secret manager.
3. Not pinning image tagsFROM node:latest silently breaks when Node releases a new major version. Always pin: FROM node:20.18-alpine3.20. Use Dependabot or Renovate to get update PRs.
4. One giant RUN layer vs. many small ones
The Docker layer cache works per-instruction. Put things that change often (application code) in later layers. Put things that change rarely (OS packages, dependencies) in early layers. This dramatically speeds up rebuilds.
5. Storing data inside the container
Containers are ephemeral. Any file written inside a container without a volume mount is lost on docker rm. Always mount volumes for databases, uploads, and any persistent data.
6. Exposing ports without binding to localhost-p 5432:5432 binds on 0.0.0.0 — your database is now reachable from the internet if your firewall allows it. Use -p 127.0.0.1:5432:5432 for services that only need to be accessible locally.
6 frequently asked questions
What is the difference between CMD and ENTRYPOINT?ENTRYPOINT sets the executable; CMD sets the default arguments. docker run myimage custom-arg replaces CMD but not ENTRYPOINT. Use ENTRYPOINT ["node"] + CMD ["server.js"] so you can override the script without overriding the runtime.
What is the difference between COPY and ADD?
Prefer COPY — it only copies files. ADD additionally auto-extracts .tar archives and can fetch URLs, which makes builds less predictable. Use ADD only when you explicitly need the extract behaviour.
How do I see what's inside an image without running it?docker run --rm -it myimage sh starts a throwaway shell. To inspect the layers: docker history myimage. To export the entire filesystem: docker export $(docker create myimage) | tar -t | head -50.
What is the difference between a volume and a bind mount?
A volume is managed by Docker (/var/lib/docker/volumes/) — portable, easy to back up, works on all OSes. A bind mount maps a specific host path into the container — useful in development where you want hot-reload from your local files. Use volumes in production.
How do I pass secrets to a container without baking them into the image?
For single containers: docker run --env-file .env myimage. For Compose: use the secrets: block (Docker Swarm) or reference a .env file that stays out of version control. For Kubernetes: use Secrets objects mounted as files.
How do I make my Docker build faster?
Order Dockerfile instructions from least-to-most-frequently-changed. Copy package.json and run npm ci before COPY . . so the dependency layer is cached unless packages change. Use multi-stage builds to keep the final image small. Use --cache-from in CI to reuse layers from previous builds.