Verified14 commandsAI-assisted

Images & Containers

.md

Verified against Docker 29.1.5, flags verified via `docker <cmd> --help`, 2026-08-21 · official docs

Building and tagging images, and running/inspecting/cleaning up containers — the day-to-day loop of local container development.

Building images#

docker build -t myapp:latest .
docker build -t myapp:v1.2.0 -f Dockerfile.prod .
docker build --no-cache -t myapp:latest .           # ignore layer cache, force a full rebuild
docker build --build-arg NODE_ENV=production -t myapp:latest .

docker build is now backed by BuildKit (docker buildx build under the hood) — --no-cache invalidates every layer, while a targeted fix is usually cheaper: touching the file that changed and letting the layer cache do its job from that point forward.

Tagging and pushing images#

docker tag myapp:latest myregistry.io/myteam/myapp:v1.2.0
docker push myregistry.io/myteam/myapp:v1.2.0
docker pull myregistry.io/myteam/myapp:v1.2.0
docker images                                        # list local images
docker rmi myapp:latest                              # remove a local image

Running containers#

docker run -d --name my-app -p 8080:80 myapp:latest
docker run -d --name my-app -e LOG_LEVEL=info -v ./data:/app/data myapp:latest
docker run --rm -it myapp:latest /bin/bash            # interactive, auto-removed on exit
docker run -d --restart unless-stopped myapp:latest

-p 8080:80 maps host:container — the host port comes first. Getting this backwards is a common cause of "it works when I exec in but not from the browser."

Listing and inspecting containers#

docker ps                                            # running containers only
docker ps -a                                         # include stopped containers
docker ps --filter status=exited
docker inspect my-app                                # full container config as JSON
docker stats                                          # live CPU/memory/network usage

Logs and exec#

docker logs my-app
docker logs -f my-app                                # follow/stream
docker logs --tail 100 --since 1h my-app
docker exec -it my-app /bin/bash                      # interactive shell in a running container
docker exec my-app env                                 # one-off command, no shell

Stopping and removing containers#

docker stop my-app
docker rm my-app
docker rm -f my-app                                   # stop and remove in one step
docker container prune                                 # remove all stopped containers

Multi-stage builds#

FROM node:20 AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
docker build -t myapp:latest .                        # builds every stage, keeps only the final one
docker build --target build -t myapp:build-debug .    # stop at an intermediate stage, e.g. to debug it

Each FROM starts a new stage; COPY --from=<stage> pulls specific artifacts out of an earlier stage into the current one. Only the final stage ends up in the built image, so build tooling (compilers, npm/node_modules, test dependencies) never ships to production — this is the standard fix for "why is my image 1.2GB for a 20MB binary."

Building for multiple platforms with buildx#

docker buildx ls                                       # list builder instances
docker buildx create --name multiarch --use --bootstrap  # create + switch to a new builder, boot it
docker buildx build --platform linux/amd64,linux/arm64 -t myregistry.io/myapp:v1 --push .
docker buildx use default                               # switch back to the default builder

docker build on a single-arch host only ever produces an image for that host's architecture. buildx uses BuildKit (via QEMU emulation or a remote builder) to produce a multi-platform image in one invocation — --push is required for a multi-platform build's output, since a multi-arch manifest can't be loaded into the local docker images store the way --load loads a single-platform one.

Health checks#

docker run -d --name my-app \
  --health-cmd="curl -f http://localhost/health || exit 1" \
  --health-interval=30s \
  --health-timeout=5s \
  --health-retries=3 \
  --health-start-period=10s \
  myapp:latest
docker inspect --format='{{.State.Health.Status}}' my-app   # healthy / unhealthy / starting
docker run -d --no-healthcheck myapp:latest              # ignore a HEALTHCHECK baked into the image

A container image can bake in its own HEALTHCHECK instruction in the Dockerfile — the --health-* flags on docker run override it per-container without rebuilding. --health-start-period matters for slow-starting apps: failed checks during that window don't count toward --health-retries, so a container isn't marked unhealthy while it's still booting.

Setting resource limits#

docker run -d --memory=512m --memory-swap=512m myapp:latest   # hard cap RAM, disable swap (swap = memory limit)
docker run -d --memory=512m --memory-reservation=256m myapp:latest  # soft limit, enforced under host pressure
docker run -d --cpus=1.5 myapp:latest                    # cap at 1.5 CPU cores
docker run -d --cpuset-cpus="0,1" myapp:latest            # pin to specific CPU cores
docker stats my-app                                        # confirm actual usage against the limits

Setting --memory-swap equal to --memory disables swap for the container (the flag is swap on top of the memory limit, not swap in isolation) — the common pattern for stopping a leaking container from silently degrading into swap thrash instead of getting OOM-killed where you'd notice it.

Working with docker context (managing multiple daemons)#

docker context ls                                        # list contexts (local + remote daemons)
docker context create staging --docker "host=ssh://user@staging-host"
docker context use staging                                # switch the CLI's target daemon
docker context show                                        # print the currently active context
docker --context staging ps                                 # one-off command against a specific context without switching
docker context rm staging

A context bundles a daemon endpoint (local socket, SSH, or TCP+TLS) under a name — switching context is how you point the same docker CLI at a different host (dev laptop vs. a remote build box) without juggling DOCKER_HOST env vars by hand.

Inspecting image layers and history#

docker history myapp:latest                                # each layer, its size, and the command that created it
docker history --no-trunc myapp:latest                     # full (untruncated) command per layer
docker inspect myapp:latest                                 # full image metadata as JSON (env, entrypoint, layers, config)
docker inspect --format='{{.Config.Env}}' myapp:latest      # pull one field out with a Go template

docker history is usually the fastest way to find which instruction in a Dockerfile bloated an image — layers are listed newest-first with their individual size, so a surprisingly large layer points straight at the offending RUN/COPY line.

Cleaning up unused resources#

docker system prune                                          # remove stopped containers, dangling images, unused networks, build cache
docker system prune -a                                        # also remove ALL unused images, not just dangling ones
docker system prune --volumes                                  # also remove anonymous (unnamed) volumes
docker image prune -a --filter "until=24h"                    # only images untouched in the last 24h
docker buildx prune                                             # clear the BuildKit build cache specifically

system prune never touches named volumes or anything attached to a running container by default — -a is still safe for that reason, but always confirms what it's about to remove interactively unless you pass -f. buildx prune is separate because BuildKit's cache lives outside the regular image/container/volume/network bookkeeping system prune covers.