Docker CLI Mastery: Essential Commands and Workflows (2026)

The Docker CLI is your primary interface for every container operation. Beyond the basics of run, stop, and rm, the CLI has powerful filtering, formatting, and scripting capabilities that separate slow manual workflows from fast automated ones. This phase covers the full command toolkit: output formatting with Go templates, filtering containers and images, tagging and pushing images to registries, build flags, Docker contexts for remote daemons, and scripting patterns that compose CLI commands into useful tools.

Formatting Output

# docker ps, docker images, docker inspect all support --format with Go templates.
# Use this to extract exactly what you need — no grep, no awk.

# Default tabular output
docker ps

# Custom columns
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# NAMES        STATUS         PORTS
# webserver    Up 3 hours     0.0.0.0:8080->80/tcp
# db           Up 3 hours     5432/tcp

# JSON output (pipe to jq for further processing)
docker ps --format '{{json .}}'
docker ps --format '{{json .}}' | jq '.Names, .Status'

# Single value — great for scripting
docker ps --format '{{.Names}}'    # Just names, one per line
docker ps --format '{{.ID}}'       # Just IDs

# Available fields for docker ps:
# .ID .Names .Image .Command .CreatedAt .RunningFor .Ports .Status .Size

# Available fields for docker images:
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}"

# docker inspect with --format (Go template on full JSON)
docker inspect mycontainer --format '{{.State.Status}}'
docker inspect mycontainer --format '{{.NetworkSettings.Networks.bridge.IPAddress}}'
docker inspect mycontainer --format '{{range .Mounts}}{{.Source}} → {{.Destination}}{{"\n"}}{{end}}'

Filtering Containers and Images

# --filter / -f accepts key=value pairs

# Filter containers by status
docker ps -a --filter "status=exited"
docker ps --filter "status=running"

# Filter by name (substring match)
docker ps --filter "name=web"

# Filter by ancestor image
docker ps --filter "ancestor=nginx:latest"

# Filter by label
docker ps --filter "label=env=production"
docker ps --filter "label=app"    # Just check label exists

# Filter by exit code (useful for debugging failed containers)
docker ps -a --filter "exited=1"   # Containers that exited with error

# Filter images — dangling (untagged) images
docker images --filter "dangling=true"

# Images created before/since another image
docker images --filter "before=nginx:latest"
docker images --filter "since=node:18"

# Images by label
docker images --filter "label=maintainer=team@example.com"

# Combine format + filter for powerful one-liners
docker ps -a --filter "status=exited" --format "{{.Names}}: {{.Status}}"

Build Flags

# docker build — the flags that matter most

# Basic build (. is the build context)
docker build -t myapp:latest .

# Specify a Dockerfile (default: ./Dockerfile)
docker build -f docker/Dockerfile.prod -t myapp:prod .

# Build arguments (available during build, not at runtime)
docker build --build-arg NODE_VERSION=20 --build-arg APP_ENV=production -t myapp .

# No cache — force a full rebuild
docker build --no-cache -t myapp .

# Target a specific stage in a multi-stage build
docker build --target builder -t myapp:builder .

# Build for a different platform (cross-compile)
docker build --platform linux/amd64 -t myapp:amd64 .
docker build --platform linux/arm64 -t myapp:arm64 .

# Progress output styles
docker build --progress=plain .    # Full output, no TTY collapsing (great for CI)
docker build --progress=tty .      # Compact with spinner (default in terminal)
docker build --progress=quiet .    # Suppress most output

# BuildKit (default since Docker 23) — enable advanced features
# DOCKER_BUILDKIT=1 docker build ...  (pre-23 only)

# Secret mount during build (never ends up in an image layer)
# --secret id=mysecret,src=./secret.txt
# In Dockerfile: RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret

# SSH forwarding during build (for private git repos)
docker build --ssh default -t myapp .

Tagging and Pushing Images

# Tags are aliases — they point to the same image ID.
# An image can have many tags: latest, v1.2.3, v1.2, v1, stable

# Tag an existing image
docker tag myapp:latest myapp:v1.2.3
docker tag myapp:latest myapp:v1.2
docker tag myapp:latest myapp:v1

# Tag for a registry
docker tag myapp:latest ghcr.io/myorg/myapp:latest
docker tag myapp:latest 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:v1.2.3

# Push to a registry (must be tagged with registry prefix first)
docker push ghcr.io/myorg/myapp:latest
docker push ghcr.io/myorg/myapp:v1.2.3

# Push all tags at once
docker push --all-tags ghcr.io/myorg/myapp

# Typical CI tagging strategy:
# - :latest       — most recent main branch build
# - :v1.2.3       — semantic version
# - :main-abc1234 — branch + short SHA (for traceability)
# - :pr-456       — pull request preview
GIT_SHA=$(git rev-parse --short HEAD)
BRANCH=$(git rev-parse --abbrev-ref HEAD)
docker tag myapp:latest ghcr.io/myorg/myapp:${BRANCH}-${GIT_SHA}
docker push ghcr.io/myorg/myapp:${BRANCH}-${GIT_SHA}

Working with Registries

# Login / logout
docker login                              # Docker Hub (prompts for creds)
docker login ghcr.io -u USERNAME --password-stdin  # GHCR (pipe token via stdin)
docker login 123456789.dkr.ecr.us-east-1.amazonaws.com  # AWS ECR

# Never pass passwords as --password on CLI — they appear in shell history.
# Use --password-stdin and pipe from env var or secret manager:
echo "$GITHUB_TOKEN" | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin

# AWS ECR login (get temporary credentials, valid 12h)
aws ecr get-login-password --region us-east-1 \
  | docker login --username AWS --password-stdin \
    123456789.dkr.ecr.us-east-1.amazonaws.com

# Pull from a private registry (must be logged in)
docker pull ghcr.io/myorg/private-image:latest

# Search Docker Hub
docker search nginx --filter is-official=true
docker search node --limit 5

# Inspect a remote image without pulling (needs experimental features or skopeo)
# Using skopeo (recommended):
# skopeo inspect docker://nginx:latest | jq '.Layers | length'

# Credential helpers — store registry creds securely (not in ~/.docker/config.json plaintext)
# macOS: docker-credential-osxkeychain (default)
# Linux: docker-credential-secretservice or docker-credential-pass
# Set in ~/.docker/config.json:  "credsStore": "osxkeychain"

Docker Contexts

# Docker contexts let you switch between different Docker daemons
# (local, remote server, Docker Desktop, Colima, Rancher, etc.)
# without changing environment variables.

# List contexts
docker context ls
# NAME        DESCRIPTION         DOCKER ENDPOINT
# default *   Current DOCKER_HOST unix:///var/run/docker.sock
# remote      Production server   ssh://deploy@prod.example.com

# Create a context for a remote server (uses SSH)
docker context create remote \
  --description "Production server" \
  --docker "host=ssh://deploy@prod.example.com"

# Switch context
docker context use remote

# Run a single command in a specific context without switching
docker --context remote ps

# Create a context for a TCP daemon (less secure — use SSH or TLS in production)
docker context create myserver \
  --docker "host=tcp://192.168.1.100:2376,ca=/path/to/ca.pem,cert=/path/to/cert.pem,key=/path/to/key.pem"

# Remove a context
docker context rm remote

# Export/import contexts (for sharing team configs)
docker context export remote remote-context.dockercontext
docker context import remote remote-context.dockercontext

Scripting Patterns

#!/bin/bash
# Useful scripting patterns with the Docker CLI

# Stop all running containers
docker stop $(docker ps -q)

# Remove all stopped containers
docker rm $(docker ps -aq --filter status=exited)

# Remove all images with a specific name (all tags)
docker images --format "{{.ID}}" myapp | xargs docker rmi

# Wait for a container to be healthy before proceeding
wait_healthy() {
  local name=$1
  local max=30
  local count=0
  until [ "$(docker inspect --format='{{.State.Health.Status}}' $name)" = "healthy" ]; do
    sleep 2
    count=$((count+1))
    [ $count -ge $max ] && echo "Timeout waiting for $name" && exit 1
  done
  echo "$name is healthy"
}

# Get the IP of a running container
container_ip() {
  docker inspect "$1" --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
}

# Run a one-shot command in a fresh container and capture output
result=$(docker run --rm alpine sh -c "echo hello")

# Build, tag, push in one go (CI script pattern)
build_and_push() {
  local image="ghcr.io/myorg/myapp"
  local sha=$(git rev-parse --short HEAD)
  docker build -t "$image:$sha" -t "$image:latest" .
  docker push "$image:$sha"
  docker push "$image:latest"
  echo "Pushed $image:$sha"
}

Productivity Shortcuts

# Aliases that save keystrokes (add to ~/.bashrc or ~/.zshrc)
alias d='docker'
alias dps='docker ps'
alias dpsa='docker ps -a'
alias di='docker images'
alias drm='docker rm $(docker ps -aq --filter status=exited) 2>/dev/null || true'
alias dprune='docker system prune -f'

# Partial ID — Docker accepts the shortest unambiguous prefix
docker stop a3b   # Works if only one container starts with a3b

# Use labels to organize containers
docker run -d \
  --label app=myapp \
  --label env=staging \
  --label version=1.2.3 \
  myapp:1.2.3

# Then filter by label
docker ps --filter "label=app=myapp" --filter "label=env=staging"

# docker events — real-time event stream (useful for debugging)
docker events
docker events --filter type=container --filter event=die  # Alert on container crashes

# docker diff — see what files changed in a container's writable layer
docker diff mycontainer
# C = changed, A = added, D = deleted

# docker commit — snapshot a container as an image (avoid in production — use Dockerfile)
docker commit mycontainer myapp:snapshot-$(date +%Y%m%d)
Next: Phase 3 — Dockerfile Best Practices covers layer caching, instruction ordering, multi-stage builds, .dockerignore and the patterns that keep images small and builds fast.