Tools: 10 Docker commands that actually matter in 2026 🐳

Tools: 10 Docker commands that actually matter in 2026 🐳

🐳 You're probably using Docker wrong

🎯 1) docker system prune β€” Clean up ALL the junk

🧠 What's actually getting deleted?

⚑ 2) docker exec -it β€” Jump inside a running container

πŸ• Real-world example:

πŸ” 3) docker logs β€” See what's actually happening

🧠 Combine for debugging:

πŸ—οΈ 4) Multi-stage builds β€” Shrinking your images by 90%

πŸ€” What's happening here?

πŸ”— 5) docker network β€” Make containers talk to each other

🧠 Why this matters

πŸ›‘οΈ 6) .dockerignore β€” Stop sending your entire disk to Docker

🧠 Why this matters

πŸ“¦ 7) docker compose watch β€” Live reload in development

πŸ€” What's happening?

🧬 8) docker inspect β€” The x-ray machine

πŸ• Useful inspection combos:

πŸ”„ 9) docker compose profiles β€” Conditional services

πŸ§ͺ 10) docker buildx β€” Build for any platform

🧠 What's happening?

🧠 TL;DR Cheat Sheet

🏁 Docker isn't magic. It's just Linux. Not trying to be mean. But every week I see developers run docker run with 12 flags they copied from Stack Overflow without understanding a single one. Docker isn't hard. It's just badly taught. Most tutorials explain it like a college textbook. That's the problem. Let me explain it like a human. Your Docker is eating 40GB of disk space and you don't know why. Here's why: every build, every container, every image you ever pulled β€” they're all still there. Docker hoards everything. Pro tip β€” be more surgical: Use it when: your disk is full, you've been building for weeks, you want a fresh start without reinstalling Docker. When something breaks IN a container, you need to get inside it. Your app throws "connection refused" to the database. But the database container IS running. What's happening? If ping works from inside the container but your app can't connect, it's a config problem, not a networking problem. You just narrowed it down in 30 seconds. Without both, you get a broken shell. Always use both together. Your container crashed. No error message on your screen. Where did it go? Use it when: container exits unexpectedly, debugging API responses, checking startup errors, monitoring request flow. This is the single biggest optimization most Dockerfiles miss. Stage 1 (builder): Full Node.js image. Has compilers, build tools, dev dependencies. Builds your app. Stage 2 (runtime): Slim image. Only copies the BUILD OUTPUT from stage 1. No compilers. No source code. No dev dependencies. The final image only contains what it needs to RUN, not what it needed to BUILD. You can have as many stages as you want: Containers are isolated by default. They can't see each other unless you connect them. When you use docker-compose, it creates a network automatically. But when running docker run manually, containers are on the default bridge network where DNS resolution by name doesn't work. Use it when: running multiple containers that need to communicate, debugging network issues, setting up microservices locally. When you run docker build ., Docker sends your ENTIRE directory as context to the daemon. Including node_modules. Including .git. Including that 4GB video file. Without .dockerignore: That's not just faster builds. Some files like .env contain secrets. You don't want those baked into your image layers where anyone with image access can read them. This one is new and it's amazing for development. sync: When files in ./src change, they're copied INTO the running container. No rebuild needed. Your app hot-reloads. rebuild: When package.json changes, the entire container is rebuilt (because dependencies changed). Before this, you had to choose between: docker compose watch gives you the best of both. When you need to know EVERYTHING about a container, image, network, or volume: Use it when: debugging networking, checking environment variables, verifying volume mounts, finding container configuration. You don't always need Redis, Elasticsearch, and Mailhog. Start only what you need. Without --profile, only non-profiled services start (just api in this case). Use it when: different team members need different stacks, CI needs minimal services, you want fast local startup for daily development. Need to build an image for ARM (Raspberry Pi, M1/M2 Mac) but you're on x86? buildx uses QEMU emulation to build for architectures your machine doesn't have. The --push flag sends the multi-platform manifest to your registry. When someone pulls my-app:latest, Docker automatically picks the right architecture: Build for Raspberry Pi from your laptop: --load loads it into your local Docker (instead of pushing to a registry). Note: can only load one platform at a time locally. Every container is just a Linux process with namespace isolation and cgroup limits. That's it. There's no virtual machine. There's no magic. Once you understand that, Docker stops being scary. It becomes a tool you control instead of a tool that controls you. Stop memorizing commands. Start understanding what they do. Tags: #docker #devops #webdev #programming Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

# Nuclear option β€” removes EVERYTHING unused -weight: 500;">docker system prune -a # You'll see: # Deleted Containers: 23 # Deleted Images: 47 # Deleted build cache: 12.4GB # Total reclaimed space: 31.2GB # Nuclear option β€” removes EVERYTHING unused -weight: 500;">docker system prune -a # You'll see: # Deleted Containers: 23 # Deleted Images: 47 # Deleted build cache: 12.4GB # Total reclaimed space: 31.2GB # Nuclear option β€” removes EVERYTHING unused -weight: 500;">docker system prune -a # You'll see: # Deleted Containers: 23 # Deleted Images: 47 # Deleted build cache: 12.4GB # Total reclaimed space: 31.2GB # Only -weight: 500;">remove stopped containers -weight: 500;">docker container prune # Only -weight: 500;">remove unused images -weight: 500;">docker image prune -a # Only -weight: 500;">remove build cache -weight: 500;">docker builder prune # Only -weight: 500;">remove unused volumes (⚠️ careful β€” this deletes data) -weight: 500;">docker volume prune # Only -weight: 500;">remove stopped containers -weight: 500;">docker container prune # Only -weight: 500;">remove unused images -weight: 500;">docker image prune -a # Only -weight: 500;">remove build cache -weight: 500;">docker builder prune # Only -weight: 500;">remove unused volumes (⚠️ careful β€” this deletes data) -weight: 500;">docker volume prune # Only -weight: 500;">remove stopped containers -weight: 500;">docker container prune # Only -weight: 500;">remove unused images -weight: 500;">docker image prune -a # Only -weight: 500;">remove build cache -weight: 500;">docker builder prune # Only -weight: 500;">remove unused volumes (⚠️ careful β€” this deletes data) -weight: 500;">docker volume prune # Get a shell inside a running container -weight: 500;">docker exec -it my-app /bin/bash # If bash isn't available (Alpine images), use sh -weight: 500;">docker exec -it my-app /bin/sh # Get a shell inside a running container -weight: 500;">docker exec -it my-app /bin/bash # If bash isn't available (Alpine images), use sh -weight: 500;">docker exec -it my-app /bin/sh # Get a shell inside a running container -weight: 500;">docker exec -it my-app /bin/bash # If bash isn't available (Alpine images), use sh -weight: 500;">docker exec -it my-app /bin/sh # 1. Jump into the app container -weight: 500;">docker exec -it my-app /bin/sh # 2. Test the connection from INSIDE the container ping db-host # or -weight: 500;">curl http://db-host:5432 # or nc -zv db-host 5432 # 1. Jump into the app container -weight: 500;">docker exec -it my-app /bin/sh # 2. Test the connection from INSIDE the container ping db-host # or -weight: 500;">curl http://db-host:5432 # or nc -zv db-host 5432 # 1. Jump into the app container -weight: 500;">docker exec -it my-app /bin/sh # 2. Test the connection from INSIDE the container ping db-host # or -weight: 500;">curl http://db-host:5432 # or nc -zv db-host 5432 # Show all logs -weight: 500;">docker logs my-app # Follow logs in real-time (like tail -f) -weight: 500;">docker logs -f my-app # Last 100 lines -weight: 500;">docker logs --tail 100 my-app # Logs from the last 5 minutes -weight: 500;">docker logs --since 5m my-app # Logs with timestamps -weight: 500;">docker logs -t my-app # Show all logs -weight: 500;">docker logs my-app # Follow logs in real-time (like tail -f) -weight: 500;">docker logs -f my-app # Last 100 lines -weight: 500;">docker logs --tail 100 my-app # Logs from the last 5 minutes -weight: 500;">docker logs --since 5m my-app # Logs with timestamps -weight: 500;">docker logs -t my-app # Show all logs -weight: 500;">docker logs my-app # Follow logs in real-time (like tail -f) -weight: 500;">docker logs -f my-app # Last 100 lines -weight: 500;">docker logs --tail 100 my-app # Logs from the last 5 minutes -weight: 500;">docker logs --since 5m my-app # Logs with timestamps -weight: 500;">docker logs -t my-app # Watch real-time logs with timestamps, last 50 lines -weight: 500;">docker logs -f -t --tail 50 my-app # Search logs for errors -weight: 500;">docker logs my-app 2>&1 | grep -i error # Save logs to a file -weight: 500;">docker logs my-app > app.log 2>&1 # Watch real-time logs with timestamps, last 50 lines -weight: 500;">docker logs -f -t --tail 50 my-app # Search logs for errors -weight: 500;">docker logs my-app 2>&1 | grep -i error # Save logs to a file -weight: 500;">docker logs my-app > app.log 2>&1 # Watch real-time logs with timestamps, last 50 lines -weight: 500;">docker logs -f -t --tail 50 my-app # Search logs for errors -weight: 500;">docker logs my-app 2>&1 | grep -i error # Save logs to a file -weight: 500;">docker logs my-app > app.log 2>&1 # ❌ Bad β€” final image includes ALL build tools FROM node:20 WORKDIR /app COPY . . RUN -weight: 500;">npm -weight: 500;">install RUN -weight: 500;">npm run build CMD ["node", "dist/index.js"] # Image size: ~1.2GB # ❌ Bad β€” final image includes ALL build tools FROM node:20 WORKDIR /app COPY . . RUN -weight: 500;">npm -weight: 500;">install RUN -weight: 500;">npm run build CMD ["node", "dist/index.js"] # Image size: ~1.2GB # ❌ Bad β€” final image includes ALL build tools FROM node:20 WORKDIR /app COPY . . RUN -weight: 500;">npm -weight: 500;">install RUN -weight: 500;">npm run build CMD ["node", "dist/index.js"] # Image size: ~1.2GB # βœ… Good β€” multi-stage build FROM node:20 AS builder WORKDIR /app COPY package*.json ./ RUN -weight: 500;">npm ci COPY . . RUN -weight: 500;">npm run build FROM node:20-slim WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules CMD ["node", "dist/index.js"] # Image size: ~180MB # βœ… Good β€” multi-stage build FROM node:20 AS builder WORKDIR /app COPY package*.json ./ RUN -weight: 500;">npm ci COPY . . RUN -weight: 500;">npm run build FROM node:20-slim WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules CMD ["node", "dist/index.js"] # Image size: ~180MB # βœ… Good β€” multi-stage build FROM node:20 AS builder WORKDIR /app COPY package*.json ./ RUN -weight: 500;">npm ci COPY . . RUN -weight: 500;">npm run build FROM node:20-slim WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules CMD ["node", "dist/index.js"] # Image size: ~180MB FROM node:20 AS deps COPY package*.json ./ RUN -weight: 500;">npm ci FROM node:20 AS builder COPY --from=deps /app/node_modules ./node_modules COPY . . RUN -weight: 500;">npm run build FROM node:20-slim AS runtime COPY --from=builder /app/dist ./dist COPY --from=deps /app/node_modules ./node_modules CMD ["node", "dist/index.js"] FROM node:20 AS deps COPY package*.json ./ RUN -weight: 500;">npm ci FROM node:20 AS builder COPY --from=deps /app/node_modules ./node_modules COPY . . RUN -weight: 500;">npm run build FROM node:20-slim AS runtime COPY --from=builder /app/dist ./dist COPY --from=deps /app/node_modules ./node_modules CMD ["node", "dist/index.js"] FROM node:20 AS deps COPY package*.json ./ RUN -weight: 500;">npm ci FROM node:20 AS builder COPY --from=deps /app/node_modules ./node_modules COPY . . RUN -weight: 500;">npm run build FROM node:20-slim AS runtime COPY --from=builder /app/dist ./dist COPY --from=deps /app/node_modules ./node_modules CMD ["node", "dist/index.js"] # Create a network -weight: 500;">docker network create my-network # Run containers on the same network -weight: 500;">docker run -d --name api --network my-network my-api -weight: 500;">docker run -d --name db --network my-network postgres # Now "api" can reach "db" by name: # postgres://db:5432/mydb # Create a network -weight: 500;">docker network create my-network # Run containers on the same network -weight: 500;">docker run -d --name api --network my-network my-api -weight: 500;">docker run -d --name db --network my-network postgres # Now "api" can reach "db" by name: # postgres://db:5432/mydb # Create a network -weight: 500;">docker network create my-network # Run containers on the same network -weight: 500;">docker run -d --name api --network my-network my-api -weight: 500;">docker run -d --name db --network my-network postgres # Now "api" can reach "db" by name: # postgres://db:5432/mydb # ❌ This won't work on default bridge -weight: 500;">docker run my-app -weight: 500;">curl http://db:5432 # βœ… This works -weight: 500;">docker network create app-net -weight: 500;">docker run --network app-net --name db postgres -weight: 500;">docker run --network app-net my-app -weight: 500;">curl http://db:5432 # ❌ This won't work on default bridge -weight: 500;">docker run my-app -weight: 500;">curl http://db:5432 # βœ… This works -weight: 500;">docker network create app-net -weight: 500;">docker run --network app-net --name db postgres -weight: 500;">docker run --network app-net my-app -weight: 500;">curl http://db:5432 # ❌ This won't work on default bridge -weight: 500;">docker run my-app -weight: 500;">curl http://db:5432 # βœ… This works -weight: 500;">docker network create app-net -weight: 500;">docker run --network app-net --name db postgres -weight: 500;">docker run --network app-net my-app -weight: 500;">curl http://db:5432 -weight: 500;">docker network inspect my-network # Shows all connected containers and their IPs -weight: 500;">docker network inspect my-network # Shows all connected containers and their IPs -weight: 500;">docker network inspect my-network # Shows all connected containers and their IPs # .dockerignore node_modules .-weight: 500;">git .gitignore *.md .env .env.local -weight: 500;">docker-compose*.yml Dockerfile .dockerignore coverage .nyc_output .vscode .idea *.log tmp # .dockerignore node_modules .-weight: 500;">git .gitignore *.md .env .env.local -weight: 500;">docker-compose*.yml Dockerfile .dockerignore coverage .nyc_output .vscode .idea *.log tmp # .dockerignore node_modules .-weight: 500;">git .gitignore *.md .env .env.local -weight: 500;">docker-compose*.yml Dockerfile .dockerignore coverage .nyc_output .vscode .idea *.log tmp Sending build context to Docker daemon 2.34GB Sending build context to Docker daemon 2.34GB Sending build context to Docker daemon 2.34GB Sending build context to Docker daemon 12.4MB Sending build context to Docker daemon 12.4MB Sending build context to Docker daemon 12.4MB # Verify what Docker can see -weight: 500;">docker build --no-cache -t test . 2>&1 | head -5 # Check the "Sending build context" line # Verify what Docker can see -weight: 500;">docker build --no-cache -t test . 2>&1 | head -5 # Check the "Sending build context" line # Verify what Docker can see -weight: 500;">docker build --no-cache -t test . 2>&1 | head -5 # Check the "Sending build context" line # -weight: 500;">docker-compose.yml services: web: build: . ports: - "3000:3000" develop: watch: - action: sync path: ./src target: /app/src - action: rebuild path: package.json # -weight: 500;">docker-compose.yml services: web: build: . ports: - "3000:3000" develop: watch: - action: sync path: ./src target: /app/src - action: rebuild path: package.json # -weight: 500;">docker-compose.yml services: web: build: . ports: - "3000:3000" develop: watch: - action: sync path: ./src target: /app/src - action: rebuild path: package.json # Start with watch mode -weight: 500;">docker compose watch # Start with watch mode -weight: 500;">docker compose watch # Start with watch mode -weight: 500;">docker compose watch # Full JSON dump -weight: 500;">docker inspect my-container # Get a specific value -weight: 500;">docker inspect --format='{{.State.Status}}' my-container # running -weight: 500;">docker inspect --format='{{.NetworkSettings.IPAddress}}' my-container # 172.17.0.2 -weight: 500;">docker inspect --format='{{json .Mounts}}' my-container # [{"Type":"bind","Source":"/home/user/data","Destination":"/app/data"}] -weight: 500;">docker inspect --format='{{.Config.Env}}' my-container # [PATH=/usr/local/sbin:... DATABASE_URL=postgres://...] # Full JSON dump -weight: 500;">docker inspect my-container # Get a specific value -weight: 500;">docker inspect --format='{{.State.Status}}' my-container # running -weight: 500;">docker inspect --format='{{.NetworkSettings.IPAddress}}' my-container # 172.17.0.2 -weight: 500;">docker inspect --format='{{json .Mounts}}' my-container # [{"Type":"bind","Source":"/home/user/data","Destination":"/app/data"}] -weight: 500;">docker inspect --format='{{.Config.Env}}' my-container # [PATH=/usr/local/sbin:... DATABASE_URL=postgres://...] # Full JSON dump -weight: 500;">docker inspect my-container # Get a specific value -weight: 500;">docker inspect --format='{{.State.Status}}' my-container # running -weight: 500;">docker inspect --format='{{.NetworkSettings.IPAddress}}' my-container # 172.17.0.2 -weight: 500;">docker inspect --format='{{json .Mounts}}' my-container # [{"Type":"bind","Source":"/home/user/data","Destination":"/app/data"}] -weight: 500;">docker inspect --format='{{.Config.Env}}' my-container # [PATH=/usr/local/sbin:... DATABASE_URL=postgres://...] # What port is exposed? -weight: 500;">docker inspect --format='{{json .NetworkSettings.Ports}}' my-app # What image was this container built from? -weight: 500;">docker inspect --format='{{.Config.Image}}' my-app # When was it created? -weight: 500;">docker inspect --format='{{.Created}}' my-app # What's the -weight: 500;">restart policy? -weight: 500;">docker inspect --format='{{.HostConfig.RestartPolicy.Name}}' my-app # What port is exposed? -weight: 500;">docker inspect --format='{{json .NetworkSettings.Ports}}' my-app # What image was this container built from? -weight: 500;">docker inspect --format='{{.Config.Image}}' my-app # When was it created? -weight: 500;">docker inspect --format='{{.Created}}' my-app # What's the -weight: 500;">restart policy? -weight: 500;">docker inspect --format='{{.HostConfig.RestartPolicy.Name}}' my-app # What port is exposed? -weight: 500;">docker inspect --format='{{json .NetworkSettings.Ports}}' my-app # What image was this container built from? -weight: 500;">docker inspect --format='{{.Config.Image}}' my-app # When was it created? -weight: 500;">docker inspect --format='{{.Created}}' my-app # What's the -weight: 500;">restart policy? -weight: 500;">docker inspect --format='{{.HostConfig.RestartPolicy.Name}}' my-app # -weight: 500;">docker-compose.yml services: api: build: . ports: - "3000:3000" db: image: postgres:16 profiles: ["database"] redis: image: redis:7 profiles: ["cache"] elasticsearch: image: elasticsearch:8 profiles: ["search"] mailhog: image: mailhog/mailhog profiles: ["email"] # -weight: 500;">docker-compose.yml services: api: build: . ports: - "3000:3000" db: image: postgres:16 profiles: ["database"] redis: image: redis:7 profiles: ["cache"] elasticsearch: image: elasticsearch:8 profiles: ["search"] mailhog: image: mailhog/mailhog profiles: ["email"] # -weight: 500;">docker-compose.yml services: api: build: . ports: - "3000:3000" db: image: postgres:16 profiles: ["database"] redis: image: redis:7 profiles: ["cache"] elasticsearch: image: elasticsearch:8 profiles: ["search"] mailhog: image: mailhog/mailhog profiles: ["email"] # Start only api + db -weight: 500;">docker compose --profile database up # Start api + db + redis -weight: 500;">docker compose --profile database --profile cache up # Start EVERYTHING -weight: 500;">docker compose --profile database --profile cache --profile search --profile email up # Start only api + db -weight: 500;">docker compose --profile database up # Start api + db + redis -weight: 500;">docker compose --profile database --profile cache up # Start EVERYTHING -weight: 500;">docker compose --profile database --profile cache --profile search --profile email up # Start only api + db -weight: 500;">docker compose --profile database up # Start api + db + redis -weight: 500;">docker compose --profile database --profile cache up # Start EVERYTHING -weight: 500;">docker compose --profile database --profile cache --profile search --profile email up # Create a multi-platform builder -weight: 500;">docker buildx create --name multiarch --use # Build for multiple platforms at once -weight: 500;">docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t my-app:latest --push . # Create a multi-platform builder -weight: 500;">docker buildx create --name multiarch --use # Build for multiple platforms at once -weight: 500;">docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t my-app:latest --push . # Create a multi-platform builder -weight: 500;">docker buildx create --name multiarch --use # Build for multiple platforms at once -weight: 500;">docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t my-app:latest --push . # On M1 Mac β†’ pulls arm64 -weight: 500;">docker pull my-app:latest # On x86 server β†’ pulls amd64 -weight: 500;">docker pull my-app:latest # On M1 Mac β†’ pulls arm64 -weight: 500;">docker pull my-app:latest # On x86 server β†’ pulls amd64 -weight: 500;">docker pull my-app:latest # On M1 Mac β†’ pulls arm64 -weight: 500;">docker pull my-app:latest # On x86 server β†’ pulls amd64 -weight: 500;">docker pull my-app:latest -weight: 500;">docker buildx build --platform linux/arm/v7 -t my-app:pi --load . -weight: 500;">docker buildx build --platform linux/arm/v7 -t my-app:pi --load . -weight: 500;">docker buildx build --platform linux/arm/v7 -t my-app:pi --load . - Stopped containers - Dangling images (no tag, not used by any container) - Unused networks - Build cache - With -a: ALL unused images (not just dangling ones) - -i = interactive (keeps STDIN open) - -t = pseudo-TTY (gives you a proper terminal) - Bind mounts (fast but breaks on macOS/Windows with file system differences) - Manual rebuilds (slow and annoying)