Images & Containers
.mdVerified 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 imageRunning 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 usageLogs 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 shellStopping 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 containersMulti-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/htmldocker 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 itEach 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 builderdocker 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 imageA 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 limitsSetting --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 stagingA 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 templatedocker 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 specificallysystem 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.