Getting Started
Basic Commands
docker run creates and starts a container. -d runs in background, -p maps host port to container port. pull downloads images from a registry (Docker Hub by default).
# check version
docker --version
# pull an image
docker pull nginx:latest
# run a container
docker run -d --name mynginx -p 8080:80 nginx
# list running containers
docker ps
# list all containers (including stopped)
docker ps -a
# stop and remove a container
docker stop mynginx
docker rm mynginx
# list images
docker imagesContainer Lifecycle
create + start is what run does under the hood. stop sends SIGTERM for graceful shutdown; kill sends SIGKILL immediately. pause uses cgroup freezer — useful for snapshots without stopping the process.
# create a container without starting it
docker create --name web nginx
# start a created (or stopped) container
docker start web
# stop gracefully (SIGTERM then SIGKILL after 10s)
docker stop web
# force kill immediately
docker kill web
# restart a container
docker restart web
# pause/resume (freezes process with cgroups)
docker pause web
docker unpause webImage Basics
Images are read-only layers. rmi fails if a container still references the image — remove the container first. history shows each instruction that created a layer and its size, useful for optimizing builds.
# list local images
docker images
docker image ls
# remove an image
docker rmi nginx:latest
docker image rm nginx
# remove all dangling (untagged) images
docker image prune
# remove all unused images
docker image prune -a
# show image history (each layer)
docker history nginx:latestSystem Information
docker info shows daemon config: storage driver, runtime, registries, swarm status. system df reveals reclaimable space — dangling images and stopped containers waste disk. The --format flag uses Go templates for structured output.
# docker system-wide info
docker info
docker version
# disk usage breakdown (images, containers, volumes)
docker system df
# detailed disk usage with reclaimable space
docker system df -v
# running containers count
docker ps -q | wc -l
# show docker root directory and storage driver
docker info --format '{{.DockerRootDir}} {{.Driver}}'Cleanup & Prune
prune safely removes stopped/dangling resources. system prune -a also removes images not referenced by any container. Adding --volumes deletes volumes not used by any container — irreversible data loss, use with care in production.
# remove stopped containers
docker container prune
# remove dangling images
docker image prune
# remove unused volumes (CAUTION: data loss)
docker volume prune
# remove unused networks
docker network prune
# remove everything unused at once
docker system prune
# also remove volumes (very aggressive)
docker system prune -a --volumesGetting Help
Docker groups commands into management namespaces (docker image, docker container, docker network, docker volume). Both old (docker rmi) and new (docker image rm) forms work. --help on any subcommand shows flags and usage.
# list all commands
docker --help
docker help
# help for a specific command
docker run --help
docker image --help
# list management commands (grouped)
docker image --help
docker container --help
docker network --help
# show daemon config
docker info
# command reference online
# https://docs.docker.com/reference/Image Management
Pulling Images
Tags are mutable labels — the same tag can point to different images over time. For reproducible deploys, pin by digest (@sha256:...). Pulling by --platform lets you fetch an image for a different architecture, useful for ARM builds on x86 hosts.
# pull latest tag
docker pull nginx
# pull specific tag
docker pull nginx:1.25-alpine
# pull all tags of a repository
docker pull -a alpine
# pull from a different registry
docker pull ghcr.io/user/repo:tag
# pull by digest (immutable, reproducible)
docker pull nginx@sha256:abc123...
# pull multiple platforms (multi-arch)
docker pull --platform linux/arm64 nginxBuilding Images
The final . is the build context — files sent to the daemon. Keep it small with .dockerignore to speed builds. --no-cache forces fresh layers when debugging. BuildKit enables multi-stage caching, SSH mounts, and secret mounting.
# build from Dockerfile in current dir, tag it
docker build -t myapp:1.0 .
# build with a different Dockerfile name
docker build -f Dockerfile.prod -t myapp:prod .
# build with build args
docker build --build-arg VERSION=1.2.3 -t myapp .
# build with no cache
docker build --no-cache -t myapp .
# build for a different platform
docker build --platform linux/arm64 -t myapp:arm .
# build using BuildKit (advanced features)
DOCKER_BUILDKIT=1 docker build -t myapp .Tagging Images
Tags are aliases pointing to the same image ID. A common workflow builds with a version tag then adds latest and major-version tags. Tagging with a registry URL prepares the image for a push to that registry.
# tag a local image
docker tag myapp:1.0 myapp:latest
# tag for pushing to Docker Hub
docker tag myapp:1.0 username/myapp:1.0
# tag for a private registry
docker tag myapp:1.0 registry.local:5000/myapp:1.0
# tag with multiple tags at once
docker tag myapp:1.0 myapp:stable
docker tag myapp:1.0 myapp:1
# tag by digest reference
docker tag myapp@sha256:abc... myapp:pinnedPushing Images
You must tag the image with the registry destination before pushing. push uploads only layers not already present in the registry. For private registries with self-signed certs, configure the daemon in /etc/docker/daemon.json with insecure-registries.
# login to Docker Hub
docker login
# login to a private registry
docker login registry.local:5000 -u user -p pass
# push to Docker Hub
docker push username/myapp:1.0
# push all tags in a repository
docker push -a username/myapp
# push to private registry
docker push registry.local:5000/myapp:1.0
# logout when done
docker logoutInspecting Images
inspect returns detailed JSON: architecture, OS, layers, env, entrypoint, config. history reveals each layer's command and size — the foundation of image optimization. manifest inspect checks a remote registry without pulling.
# low-level image metadata (JSON)
docker inspect nginx:latest
# specific field via Go template
docker inspect --format '{{.Arch}} {{.Os}}' nginx
# show image layers and sizes
docker history nginx:latest
# no-trunc to see full command for each layer
docker history --no-trunc nginx:latest
# inspect an image in a registry (manifest)
docker manifest inspect nginx:latestSaving & Loading Images
save/load preserves image layers and history — the right choice for transferring images offline. export/import flattens to a single layer, losing history. For online transfer, prefer registry push/pull over tar files.
# save an image to a tar file
docker save -o myapp.tar myapp:1.0
# save multiple images
docker save -o bundle.tar myapp:1.0 nginx:latest
# load an image from a tar file
docker load -i myapp.tar
# pipe between hosts (no intermediate file)
docker save myapp:1.0 | gzip | ssh host 'gunzip | docker load'
# export a container filesystem (not an image)
docker export mycontainer | gzip > fs.tar.gzContainer Management
Running Containers
-it combines -i (keep stdin open) and -t (allocate a tty) for interactive sessions. --rm cleans up after exit — ideal for one-off commands. --restart unless-stopped survives reboots but respects manual stops.
# run in background with a name and port mapping
docker run -d --name web -p 8080:80 nginx
# run interactively with a terminal
docker run -it --name shell alpine sh
# run and auto-remove on exit
docker run --rm alpine echo hello
# run with a volume
docker run -d -v mydata:/data alpine
# run with environment variables
docker run -d -e MYSQL_ROOT_PASSWORD=secret mysql
# run with a restart policy
docker run -d --restart unless-stopped nginxExecuting Commands (exec)
exec runs a new process inside an already-running container — the original entrypoint keeps running. Use it for debugging, admin tasks, or running one-off scripts. The default user is the image's USER; override with -u.
# open an interactive shell in a running container
docker exec -it web sh
# run a command and return
docker exec web ls /usr/share/nginx/html
# run as a specific user
docker exec -u root web whoami
# set environment variables for the command
docker exec -e DEBUG=1 web env
# run with a working directory
docker exec -w /tmp web pwd
# run as detached (rarely useful)
docker exec -d web touch /tmp/markerViewing Logs
logs shows stdout/stderr captured by the container's logging driver. Applications should log to stdout, not files, to integrate with docker logs. --since/--until accept RFC3339 timestamps or Go durations (30m, 2h).
# follow (tail) logs
docker logs -f web
# show last 100 lines
docker logs --tail 100 web
# show logs since a timestamp
docker logs --since 2024-01-01T00:00:00 web
# show logs from the last 30 minutes
docker logs --since 30m web
# add timestamps to each line
docker logs -t web
# show logs for a specific timeframe
docker logs --since 10m --until 5m webContainer Inspection
inspect returns rich JSON covering config, state, network, and mounts. Go templates extract specific fields — extremely useful for scripting. The LogPath reveals where the json-file driver stores logs on the host.
# full JSON metadata
docker inspect web
# container IP address
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web
# container log path (json-file driver)
docker inspect --format '{{.LogPath}}' web
# mounts and volumes
docker inspect --format '{{json .Mounts}}' web
# container state (running, exited, etc.)
docker inspect --format '{{.State.Status}} {{.State.ExitCode}}' web
# all container ports
docker inspect --format '{{json .NetworkSettings.Ports}}' webStarting, Stopping & Restarting
start resumes a stopped container with its original config. update modifies a running container's constraints and restart policy without recreating it — useful for tightening resource limits in production.
# start a stopped container
docker start web
# stop with a custom timeout (seconds)
docker stop -t 30 web
# restart (keeps container intact)
docker restart web
# rename a container
docker rename web webserver
# update container config (e.g. restart policy)
docker update --restart unless-stopped web
# pause/resume (cgroup freezer)
docker pause web
docker unpause webCopying Files
cp works in both directions and on stopped containers — handy for injecting hotfixes or extracting logs. It is not designed for high-frequency sync; use a bind mount or volume for development workflows. The tar+exec trick streams large trees efficiently.
# copy a file from host to container
docker cp file.txt web:/tmp/
# copy from container to host
docker cp web:/etc/nginx/nginx.conf ./nginx.conf
# copy a directory
docker cp ./app web:/usr/src/app
# copy with archive mode (preserves perms)
docker cp -a ./config web:/etc/app
# copy to a stopped container (works too)
docker cp script.sh web:/script.sh
# tar stream from host to container stdin
docker exec -i web tar xzf - -C /app < app.tar.gzDockerfile Basics
FROM — Base Image
FROM is the first instruction and defines the base layer. Choose minimal bases (alpine, distroless, scratch) to reduce attack surface and size. Pinning by digest guarantees reproducible builds since tags can change.
# use an official base image
FROM ubuntu:22.04
# pin a specific digest for reproducibility
FROM ubuntu@sha256:abc123def...
# multi-arch build (buildx picks the host arch)
FROM --platform=$BUILDPLATFORM golang:1.21 AS builder
# use a minimal image
FROM alpine:3.18
# scratch = empty image, for static binaries
FROM scratch
# use a previous build stage
FROM builder AS finalRUN — Execute Commands
Each RUN creates a layer. Combine related commands with && and clean up in the same layer to keep images small. The exec form (JSON array) does not invoke a shell, so no variable expansion or chaining — but it handles signals correctly.
# install packages
RUN apt-get update && apt-get install -y curl
# clean up in the same layer to reduce size
RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
# combine commands with && to reduce layers
RUN mkdir -p /app/config && chown 1000:1000 /app/config
# use exec form (preferred) to ensure signals
RUN ["npm", "install"]
# use a shell for variables and chaining
RUN VERSION=1.0 && wget https://example.com/v${VERSION}.tar.gzCOPY & ADD
COPY is preferred for local files — it is explicit and predictable. ADD adds magic: URL fetching and auto-extraction of tar/zip archives. This magic can surprise you; reach for ADD only when you need extraction. Both create a layer.
# copy a file into the image
COPY package.json /app/
# copy a directory
COPY src/ /app/src/
# copy with ownership
COPY --chown=node:node package.json /app/
# copy from a previous build stage
COPY --from=builder /app/dist /app/dist
# ADD can fetch URLs and extract archives
ADD https://example.com/app.tar.gz /tmp/
# ADD extracts local tar archives automatically
ADD app.tar.gz /usr/src/WORKDIR
WORKDIR sets the working directory for subsequent instructions and the container's default. It creates the directory if missing. Always prefer WORKDIR over cd in RUN — cd does not persist across layers. Use absolute paths for clarity.
# set the working directory
WORKDIR /usr/src/app
# subsequent RUN, CMD, COPY use this dir
COPY . .
RUN npm install
# WORKDIR creates the directory if it does not exist
WORKDIR /opt/data/logs
# use a relative path (relative to previous WORKDIR)
WORKDIR sub
# avoid this anti-pattern (use absolute paths)
WORKDIR /app
RUN cd /app && do_something # cd is lost after RUNUSER
USER sets the user (and optionally group) for RUN, CMD, and ENTRYPOINT. Running as non-root is a key security best practice. The user must exist in the image. Numeric UIDs are portable across images and avoid name lookup issues.
# create a non-root user and switch to it
RUN groupadd -r app && useradd -r -g app appuser
USER appuser
# run subsequent instructions as this user
RUN whoami # appuser
# use a numeric UID for portability
USER 1000
# USER in compose/runtime overrides this
USER 1000:1000
# common pattern: node image ships a 'node' user
FROM node:20-alpine
USER node.dockerignore
.dockerignore reduces the build context sent to the daemon — faster builds, smaller context, and prevents secrets like .env from leaking into image layers. It uses similar syntax to .gitignore. Always exclude node_modules and build artifacts that are recreated during the build.
# .dockerignore — exclude files from the build context
# version control
.git
.gitignore
# dependencies (fetched inside the image)
node_modules
npm-debug.log
# build artifacts
dist
build
*.log
# local env files (NEVER bake secrets)
.env
.env.local
# docker files themselves
Dockerfile
docker-compose*.yml
# OS files
.DS_Store
Thumbs.dbDockerfile Advanced
CMD vs ENTRYPOINT
ENTRYPOINT defines the executable; CMD provides default arguments that can be overridden. Use exec form (JSON array) so the process receives signals directly — shell form wraps the command in /bin/sh -c, breaking graceful shutdown. A common pattern: ENTRYPOINT script + CMD default args.
# CMD: default command, easily overridden
CMD ["nginx", "-g", "daemon off;"]
# override at runtime
# docker run myimage nginx -v
# ENTRYPOINT: fixed command, CMD becomes args
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
# runtime args append to ENTRYPOINT
# docker run myimage -v -> nginx -v
# shell form (avoid — no signal handling)
CMD nginx -g "daemon off;"
# script as entrypoint that accepts args
ENTRYPOINT ["./entrypoint.sh"]
CMD ["--help"]ENV — Environment Variables
ENV creates image-level variables visible at build and runtime. Use ARG for build-only values to avoid leaking config into the final image. In exec-form CMD/ENTRYPOINT, ${VAR} is not expanded by the shell — wrap with sh -c to expand.
# set a persistent env var (baked into image)
ENV NODE_ENV=production
ENV PATH=/app/node_modules/.bin:$PATH
# use in subsequent RUN instructions
ENV VERSION=1.2.3
RUN wget https://example.com/v${VERSION}.tar.gz
# multiple in one line
ENV NODE_ENV=production PORT=3000 LOG_LEVEL=info
# override at runtime
# docker run -e NODE_ENV=development myapp
# view env vars of a container
docker exec web env
# reference env vars in CMD
ENV APP_PORT=3000
CMD ["sh", "-c", "node server.js ${APP_PORT}"]ARG — Build Arguments
ARG values exist only during the build of the stage that declares them. They do not persist into the runtime container — use ENV to carry a value to runtime. ARGs declared before FROM are 'global' but must be re-declared in each stage to use there.
# declare a build argument with a default
ARG VERSION=latest
FROM base:${VERSION}
# use ARG only during build (not at runtime)
ARG NODE_ENV=production
RUN echo "Building for ${NODE_ENV}"
# multi-stage: ARG must be redeclared per stage
ARG VERSION
FROM base:${VERSION}
ARG VERSION
RUN echo ${VERSION}
# pass at build time
# docker build --build-arg VERSION=1.0 .
# ARG not available in CMD (use ENV to pass through)
ARG PORT=3000
ENV PORT=${PORT}
CMD ["sh", "-c", "serve --port ${PORT}"]EXPOSE
EXPOSE is documentation — it does not publish ports. Publishing happens with -p (host:container) at runtime or ports: in Compose. EXPOSE helps tooling understand the intended ports and enables automatic linking in legacy networks.
# document which port the container listens on
EXPOSE 80
# expose multiple ports
EXPOSE 80 443
# expose with protocol
EXPOSE 53/udp
# EXPOSE does NOT publish the port
# you still need -p at runtime
# docker run -p 8080:80 myapp
# in compose, expose vs ports
# expose: only to linked services
# ports: published to the host
# view exposed ports
docker inspect --format '{{json .Config.ExposedPorts}}' myappLABEL & Metadata
LABEL attaches key-value metadata to images. Adopt the OCI org.opencontainers.image.* labels for interoperability with registries and tools. Labels are useful for tracking version, source, licensing, and CI build info.
# add a label (key=value metadata)
LABEL maintainer="[email protected]"
LABEL version="1.0"
LABEL description="My web app"
# multiple labels in one instruction
LABEL org.opencontainers.image.title="myapp" \
org.opencontainers.image.version="1.0" \
org.opencontainers.image.source="https://github.com/me/myapp"
# view labels
docker inspect --format '{{json .Config.Labels}}' myapp
# filter images by label
docker images --filter "label=version=1.0"
# deprecated: MAINTAINER (use LABEL maintainer instead)
# MAINTAINER Alice <[email protected]>VOLUME in Dockerfile
VOLUME declares a mount point; Docker creates an anonymous volume on first run unless you bind a host path. It signals that the directory holds stateful data. A pitfall: declaring VOLUME after COPY can shadow your copied files with an empty volume.
# declare an anonymous volume mount point
VOLUME /data
# multiple mount points
VOLUME /data /config /logs
# at runtime, Docker creates an anonymous volume
# docker run -v mydata:/data myapp (named instead)
# VOLUME in Dockerfile: any files copied to /data
# are copied into the new anonymous volume on first run
# inspect volumes
docker inspect --format '{{json .Mounts}}' myapp
# avoid: VOLUME after COPY can hide your data
COPY ./config /config
VOLUME /config # new empty volume shadows /configMulti-stage Builds
Basic Multi-stage
Multi-stage builds copy only the final artifacts into the runtime image — build tools, source, and dev dependencies stay in the builder stage. The result: a tiny, secure runtime image. Name stages with AS to reference them in COPY --from.
# stage 1: build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# stage 2: runtime (small)
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]Selecting a Build Stage
--target builds only up to the named stage, skipping later stages. This enables a single Dockerfile for dev, test, and production: target 'test' for CI, 'final' for production. BuildKit parallelizes independent stages automatically.
# build only up to a named stage
# docker build --target builder -t myapp:dev .
FROM node:20 AS builder
RUN npm ci && npm run build
FROM node:20-alpine AS test
COPY --from=builder /app /app
RUN npm test
FROM nginx:alpine AS final
COPY --from=builder /app/dist /usr/share/nginx/html
# target a specific stage
# docker build --target test .Slim Runtime Images
For static binaries (Go, Rust), scratch produces the smallest possible image with zero attack surface. Add CA certificates for HTTPS. Distroless offers a minimal base with root CAs and a non-root user, ideal for dynamic languages or when you need a shell-free runtime.
# build stage
FROM golang:1.21 AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o app -ldflags="-s -w" .
# scratch = empty image, only the static binary
FROM scratch
COPY --from=builder /src/app /app
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/app"]
# result: ~10MB image
# or use distroless for a minimal OS base
FROM gcr.io/distroless/static
COPY --from=builder /src/app /app
ENTRYPOINT ["/app"]Builder Pattern for Compiled Languages
Compile in a full SDK image, then copy the artifact into a runtime-only image (JRE). This dramatically shrinks the image (JDK ~600MB vs JRE ~200MB) and removes build tools from production. Pre-fetching dependencies before COPY improves layer caching.
# stage 1: compile
FROM maven:3.9 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
# stage 2: runtime with only the JRE
FROM eclipse-temurin:21-jre-alpine
COPY --from=builder /build/target/*.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
# benefits: no JDK, no source, no Maven cache in runtimeCopying from External Images
COPY --from can pull from any image — useful for assembling tools from multiple bases without a build stage. Stages can also be referenced by index (0-based), but naming with AS is clearer. Pin external images by digest for reproducibility.
# copy a binary from another image (no build stage needed)
FROM alpine:3.18
COPY --from=nginx:alpine /usr/sbin/nginx /usr/sbin/nginx
COPY --from=nginx:alpine /etc/nginx /etc/nginx
RUN apk add --no-cache libpcre2
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# use a specific digest for reproducibility
COPY --from=redis:7@sha256:abc... /usr/local/bin/redis-server /usr/local/bin/
# copy from a built stage referenced by index
COPY --from=0 /build/output /appBuildKit Features
BuildKit unlocks cache mounts (npm/pip caches persist between builds without bloating the image), secret mounts (credentials never touch a layer), SSH forwarding for private repos, and heredoc syntax for cleaner multi-line RUN. Add the # syntax directive to pin the Dockerfile frontend version.
# enable BuildKit
# DOCKER_BUILDKIT=1 docker build .
# syntax directive for latest frontend
# syntax=docker/dockerfile:1
FROM alpine
# mount a cache (persists across builds, not in image)
RUN --mount=type=cache,target=/root/.npm npm ci
# mount a secret (never written to layers)
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
# mount an SSH socket for private repos
RUN --mount=type=ssh git clone [email protected]:me/repo.git
# heredoc for multi-line scripts
RUN <<EOF
set -ex
apk add --no-cache curl
mkdir -p /app/data
EOFVolumes
Creating & Using Volumes
Named volumes are Docker-managed and the recommended way to persist data. They survive container removal and are independent of the host filesystem layout. The :ro suffix mounts read-only. inspect reveals the on-disk path under /var/lib/docker/volumes.
# create a named volume
docker volume create mydata
# use it in a container
docker run -d -v mydata:/data alpine sh -c "sleep 1h"
# inspect the volume
docker volume inspect mydata
# mount read-only
docker run -v mydata:/data:ro alpine ls /data
# mount with a different filesystem (e.g. tmpfs)
docker run --tmpfs /cache alpine
# default volume driver: local
docker volume create --driver local mydataListing & Inspecting Volumes
ls lists named volumes; inspect shows the driver and mountpoint. To find which containers use a volume, filter ps by volume. Volumes live under /var/lib/docker/volumes/<name>/_data on Linux hosts — access via docker exec, not direct host edits.
# list all volumes
docker volume ls
# filter by name
docker volume ls -q --filter name=mydata
# show volume details (driver, mountpoint, options)
docker volume inspect mydata
# find the on-disk path
docker volume inspect --format '{{.Mountpoint}}' mydata
# find containers using a volume
docker ps -a --filter volume=mydata --format '{{.Names}}'
# check volume disk usage
docker system df -v | grep mydataVolume Drivers
The local driver supports NFS, block devices, and tmpfs via mount options. For cloud storage (EBS, Azure Disk), use a plugin driver. Drivers abstract storage so containers move between hosts without changing the run command — key for Swarm/Kubernetes.
# local driver (default, on host disk)
docker volume create --driver local mydata
# NFS volume
docker volume create --driver local \
--opt type=nfs \
--opt o=addr=10.0.0.5,rw \
--opt device=:/export/data nfsvol
# tmpfs (in-memory, ephemeral)
docker run --tmpfs /cache alpine
# block device
docker volume create --driver local \
--opt type=ext4 \
--opt device=/dev/sdb1 blockvol
# third-party drivers (e.g. cloud)
docker volume create --driver rexray/ebs ebsvolBacking Up Volumes
Back up a volume by mounting both the volume and a host directory in a temporary container, then tar the contents. Restore reverses the process. This pattern works for any volume driver and is the basis of automated backup scripts.
# back up a volume to a tar file
docker run --rm -v mydata:/data -v $(pwd):/backup alpine \
tar czf /backup/mydata.tar.gz -C /data .
# restore a volume from a tar file
docker volume create mydata2
docker run --rm -v mydata2:/data -v $(pwd):/backup alpine \
tar xzf /backup/mydata.tar.gz -C /data
# migrate data between volumes
docker run --rm -v src:/src -v dst:/dst alpine \
cp -a /src/. /dst/
# snapshot using a temporary container
docker run --rm -v mydata:/data alpine ls -la /dataRemoving Volumes
A volume cannot be removed while a container references it (remove the container first). prune removes only unused volumes. Volume deletion is irreversible — the data is permanently lost. Always back up production volumes before pruning.
# remove a named volume (must not be in use)
docker volume rm mydata
# remove all unused volumes
docker volume prune
# remove volumes older than 24h not used
docker volume prune --filter "until=24h"
# force remove (even if a stopped container referenced it)
docker volume rm -f mydata
# remove with a label filter
docker volume prune --filter "label=temporary"
# CAUTION: data is gone permanently
docker volume rm mydataSharing Volumes Between Containers
Multiple containers can mount the same volume for sharing state. --volumes-from clones another container's volume mounts — handy for sidecar patterns. Add :ro for read-only consumers. Coordinate concurrent writes to avoid corruption; some apps need a lock.
# share a volume between two containers
docker run -d --name db -v shared:/data alpine sleep 1h
docker run -d --name app -v shared:/data alpine sleep 1h
# use --volumes-from to copy a container's mounts
docker run -d --name helper --volumes-from db alpine sleep 1h
# read-only shared volume
docker run -d --name reader -v shared:/data:ro alpine sleep 1h
# compose: shared volume across services
# volumes:
# shared:
# services share 'shared' via volumes: ['shared:/data']
# verify both see the same data
docker exec db sh -c "echo hi > /data/msg"
docker exec app cat /data/msgBind Mounts
Basic Bind Mount
Bind mounts map a host path directly into the container — ideal for live development where host edits reflect instantly. Use absolute host paths. The :ro suffix prevents the container from writing back to the host. Modern syntax uses --mount for clarity.
# bind mount a host directory (modern syntax)
docker run -d --type bind --source $(pwd)/app --target /app nginx
# short form (-v)
docker run -d -v $(pwd)/app:/app nginx
# absolute host path required
docker run -v /home/me/app:/app nginx
# mount a single file
docker run -v /home/me/app.conf:/etc/app/app.conf:ro nginx
# mount read-only
docker run -v $(pwd)/app:/app:ro nginx
# compose equivalent
# volumes:
# - ./app:/appRead-only Bind Mount
Read-only mounts protect host configuration from container writes — critical for security. --read-only makes the entire root filesystem immutable, requiring explicit writable mounts for /tmp and log dirs. This hardens containers against compromise.
# mount config read-only
docker run -v /etc/myapp:/etc/myapp:ro myapp
# --mount equivalent with explicit type
docker run --mount type=bind,source=/etc/myapp,target=/etc/myapp,readonly myapp
# read-only root filesystem (extra security)
docker run --read-only -v /tmp myapp
# combine read-only root with a writable tmpfs
docker run --read-only --tmpfs /tmp --tmpfs /var/cache myapp
# verify the mount is read-only
docker exec myapp touch /etc/myapp/test # should failDevelopment Workflow
Mount source code so host edits reflect instantly in the container. The 'anonymous volume for node_modules' trick prevents the host's empty node_modules from shadowing the installed deps in the image. Polling may be needed on Windows/Mac for file-watch reliability.
# live-reload: mount source, run dev server
docker run -d -p 3000:3000 -v $(pwd)/src:/app/src node:20 \
sh -c "cd /app && npm run dev"
# mount node_modules separately (avoid host override)
docker run -d -v $(pwd):/app -v /app/node_modules node:20 npm start
# compose dev workflow
# services:
# web:
# volumes:
# - ./src:/app/src
# - /app/node_modules # anonymous, isolates deps
# hot reload with file watching (polling for Windows/Mac)
docker run -e CHOKIDAR_USEPOLLING=true -v $(pwd):/app node:20 npm startSELinux & AppArmor Labels
On SELinux-enabled hosts (RHEL/CentOS/Fedora), bind mounts need a :z or :Z suffix to relabel the host path so the container can access it. :z shares the path among containers; :Z makes it private. Forgetting the suffix causes 'permission denied' errors.
# SELinux: :z (shared) or :Z (private) suffix
docker run -v /host/path:/container/path:z myapp
docker run -v /host/path:/container/path:Z myapp
# :z labels the host path as shared among containers
# :Z labels it private to this container
# disable SELinux isolation (NOT recommended)
docker run --security-opt label=disable myapp
# AppArmor profile
docker run --security-opt apparmor=docker-default myapp
# custom AppArmor profile
docker run --security-opt apparmor=myprofile myappMount vs Volume vs tmpfs
Volumes: Docker-managed, portable, best for persistent data. Bind mounts: host paths, best for dev and config injection. tmpfs: in-memory, best for secrets and ephemeral caches. --mount syntax is more explicit and self-documenting than -v.
# named volume (Docker-managed, portable)
docker run -v mydata:/data nginx
# bind mount (host path, host-dependent)
docker run -v /home/me/data:/data nginx
# --mount explicit syntax (recommended)
docker run --mount type=volume,source=mydata,target=/data nginx
docker run --mount type=bind,source=/home/me/data,target=/data nginx
docker run --mount type=tmpfs,target=/cache nginx
# tmpfs (in-memory, ephemeral, fast)
docker run --tmpfs /cache nginx
# tmpfs with size limit
docker run --mount type=tmpfs,target=/cache,tmpfs-size=64m nginxBind Mounts in Compose
In Compose, a relative path (./html) creates a bind mount, while a name (logs) referencing the volumes: section creates a named volume. A bare path (/cache) creates an anonymous volume. The long-form syntax makes options like read_only explicit.
# docker-compose.yml
services:
web:
image: nginx
volumes:
# relative host path = bind mount
- ./html:/usr/share/nginx/html:ro
# named volume
- logs:/var/log/nginx
# anonymous volume
- /cache
volumes:
logs: # declare named volumes here
# use ~ (tilde) — Compose expands it
volumes:
- ~/config:/etc/app:ro
# long-form syntax with options
volumes:
- type: bind
source: ./html
target: /usr/share/nginx/html
read_only: trueNetworking
Bridge Network (Default)
The default bridge network lacks automatic DNS — containers must use IPs. Custom bridge networks provide DNS-based service discovery: containers resolve each other by name. This is why Compose creates a custom network for each project automatically.
# the default bridge network (named 'bridge')
docker run -d --name web nginx
# create a custom bridge network (recommended)
docker network create mynet
# attach a container to a custom network
docker run -d --name web --network mynet nginx
docker run -d --name db --network mynet postgres
# containers on the same custom network can resolve by name
docker exec web ping db # DNS resolves 'db'
# the default bridge does NOT support DNS names
# always use a custom bridge for multi-container appsHost Network
Host networking removes the network namespace — the container shares the host's interfaces and ports. It offers the best performance but sacrifices isolation and port mapping. Available only on Linux; on Mac/Windows, it maps to a VM network, not the host.
# use the host's network stack directly (no isolation)
docker run --network host nginx
# host network disables port mapping
# -p is ignored when --network host is used
# useful for performance-sensitive apps
docker run --network host -d myapp
# only works on Linux (not Docker Desktop on Mac/Windows)
# inspect host network
docker network inspect host
# bound to host interfaces — be careful with privileged ports
docker run --network host --cap-add NET_ADMIN myappOverlay Network (Swarm)
Overlay networks span multiple Swarm nodes, enabling secure cross-host communication with built-in DNS. --attachable lets standalone containers join an overlay (useful for debugging). Encryption secures traffic between nodes; enable it for sensitive workloads.
# create an overlay network (requires Swarm mode)
docker network create -d overlay myoverlay
# overlay with encryption
docker network create -d overlay -o encrypted myoverlay
# deploy a service on the overlay
docker service create --network myoverlay --name web nginx
# multi-host communication for Swarm services
# containers on different nodes resolve by service name
# node-local network bridged to overlay
docker network create -d overlay --attachable myoverlay
docker run -d --network myoverlay alpine # standalone containerCustom Networks
Custom networks let you control subnets, gateways, and isolation. --internal prevents outbound internet access. macvlan gives containers a real presence on the physical LAN (each gets its own MAC) — useful for legacy apps but breaks host-to-container communication by default.
# create a bridge network with a subnet
docker network create --subnet 172.20.0.0/16 mynet
# assign a static IP
docker run -d --network mynet --ip 172.20.0.10 nginx
# set a custom gateway
docker network create --subnet 172.20.0.0/16 \
--gateway 172.20.0.1 mynet
# restrict to internal only (no external access)
docker network create --internal mynet
# specify a driver and options
docker network create -d bridge \
-o com.docker.network.bridge.name=br0 mynet
# macvlan (container gets a MAC on the physical network)
docker network create -d macvlan \
--subnet 192.168.1.0/24 -o parent=eth0 mymacvlanContainer DNS
Docker's embedded DNS (127.0.0.11) resolves container names on user-defined networks. The default bridge has no DNS. --dns adds external resolvers; --add-host injects static entries. Use service names (not IPs) for inter-container communication so redeploys don't break DNS.
# containers on custom networks resolve by name
docker network create mynet
docker run -d --name db --network mynet postgres
docker run -it --network mynet alpine nslookup db
# the embedded DNS server is at 127.0.0.11
docker exec web cat /etc/resolv.conf
# add custom DNS servers
docker run --dns 8.8.8.8 alpine nslookup example.com
# add DNS search domains
docker run --dns-search example.com alpine nslookup myhost
# add a host entry (like /etc/hosts)
docker run --add-host myservice:10.0.0.5 alpine ping myservice
# extra hosts in compose
# extra_hosts:
# - "myservice:10.0.0.5"Network Inspection & Management
network connect/disconnect lets you attach a running container to additional networks without restarting — useful for sidecars. inspect shows connected containers, subnet, and driver. prune removes networks not used by any container.
# list all networks
docker network ls
# inspect a network (containers, IPAM, driver)
docker network inspect mynet
# see which containers are on a network
docker network inspect --format '{{range .Containers}}{{.Name}} {{end}}' mynet
# connect a running container to another network
docker network connect mynet2 web
# disconnect a container from a network
docker network disconnect mynet web
# remove an unused network
docker network rm mynet
docker network prunePort Mapping
Basic Port Mapping
-p host:container maps a host port to a container port. If you omit the host port, Docker picks one (use docker port to find it). Each host port can only map to one container — use a reverse proxy or different host ports for multiple instances.
# map host:container
docker run -d -p 8080:80 nginx
# map to a random host port
docker run -d -p 80 nginx
docker port $(docker ps -lq) 80 # find the assigned port
# map multiple ports
docker run -d -p 8080:80 -p 8443:443 nginx
# map the same port on host and container
docker run -d -p 80:80 nginx
# explicit protocol (default tcp)
docker run -d -p 53:53/udp dns
# long form
docker run -d --publish 8080:80 nginxBinding to a Specific IP
Prefixing with an IP controls which interface the port binds to. 127.0.0.1 keeps the service local-only — essential for databases you don't want exposed. 0.0.0.0 (default) binds to all interfaces. Always bind databases to localhost in production.
# bind to a specific host IP
docker run -d -p 127.0.0.1:8080:80 nginx
# bind to localhost only (not exposed externally)
docker run -d -p 127.0.0.1:5432:5432 postgres
# bind to all interfaces (default)
docker run -d -p 0.0.0.0:8080:80 nginx
# bind to an IPv6 address
docker run -d -p [::1]:8080:80 nginx
# multiple bindings
docker run -d -p 127.0.0.1:8080:80 -p 0.0.0.0:8443:443 nginx
# compose: specify IP
# ports:
# - "127.0.0.1:5432:5432"UDP & SCTP Ports
The /udp or /sctp suffix selects the protocol; TCP is the default. For services like DNS that need both, declare both mappings. Port ranges map 1:1 between host and container — useful for WebRTC, RTP, or passive FTP. Ensure firewall rules match the protocols.
# UDP port (DNS, syslog, statsd)
docker run -d -p 53:53/udp coredns
# both TCP and UDP
docker run -d -p 53:53/tcp -p 53:53/udp coredns
# SCTP (e.g. telecom signaling)
docker run -d -p 3868:3868/sctp myapp
# range of ports (host range maps to container range)
docker run -d -p 8000-8100:8000-8100 myapp
# UDP range
docker run -d -p 50000-50010:50000-50010/udp myapp
# compose with protocol
# ports:
# - "53:53/udp"Range of Ports
Port ranges map 1:1 (host 8000-8005 to container 8000-8005). -P (capital) auto-publishes every EXPOSE port to a random high host port — handy for quick tests. Use docker ps to see the actual mappings when ports are random.
# map a range of host ports to container ports
docker run -d -p 8000-8005:8000-8005 myapp
# map host range to a single container port (NOT supported)
# this maps range-to-range only
# random port for each (useful for testing)
docker run -d -p 80 -p 443 nginx
docker port $(docker ps -lq)
# expose a range with EXPOSE (documentation only)
# EXPOSE 8000-8005
# publish all exposed ports at once
docker run -d -P nginx # capital P publishes all EXPOSE ports
# view the random mappings
docker ps --format '{{.Names}} {{.Ports}}'Exposing vs Publishing
EXPOSE documents intent; -p actually publishes. In Compose, expose shares a port only with other services on the same network, while ports publishes to the host. This distinction matters for databases: expose internally, never publish to the host in production.
# Dockerfile: EXPOSE is documentation, NOT a publish
EXPOSE 80 443
# runtime: -p PUBLISHES the port to the host
docker run -d -p 8080:80 nginx
# -P publishes all EXPOSE ports to random host ports
docker run -d -P nginx
# compose: expose vs ports
services:
web:
image: nginx
expose:
- "80" # only to linked services, NOT to host
ports:
- "8080:80" # published to the host
# view exposed (not published) ports
docker inspect --format '{{json .Config.ExposedPorts}}' nginxRandom Port Mapping
Omitting the host port lets Docker pick a random ephemeral port (32768-60999 by default). Use docker port or docker ps to discover it. This is useful for scaling identical services on one host. Adjust the ephemeral range in /proc/sys/net/ipv4/ip_local_port_range on Linux.
# publish to a random high host port
docker run -d -p 80 nginx
# publish all EXPOSE ports randomly
docker run -d -P nginx
# find the assigned port
docker port <container> 80
docker port <container>
# see mappings in ps output
docker ps --format 'table {{.Names}} {{.Ports}}'
# prefer a range for random allocation
docker run -d -p 30000-40000:80 nginx
# compose: random host port
# ports:
# - "80" # random host port, container port 80Environment Variables
Setting Env Vars
-e (or --env) sets environment variables in the container. -e VAR (no value) inherits the variable from the host shell. For sensitive values, prefer --env-file or Docker secrets over plain -e, which is visible in docker inspect and process listings.
# single variable
docker run -d -e MYSQL_ROOT_PASSWORD=secret mysql
# multiple variables
docker run -d \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=mydb \
-e MYSQL_USER=app \
mysql
# pass from the host environment
docker run -d -e HOST_PWD mysql
# HOST_PWD in the container = $HOST_PWD on host
# set a variable without a value (passes host value or empty)
docker run -e DEBUG alpine env
# long form
docker run --env MYSQL_ROOT_PASSWORD=secret mysqlEnv Files
--env-file loads variables from a file, keeping the run command clean and secrets out of shell history. Compose auto-loads a top-level .env for variable substitution in the compose file itself. Comments (#) and blank lines are ignored; quotes are preserved literally.
# .env file
# DB_HOST=db
# DB_USER=app
# DB_PASSWORD=secret
# load variables from a file
docker run --env-file .env myapp
# load multiple env files
docker run --env-file .env --env-file .env.local myapp
# Compose reads .env automatically for variable substitution
# and supports env_file per service:
# services:
# web:
# env_file:
# - .env
# - .env.local
# env file with comments and blank lines
docker run --env-file config.env myappEnv Vars in Dockerfile
ENV bakes variables into the image, visible at build and runtime. ARG is build-time only — convert to ENV if the runtime needs it. Override baked values with -e at runtime. Note: env vars are visible in docker inspect, so don't store secrets there.
# ENV sets a persistent variable
ENV NODE_ENV=production
ENV PATH=/app/bin:$PATH
# use ARG for build-time only, ENV for runtime
ARG BUILD_VERSION=1.0
ENV APP_VERSION=${BUILD_VERSION}
# view all env vars
docker exec web env
# inspect a specific variable
docker exec web printenv NODE_ENV
# override at runtime
docker run -e NODE_ENV=development myapp
# set via Compose
# environment:
# - NODE_ENV=development
# - DEBUG=trueDynamic Env Vars & Substitution
Shell expansion ($(...) or ${VAR}) happens on the host before Docker sees the value. In Compose, ${VAR} is substituted from the .env file. In exec-form CMD, ${VAR} is NOT expanded — wrap with sh -c. Pass the host UID for correct file ownership in bind mounts.
# pass a computed value at runtime
docker run -e STARTUP_TIME=$(date +%s) myapp
# use host shell expansion
docker run -e USER_ID=$(id -u) myapp
# Compose variable substitution from .env
# services:
# web:
# environment:
# DB_HOST: ${DB_HOST}
# .env file:
# DB_HOST=db
# Compose with default values
# DB_HOST: ${DB_HOST:-localhost}
# pass the current user for permission matching
docker run -u $(id -u):$(id -g) -e HOME=/tmp myapp
# resolve env at runtime with sh -c (not in exec form)
ENV PORT=3000
CMD ["sh", "-c", "node server.js --port ${PORT}"]Compose Environment
Compose's environment accepts list or map syntax. env_file loads values into the container; .env (top-level) provides variables for ${} substitution in the compose file itself. Use the map form for readability with many variables.
# docker-compose.yml
services:
web:
image: myapp
environment:
- NODE_ENV=production
- DEBUG=false
- DB_HOST=db
# map form (cleaner for many vars)
environment:
NODE_ENV: production
DB_HOST: db
db:
image: postgres
env_file:
- .env.db
environment:
POSTGRES_DB: ${DB_NAME} # from .env
# top-level .env for substitution
# DB_NAME=mydbSecrets Management
Secrets are mounted as files under /run/secrets, never as env vars, and are encrypted in transit and at rest in Swarm. Avoid baking secrets into images (ENV) or passing via -e — both leak via docker inspect and image layers. BuildKit secret mounts keep build-time credentials out of layers.
# Docker Swarm secrets (encrypted, mounted as files)
echo "supersecret" | docker secret create db_password -
docker service create --secret db_password myapp
# read in the app
# cat /run/secrets/db_password
# Compose secrets (v3.1+)
# services:
# web:
# secrets:
# - db_password
# secrets:
# db_password:
# file: ./secrets/db_password.txt
# BuildKit secret mount (build-time, not in layers)
# RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
# avoid: baking secrets in ENV or -e (visible in inspect)
docker run -e API_KEY=xxx myapp # NOT recommendedDocker Compose Basics
Basic docker-compose.yml
Compose defines a multi-container app in a single YAML file. services describes each container; volumes and networks are declared at the top level. depends_on controls startup order. docker compose up -d starts everything in the background.
# docker-compose.yml (Compose spec v2)
services:
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
depends_on:
- db
restart: unless-stopped
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- dbdata:/var/lib/postgresql/data
volumes:
dbdata:
# run: docker compose up -dCompose Commands
up creates and starts, down stops and removes. Add -v to down to delete volumes (data loss). exec runs a command in a running service; run starts a new container for a one-off task. --scale launches multiple replicas of a service on the same host.
# start all services (foreground)
docker compose up
# start in background (detached)
docker compose up -d
# rebuild images before starting
docker compose up -d --build
# stop and remove containers (keeps volumes)
docker compose down
# also remove volumes (data loss)
docker compose down -v
# view logs (all services)
docker compose logs -f
# scale a service
docker compose up -d --scale worker=3
# run a one-off command in a service
docker compose exec web sh
docker compose run --rm web python manage.py migrateService Dependencies
depends_on controls startup order, but the dependency may not be 'ready' — use condition: service_healthy with a healthcheck to wait until the database accepts connections. service_completed_successfully waits for init containers. Apps should still retry connections.
services:
web:
image: myapp
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
restart: on-failure
db:
image: postgres
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 5s
retries: 5
redis:
image: redis
# conditions: service_started | service_healthy | service_completed_successfullyBuild in Compose
build defines how to build an image from source; image tags the result. context is sent to the builder (keep it small). target selects a multi-stage build stage. --build forces a rebuild on up. Build args pass values at build time.
services:
web:
build:
context: ./app
dockerfile: Dockerfile.prod
args:
VERSION: 1.0
target: final
image: myapp:1.0
ports:
- "8080:80"
worker:
build: ./worker # shorthand: context only
image: myworker:1.0
# build and tag
docker compose build
docker compose build --no-cache web
docker compose up -d --buildVolumes in Compose
A relative path (./html) is a bind mount; a name (logs) declared under top-level volumes: is a named volume; a bare path (/cache) is anonymous. Named volumes can specify a driver and options (e.g. NFS). Compose prefixes volume names with the project name.
services:
web:
image: nginx
volumes:
# named volume
- logs:/var/log/nginx
# bind mount (relative path)
- ./html:/usr/share/nginx/html:ro
# anonymous volume
- /cache
db:
image: postgres
volumes:
- dbdata:/var/lib/lib/postgresql/data
volumes:
logs:
dbdata:
driver: local
driver_opts:
type: nfs
device: ":/export/data"
o: addr=10.0.0.5,rwNetworks in Compose
Compose creates a default network for all services, but defining custom networks lets you isolate tiers. web bridges frontend and backend. internal: true blocks internet access for the backend. Services on the same network resolve each other by service name via DNS.
services:
web:
image: nginx
networks:
- frontend
- backend
db:
image: postgres
networks:
- backend
proxy:
image: traefik
networks:
- frontend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # no external access
# custom subnet
networks:
backend:
ipam:
config:
- subnet: 172.28.0.0/16Docker Compose Advanced
Compose Profiles
Profiles group optional services. Services without a profile always start; profiled services only start when their profile is activated with --profile. This lets one compose file serve dev, test, and prod: enable profiles selectively per environment.
services:
web:
image: nginx
profiles: ["default", "prod"]
debug:
image: nginx
profiles: ["debug"]
ports:
- "8080:80"
test:
image: myapp-test
profiles: ["test"]
# start only default services
docker compose up -d
# start with the debug profile
docker compose --profile debug up -d
# run tests
docker compose --profile test run test
# start multiple profiles
docker compose --profile prod --profile debug up -dCompose Overrides
Compose auto-loads docker-compose.override.yml on top of docker-compose.yml for local dev customization. For other environments, pass multiple -f files in order — later files override earlier ones. This separates base config from environment-specific tweaks cleanly.
# docker-compose.yml (base)
services:
web:
image: myapp:latest
environment:
NODE_ENV: production
# docker-compose.override.yml (auto-loaded for dev)
services:
web:
build: ./app
environment:
NODE_ENV: development
DEBUG: "true"
volumes:
- ./src:/app/src
command: npm run dev
# explicit override file
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# merge order: later files override earlier ones
# docker compose -f base.yml -f dev.yml -f local.yml upHealthchecks in Compose
Healthchecks in Compose mirror Dockerfile HEALTHCHECK. start_period grants grace time during startup. Combining healthcheck with depends_on: condition: service_healthy makes a service wait until its dependencies are actually ready, not just started.
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
app:
image: myapp
depends_on:
db:
condition: service_healthyScaling & Replicas
--scale launches N identical containers on one host — the service must not bind a fixed host port (use a random port or no host mapping). The deploy.replicas key is honored by Swarm; for plain Compose use --scale. A load balancer fronts scaled services.
# scale at startup (single host)
docker compose up -d --scale worker=4
# docker-compose.yml with replicas (Compose spec)
services:
worker:
image: myworker
deploy:
replicas: 4
resources:
limits:
cpus: "0.5"
memory: 512M
web:
image: nginx
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
# note: replicas deploy key is for Swarm;
# plain Compose uses --scale
# prevent port conflicts when scaling
docker compose up -d --scale web=3 # web must use random portsCompose Config & Validation
config validates and renders the merged compose file (including overrides and .env substitution), which is invaluable for debugging. --services, --images, and --volumes inventory the resources. Use -q in CI to fail fast on malformed compose files.
# validate and print the merged compose file
docker compose config
# check for errors only
docker compose config -q
# list all services
docker compose config --services
# list all images used
docker compose config --images
# list all volumes
docker compose config --volumes
# render with variable substitution (resolve .env)
docker compose config > rendered.yml
# validate before deploying
docker compose config -q && docker compose up -dMultiple Compose Files
Stacking compose files separates concerns: base config, environment overrides, local tweaks. COMPOSE_FILE (colon-separated on Linux/Mac, semicolon on Windows) avoids repeating -f. Lists like ports are appended; maps like environment are key-merged.
# base, dev, and prod files
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# COMPOSE_FILE env var (avoids -f repetition)
export COMPOSE_FILE=docker-compose.yml:docker-compose.dev.yml
docker compose up -d
# docker-compose.prod.yml
services:
web:
image: registry.com/myapp:${TAG}
environment:
NODE_ENV: production
deploy:
replicas: 3
# difference files: add/override specific keys
# lists (ports, volumes) are merged; maps (environment) are mergedDocker Registry
Running a Local Registry
The registry:2 image runs a private registry on port 5000. Mount a volume to persist images. For remote hosts or HTTPS, configure TLS certificates or add the registry to insecure-registries in /etc/docker/daemon.json. The v2 API lets you catalog repositories via curl.
# run the official registry image
docker run -d -p 5000:5000 --name registry \
-v registrydata:/var/lib/registry \
--restart always \
registry:2
# tag and push to it
docker tag myapp:1.0 localhost:5000/myapp:1.0
docker push localhost:5000/myapp:1.0
# pull from it
docker pull localhost:5000/myapp:1.0
# list repositories in the registry
curl http://localhost:5000/v2/_catalog
# list tags for a repository
curl http://localhost:5000/v2/myapp/tags/listPushing to a Private Registry
Images must be tagged with the registry host before pushing. For registries with self-signed certs, add the host to insecure-registries and restart the daemon (not recommended for production — use a real CA or internal CA instead). push -a uploads all tags in the repository.
# tag with the registry URL
docker tag myapp:1.0 registry.local:5000/myapp:1.0
# login
docker login registry.local:5000
# push
docker push registry.local:5000/myapp:1.0
# push all tags
docker push -a registry.local:5000/myapp
# verify it landed
curl http://registry.local:5000/v2/myapp/tags/list
# self-signed cert: configure the daemon
# /etc/docker/daemon.json:
# { "insecure-registries": ["registry.local:5000"] }
# then: systemctl restart dockerPulling from a Private Registry
Pull from a private registry by specifying the full image name. Login credentials are stored in ~/.docker/config.json (use a credential helper for security). For CI/CD, store credentials as secrets and login before pulling. Pin by digest for reproducible deployments.
# login first (credentials cached in ~/.docker/config.json)
docker login registry.local:5000
# pull by full name
docker pull registry.local:5000/myapp:1.0
# pull by digest (immutable)
docker pull registry.local:5000/myapp@sha256:abc123...
# use in Compose
# services:
# web:
# image: registry.local:5000/myapp:1.0
# logout
docker logout registry.local:5000
# check stored credentials
cat ~/.docker/config.jsonRegistry Authentication
Secure a registry with htpasswd basic auth or a token (OAuth) server. For TLS, mount certs and listen on 443. Credential helpers (credsStore) store passwords in the OS keychain instead of the plaintext config.json — essential for shared or CI machines.
# htpasswd-based auth
mkdir -p auth
docker run --entrypoint htpasswd httpd:2 -Bbn user password > auth/htpasswd
# run registry with auth
docker run -d -p 5000:5000 --name registry \
-v $(pwd)/auth:/auth \
-e REGISTRY_AUTH=htpasswd \
-e REGISTRY_AUTH_HTPASSWD_REALM=Registry \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:2
# login now required
docker login localhost:5000
# token-based auth (OAuth, JWT) for enterprise
# configure REGISTRY_AUTH=token with an auth server
# credential helpers (avoid storing in config.json)
# "credsStore": "secretservice" (Linux)
# "credsStore": "osxkeychain" (Mac)
# "credsStore": "wincred" (Windows)Registry API
The Registry HTTP API v2 lets you script registry management. _catalog lists repos; manifests describe image layers. Deletion requires REGISTRY_STORAGE_DELETE_ENABLED=true and removes manifests — blobs are cleaned up by garbage collection (registry garbage-collect).
# list all repositories
curl http://localhost:5000/v2/_catalog
# list tags for a repo
curl http://localhost:5000/v2/myapp/tags/list
# get a manifest
curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
http://localhost:5000/v2/myapp/manifests/1.0
# delete a manifest (by digest)
curl -X DELETE \
http://localhost:5000/v2/myapp/manifests/sha256:abc123...
# enable delete API
# -e REGISTRY_STORAGE_DELETE_ENABLED=true
# check if a blob exists
curl -I http://localhost:5000/v2/myapp/blobs/sha256:abc...Garbage Collection
Deleting a manifest leaves orphaned blobs until garbage collection runs. Stop the registry (or use --dry-run) to avoid collecting in-use layers. Schedule GC regularly in production to reclaim disk from deleted or superseded images. The config.yml path is the registry's config file.
# run garbage collection on a registry
docker exec -it registry \
garbage-collect --dry-run /etc/docker/registry/config.yml
# actual collection (stop the registry first for consistency)
docker stop registry
docker run --rm -v registrydata:/var/lib/registry \
-v /path/config.yml:/etc/docker/registry/config.yml \
registry:2 garbage-collect /etc/docker/registry/config.yml
docker start registry
# delete unreferenced blobs after manifest deletion
# schedule GC as a cron job for production registries
# view disk usage
docker exec registry du -sh /var/lib/registryDocker Hub
Login & Logout
Prefer access tokens over passwords — they're revocable and scoped. --password-stdin reads from stdin, avoiding the password in shell history and process lists. Configure a credential helper to store credentials in the OS keychain rather than plaintext config.json.
# interactive login (prompts for username/password)
docker login
# login with a token (recommended over password)
docker login -u username -p dckr_pat_xxxxx
# login via stdin (avoids shell history)
echo "dckr_pat_xxxxx" | docker login -u username --password-stdin
# logout
docker logout
# credentials stored in ~/.docker/config.json
# or OS keychain via credential helper
# create an access token on Docker Hub
# Account Settings -> Security -> New Access TokenSearching Images
docker search queries Docker Hub from the CLI, with filters for stars, official status, and automated builds. Official images (library/nginx) are vetted by Docker — prefer them for bases. The web UI offers richer filtering by OS, architecture, and category.
# search Docker Hub from the CLI
docker search nginx
# filter by stars
docker search --filter stars=100 nginx
# only official images
docker search --filter is-official nginx
# only automated builds
docker search --filter is-automated nginx
# limit results
docker search --limit 5 nginx
# show full description (no truncation)
docker search --no-trunc nginx
# search on the web for richer filtering
# https://hub.docker.com/search?q=nginxPushing to Docker Hub
Tag images as username/repo:tag, then push to publish to Docker Hub. The repository is created automatically on first push (public by default). Push -a uploads all tags. Free accounts get one private repo; paid plans offer more. Set repository visibility in the Hub web UI.
# tag with your username
docker tag myapp:1.0 username/myapp:1.0
# also tag as latest
docker tag username/myapp:1.0 username/myapp:latest
# login
docker login
# push (creates the repo if it doesn't exist)
docker push username/myapp:1.0
docker push username/myapp:latest
# push all tags
docker push -a username/myapp
# make the repo private on Docker Hub
# (Hub web UI -> Repository -> Settings -> Make Private)Automated Builds
Docker Hub's autobuilds link a git repo and build on every push. Modern workflows prefer GitHub Actions with buildx and push, which offer more control, caching, and multi-arch builds. Hub autobuilds remain a low-effort option for simple projects.
# connect a GitHub/Bitbucket repo to Docker Hub
# Hub web UI -> Create -> Build Rule
# build rule examples:
# Source: github.com/me/myapp Branch: main
# Dockerfile location: /Dockerfile
# Image tag: latest
# trigger a build via webhook
curl -H "Content-Type: application/json" \
--data '{"build": true}' \
https://hub.docker.com/api/build/v1/source/.../trigger/.../call/
# build context with multiple tags and stages
# Source: Branch main -> Tag: branch-main
# Source: Tag v1.0 -> Tag: 1.0, latest
# Autobuild triggers on git push
# (deprecated in favor of GitHub Actions)Organization & Teams
Organizations share repositories across a team with role-based access. Teams get per-repository permissions (read, write, admin). Service accounts provide non-personal tokens for CI/CD — revoke and rotate without affecting individuals. Use orgname/repo namespacing.
# create an organization on Docker Hub
# (web UI: Create Organization)
# create a team and add members
# Org -> Teams -> Create Team -> Add Members
# assign repository permissions per team
# Repository -> Permissions -> Add Team
# Read-only, Read-write, Admin
# create a service account token for CI
# Org -> Service Accounts -> New Service Account
# use the token in CI: docker login -u tokenname -p token
# team namespaces: orgname/repo
docker tag myapp orgname/myapp:1.0
docker push orgname/myapp:1.0Image Trust (DCT)
Docker Content Trust (DCT) uses Notary to sign image tags, ensuring the image you pull is the one the publisher pushed. Enable via DOCKER_CONTENT_TRUST=1. With DCT on, only signed images can be pulled or pushed. Manage signing keys carefully — losing them can lock you out of your images.
# enable Docker Content Trust (signing)
export DOCKER_CONTENT_TRUST=1
# push signs the image automatically
docker push username/myapp:1.0
# pull refuses unsigned images when DCT is on
docker pull username/myapp:1.0
# disable for a single command
DOCKER_CONTENT_TRUST=0 docker pull unsigned/myapp:1.0
# generate delegation keys
docker trust key generate alice
# add a signer to a repository
docker trust signer add --key alice.pub alice username/myapp
# view signatures
docker trust inspect username/myapp:1.0
# revoke and rotate keys via the notary CLIHealthcheck
Basic Healthcheck
A healthcheck runs a command periodically; exit 0 is healthy, non-zero is unhealthy. The container starts in 'starting' and becomes 'healthy' after retries succeed, or 'unhealthy' after retries fail. start_period gives grace time during boot. Health drives Swarm restart decisions.
# add a healthcheck at runtime
docker run -d --name web \
--health-cmd="curl -f http://localhost/ || exit 1" \
--health-interval=30s \
--health-timeout=5s \
--health-retries=3 \
--health-start-period=10s \
nginx
# check health status
docker inspect --format '{{.State.Health.Status}}' web
# view health check log (last 5 results)
docker inspect --format '{{json .State.Health.Log}}' web | jq
# states: starting | healthy | unhealthy | noneHealthcheck in Dockerfile
HEALTHCHECK in a Dockerfile bakes the check into the image. Use the lightest check that confirms the app actually serves — curl -f or wget --spider for HTTP, pg_isready for Postgres. HEALTHCHECK NONE disables an inherited check. Over-broad checks cause false 'unhealthy' flapping.
FROM nginx:alpine
# basic healthcheck (exec form)
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \
CMD curl -f http://localhost/ || exit 1
# using wget (alpine)
HEALTHCHECK CMD wget --spider -q http://localhost/ || exit 1
# postgres ships a ready check
HEALTHCHECK CMD pg_isready -U postgres
# disable a healthcheck inherited from the base image
HEALTHCHECK NONE
# verify
docker inspect --format '{{.Config.Healthcheck}}' myappHealthcheck Options
interval sets check frequency; timeout bounds each check; retries is the consecutive-failure threshold. start_period ignores failures during boot, preventing false 'unhealthy' while the app warms up. Tune these to catch real outages quickly without flapping under load.
# full set of options
HEALTHCHECK \
--interval=30s \ # time between checks (default 30s)
--timeout=5s \ # max time per check (default 30s)
--retries=3 \ # failures to mark unhealthy (default 3)
--start-period=30s \ # boot grace period (default 0s)
CMD curl -f http://localhost/
# interval: how often to run
# timeout: if a check exceeds this, it's a failure
# retries: consecutive failures before unhealthy
# start-period: failures during this don't count
# compose form
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30sInspecting Health Status
ps shows (healthy)/(unhealthy) next to the status. The Health.Log records the last 5 check outputs — invaluable for debugging why a check fails. Run the check command manually via exec to see the actual error. Filter ps by health to find sick containers fast.
# current status
docker inspect --format '{{.State.Health.Status}}' web
# last 5 check results
docker inspect --format '{{range .State.Health.Log}}\
{{.ExitCode}} {{.Output}}{{end}}' web
# show in ps (status column shows (healthy)/(unhealthy))
docker ps
# filter containers by health
docker ps --filter "health=unhealthy"
# troubleshoot a failing check
docker inspect --format '{{json .State.Health.Log}}' web | jq
# run the check command manually
docker exec web curl -f http://localhost/Compose Healthcheck
Compose healthcheck mirrors the Dockerfile. test accepts an array (exec form) or a CMD-SHELL string. disable: true removes an inherited check. Pair healthcheck with depends_on: condition: service_healthy so a service waits for dependencies to be actually ready, not just started.
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
retries: 5
app:
image: myapp
depends_on:
db:
condition: service_healthy
web:
condition: service_healthy
# disable a healthcheck
debug:
image: nginx
healthcheck:
disable: trueHealthcheck Best Practices
Check a real /healthz endpoint that verifies critical dependencies (DB, cache) — a bare TCP check can pass while the app is broken. Keep checks lightweight and fast; they run every interval. Use start_period for slow-starting runtimes (JVM, Rails) so they aren't marked unhealthy during boot.
# check the actual app endpoint, not just TCP
HEALTHCHECK CMD curl -f http://localhost/healthz || exit 1
# include a /healthz endpoint in your app
# that verifies DB and cache connectivity
# keep checks fast (under timeout)
HEALTHCHECK --timeout=3s CMD curl -f http://localhost/healthz
# avoid heavy checks — they run every interval
# don't run migrations or full DB queries
# use start-period for slow starters (Java, Rails)
HEALTHCHECK --start-period=60s CMD ...
# for Swarm, health enables auto-restart of unhealthy tasks
# combine with restart policies and update_config
# example app endpoint (Express)
# app.get('/healthz', (req, res) => res.send('ok'))Resource Limits
Memory Limits
--memory sets a hard limit; exceeding it triggers OOM kill. --memory-swap caps memory+swap combined; setting it equal to memory disables swap. --memory-reservation is a soft hint for the kernel. Disable swap for predictable performance in latency-sensitive workloads.
# limit to 512MB of memory
docker run -d --memory="512m" nginx
# memory + swap (swap = memory + swap allowance)
docker run -d --memory="512m" --memory-swap="1g" nginx
# soft limit (hint to the kernel, may be exceeded)
docker run -d --memory-reservation="256m" nginx
# disable swap (swap = memory, no extra)
docker run -d --memory="512m" --memory-swap="512m" nginx
# OOM behavior: kill the container (default)
docker run -d --memory="512m" --oom-kill-disable nginx
# verify limits
docker inspect --format '{{.HostConfig.Memory}}' <container>CPU Limits
--cpus sets the CPU limit as a fraction of cores (1.5 = 1.5 cores). --cpu-shares is a relative weight used for contention, not a hard cap. --cpuset-cpus pins a container to specific cores, useful for performance isolation. Quota/period is the low-level cgroup form.
# limit to 1 CPU (1.0 = one full core)
docker run -d --cpus="1.0" nginx
# limit to 1.5 CPUs
docker run -d --cpus="1.5" nginx
# CPU shares (relative weight, default 1024)
docker run -d --cpu-shares=512 nginx
# pin to specific CPU cores (0,1)
docker run -d --cpuset-cpus="0,1" nginx
# pin to cores 0-3
docker run -d --cpuset-cpus="0-3" nginx
# quota/period form (100000us per 100000us = 1 CPU)
docker run -d --cpu-quota=50000 --cpu-period=100000 nginxMemory & Swap Behavior
memory-swap is the total of memory + swap. Setting it equal to memory disables swap. -1 allows unlimited swap. Swappiness (0-100) controls the kernel's tendency to swap; lower values favor keeping pages in RAM. Kernel memory limits are advanced and rarely needed.
# memory limit, swap disabled
docker run -d -m 512m --memory-swap 512m nginx
# memory limit, 512m extra swap
docker run -d -m 512m --memory-swap 1g nginx
# unlimited swap (limited memory)
docker run -d -m 512m --memory-swap -1 nginx
# swappiness (0-100, lower = less swapping)
docker run -d -m 512m --memory-swappiness=10 nginx
# kernel memory limit (advanced, rarely needed)
docker run -d --kernel-memory="100m" nginx
# verify swap settings
docker inspect --format '{{.HostConfig.MemorySwap}}' <container>Compose Resource Limits
deploy.resources.limits sets hard caps; reservations set soft guarantees (used for scheduling). Swarm honors these natively. For plain Compose (non-Swarm), use top-level mem_limit and cpus in the v2 format. Reservations let a container claim a minimum even when idle.
services:
web:
image: nginx
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 256M
worker:
image: myworker
mem_swappiness: 10
memswap_limit: 1g
cpus: 2.0
# limits = hard caps; reservations = soft guarantees
# Swarm honors deploy.resources
# plain Compose: use top-level mem_limit, cpus (v2 compat)OOM & Restart Behavior
When memory is exhausted, the kernel OOM-killer picks a victim. oom-kill-disable protects a container but can crash the host — use only with host memory limits. oom-score-adj biases selection: positive = killed first, negative = protected. Check OOMKilled to diagnose restarts.
# default: OOM kills the container, restart policy restarts it
docker run -d --memory=512m --restart unless-stopped nginx
# disable OOM kill for this container (risky)
docker run -d --memory=512m --oom-kill-disable nginx
# set OOM score adjustment (higher = killed first)
docker run -d --oom-score-adj=500 nginx
# protect a critical container (killed last)
docker run -d --oom-score-adj=-500 nginx
# verify restart count
docker inspect --format '{{.RestartCount}}' <container>
# check if OOM-killed
docker inspect --format '{{.State.OOMKilled}}' <container>Inspecting Resource Usage
docker stats streams live CPU, memory, network, and disk IO per container — the go-to for quick triage. --no-stream gives a single snapshot for scripts. For deeper analysis, scrape /sys/fs/cgroup or expose Prometheus metrics via cAdvisor. Compare usage against limits to right-size.
# live resource stats (CPU, memory, network, IO)
docker stats
# specific container
docker stats web
# one snapshot (no streaming)
docker stats --no-stream
# custom format
docker stats --format "table {{.Name}} {{.CPUPerc}} {{.MemUsage}}"
# container cgroup details
docker exec web cat /sys/fs/cgroup/memory/memory.usage_in_bytes
docker exec web cat /sys/fs/cgroup/cpu/cpu.stat
# inspect configured limits
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.NanoCpus}}' webSecurity
Non-root User
Running as non-root limits the blast radius if the app is compromised. Create a dedicated user in the Dockerfile and switch to it with USER. If you must bind a privileged port, use a reverse proxy (nginx) or grant the NET_BIND_SERVICE capability rather than running as root.
# Dockerfile: create and use a non-root user
FROM node:20-alpine
RUN addgroup -S app && adduser -S app -G app
USER app
WORKDIR /app
COPY --chown=app:app . .
CMD ["node", "server.js"]
# at runtime, override the user
docker run -u 1000:1000 myapp
# verify the current user
docker exec web id
# bind to privileged ports? Use capabilities or a reverse proxy
# non-root cannot bind to ports < 1024 without NET_BIND_SERVICE
# distroless images ship a non-root user by default
FROM gcr.io/distroless/nodejs20
USER nonrootRead-only Root Filesystem
A read-only root filesystem prevents attackers from writing malware or modifying configs if compromised. Pair with tmpfs for scratch dirs (/tmp, /cache) and volumes for persistent data. Apps must be designed for this — some frameworks write to unexpected paths and need tmpfs mounts.
# make the root filesystem read-only
docker run --read-only nginx
# allow writes only to specific dirs via tmpfs
docker run --read-only --tmpfs /tmp --tmpfs /var/cache nginx
# combine with a writable volume for persistent data
docker run --read-only -v appdata:/data --tmpfs /tmp myapp
# compose form
services:
web:
image: nginx
read_only: true
tmpfs:
- /tmp
- /var/cache/nginx
# apps must be designed to not write outside allowed pathsLinux Capabilities
By default Docker grants a limited set of Linux capabilities. Drop ALL and add back only what the app needs — the principle of least privilege. --privileged grants all capabilities and host device access; avoid it entirely except for trusted container runtimes. Drop NET_RAW by default.
# drop all capabilities, add only what's needed
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx
# add a specific capability
docker run --cap-add=SYS_PTRACE myapp
# common capabilities to drop (rarely needed by apps)
# CHOWN, DAC_OVERRIDE, FOWNER, KILL, NET_RAW, SETGID, SETUID
# NET_RAW is often safe to drop (prevents raw sockets/ping)
docker run --cap-drop=NET_RAW myapp
# privileged = all capabilities + host access (AVOID)
docker run --privileged myapp # DANGEROUS
# inspect capabilities
docker inspect --format '{{json .HostConfig.CapAdd}}' <container>Seccomp & AppArmor
Seccomp filters which syscalls a container can make — the default profile blocks ~44 dangerous syscalls. AppArmor confines file and capability access. no-new-privileges prevents setuid binaries from granting new privileges — a cheap, high-value hardening for any container.
# default seccomp profile blocks dangerous syscalls
docker run --security-opt seccomp=default.json nginx
# custom seccomp profile
docker run --security-opt seccomp=/path/to/profile.json nginx
# disable seccomp (NOT recommended)
docker run --security-opt seccomp=unconfined nginx
# AppArmor profile
docker run --security-opt apparmor=docker-default nginx
# custom AppArmor profile
docker run --security-opt apparmor=myprofile nginx
# no-new-privileges (prevents setuid escalation)
docker run --security-opt no-new-privileges nginxImage Scanning
Scan images for known vulnerabilities (CVEs) before pushing. docker scout (formerly snyk-based docker scan) is built-in. Trivy and Grype are popular alternatives. Integrate scanning into CI to block images with high-severity CVEs. Re-scan base images periodically as new CVEs are disclosed.
# docker scout (built-in, replaces docker scan)
docker scout cves myapp:1.0
# scan a local image
docker scout cves --format json myapp:1.0
# compare two images
docker scout compare myapp:1.0 --to myapp:1.1
# Trivy (third-party, popular)
trivy image myapp:1.0
# Grype (third-party)
grype myapp:1.0
# scan a Dockerfile for bad practices
docker scout policy myapp --only-stream-default
# integrate scanning in CI
# - build image
# - scan
# - fail the pipeline on HIGH/CRITICAL CVEsDocker Content Trust & Hardening
Content Trust ensures you pull signed images. Harden the daemon: disable inter-container communication (icc=false), enable live-restore so containers survive daemon restarts, and enable user namespaces (userns-remap) to map container root to a non-root host UID for stronger isolation.
# enable content trust (image signing)
export DOCKER_CONTENT_TRUST=1
docker pull myapp:1.0 # refuses unsigned
# sign on push
docker push myapp:1.0 # signs with your key
# daemon hardening in /etc/docker/daemon.json
# {
# "userland-proxy": false,
# "live-restore": true,
# "no-new-privileges": true,
# "icc": false # disable inter-container communication on default bridge
# }
# restrict registry access
# "registry-mirrors": [],
# "insecure-registries": []
# audit the daemon
docker info --format '{{json .SecurityOptions}}'
# use user namespaces for host UID isolation
# /etc/docker/daemon.json: "userns-remap": "default"Logging & Monitoring
Viewing Logs
docker logs reads stdout/stderr captured by the logging driver. Apps should log to stdout — not files — to integrate with docker logs. --tail and --since limit output. Compose logs aggregates across services. The default json-file driver grows unbounded unless you cap it.
# follow logs
docker logs -f web
# last 100 lines
docker logs --tail 100 web
# since a timestamp
docker logs --since 30m web
# with timestamps
docker logs -t web
# a specific time range
docker logs --since 1h --until 30m web
# write logs to a file
docker logs web > web.log 2>&1
# logs for all containers (compose)
docker compose logs -f
docker compose logs -f web dbLog Drivers
The json-file driver grows unbounded without rotation — set max-size and max-file to prevent disk exhaustion. For production, ship logs to a central system (fluentd, splunk, awslogs, gelf). Set a global default in daemon.json so every container gets rotation by default.
# default: json-file
docker run -d --log-driver=json-file nginx
# cap log size and rotate (json-file)
docker run -d \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
nginx
# syslog
docker run -d --log-driver=syslog nginx
# journald
docker run -d --log-driver=journald nginx
# fluentd / splunk / gelf / awslogs
docker run -d --log-driver=fluentd nginx
# global default in /etc/docker/daemon.json
# { "log-driver": "json-file",
# "log-opts": { "max-size": "10m", "max-file": "3" } }Log Tagging & Attributes
The tag template customizes log identifiers — using {{.Name}} produces readable container names instead of truncated IDs. For fluentd/splunk, labels and env vars are attached to log records for filtering. Set daemon-level tag templates so all containers are tagged consistently.
# tag logs with container name instead of ID
docker run -d \
--log-driver=json-file \
--log-opt tag="{{.Name}}" \
nginx
# tag with image name and tag
--log-opt tag="{{.ImageName}}/{{.Name}}"
# fluentd: add labels and attributes
docker run -d \
--log-driver=fluentd \
--log-opt fluentd-address=localhost:24224 \
--log-opt tag="docker.{{.Name}}" \
--log-opt labels=app,env \
--label app=web --label env=prod \
nginx
# syslog with facility
docker run -d --log-driver=syslog \
--log-opt syslog-facility=daemon nginxStats & Resource Monitoring
stats streams CPU, memory, network, and block IO. docker events is a real-time stream of daemon events — useful for alerting scripts. docker top shows processes inside a container; docker diff shows filesystem changes (A=added, C=changed, D=deleted) since the image was created.
# live stats for all containers
docker stats
# specific container, one snapshot
docker stats --no-stream web
# custom format
docker stats --format "table {{.Name}} {{.CPUPerc}} {{.MemUsage}} {{.NetIO}}"
# events stream (start, stop, die, etc.)
docker events
# filter events
docker events --filter type=container --filter event=die
# container process list (like top)
docker top web
# container file changes since creation
docker diff webDocker Events
docker events is a real-time stream of lifecycle events: create, start, stop, pause, die, destroy, plus image and network events. Filter by type, event, or container. It's the basis for alerting scripts that react to container deaths or auto-restart failing services.
# stream all events
docker events
# container events only
docker events --filter type=container
# specific event types
docker events --filter event=start --filter event=die
# events for a specific container
docker events --filter container=web
# events since a timestamp
docker events --since 1h
# format output
docker events --format '{{.Time}} {{.Action}} {{.Actor.Attributes.name}}'
# pipe to a monitoring script
docker events --filter event=die | while read event; do
echo "Container died: $event"
donePrometheus & Grafana Stack
cAdvisor exposes per-container metrics (CPU, memory, network, filesystem) in Prometheus format. Prometheus scrapes and stores them; Grafana visualizes. This is the de-facto open-source monitoring stack for Docker. Add node-exporter for host metrics and alertmanager for alerting.
# run cAdvisor (container metrics exporter)
docker run -d --name cadvisor \
-v /:/rootfs:ro -v /var/run:/var/run:ro \
-v /sys:/sys:ro -v /var/lib/docker/:/var/lib/docker:ro \
-p 8080:8080 gcr.io/cadvisor/cadvisor
# run Prometheus
docker run -d -p 9090:9090 \
-v ./prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus
# run Grafana
docker run -d -p 3000:3000 grafana/grafana
# scrape cAdvisor in prometheus.yml
# scrape_configs:
# - job_name: cadvisor
# static_configs:
# - targets: ['cadvisor:8080']
# view dashboards in Grafana at http://localhost:3000Docker Swarm
Initializing Swarm
swarm init turns a Docker host into a Swarm manager. The join token lets workers and additional managers join. Keep manager tokens secret — they can control the cluster. Run an odd number of managers (3 or 5) for Raft consensus tolerance. Workers run tasks; managers schedule them.
# initialize a Swarm (this node becomes a manager)
docker swarm init --advertise-addr 10.0.0.5
# get a join token for workers
docker swarm join-token worker
# get a join token for managers
docker swarm join-token manager
# on a worker node
docker swarm join --token SWMTKN-... 10.0.0.5:2377
# list nodes
docker node ls
# promote a worker to manager
docker node promote <node>
# demote a manager
docker node demote <node>
# leave the Swarm (worker)
docker swarm leaveDeploying Services
Services are the Swarm abstraction over containers. A replicated service runs N identical tasks; a global service runs one task per node. update rolls out a new image with zero downtime by default. scale changes the replica count live. Swarm handles rescheduling if a node fails.
# deploy a service
docker service create --name web -p 80:80 nginx
# with replicas
docker service create --name web --replicas 3 -p 80:80 nginx
# with a constraint (e.g. only on worker nodes)
docker service create --name web --replicas 3 \
--constraint node.role==worker nginx
# with a global service (one per node)
docker service create --name log --mode global fluentd
# update the image
docker service update --image nginx:1.25 web
# scale a service
docker service scale web=5
# remove a service
docker service rm webScaling & Updates
scale changes replica count instantly. update performs rolling updates — parallelism controls how many tasks update at once, delay spaces them out, and failure-action=rollback auto-reverts on errors. Swarm keeps old tasks running until new ones are healthy, enabling zero-downtime deploys.
# scale up
docker service scale web=10
# scale multiple services
docker service scale web=5 worker=8
# rolling update
docker service update --image myapp:2.0 web
# update with parallelism and delay
docker service update \
--image myapp:2.0 \
--update-parallelism 2 \
--update-delay 30s \
--update-failure-action rollback \
web
# environment or port update without redeploy
docker service update --env-add DEBUG=true web
docker service update --publish-rm 80:80 --publish-add 8080:80 web
# rollback to previous version
docker service rollback webStack Deploy
A stack is a Compose file deployed to Swarm. The deploy: key configures replicas, update/rollback policies, placement constraints, and resources — all Swarm-specific. docker stack deploy applies the spec declaratively. Stacks group related services, networks, and secrets.
# docker-compose.yml as a Swarm stack
docker stack deploy -c docker-compose.yml mystack
# the compose file uses deploy: for Swarm config
services:
web:
image: nginx
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
placement:
constraints: [node.role == worker]
ports:
- "80:80"
# list stacks and services
docker stack ls
docker stack services mystack
# remove a stack
docker stack rm mystackNode Management
Drain a node before maintenance to gracefully reschedule its tasks elsewhere. Active/pause/drain control scheduling. Labels enable placement constraints (e.g. only run DB on nodes labeled tier=db). Remove a node only after it has left the Swarm with docker swarm leave.
# list nodes
docker node ls
# node details (status, role, labels, resources)
docker node inspect <node>
# drain a node (reschedule tasks off it, for maintenance)
docker node update --availability drain <node>
# activate a drained node
docker node update --availability active <node>
# pause a node (no new tasks, existing stay)
docker node update --availability pause <node>
# add a label (for placement constraints)
docker node update --label-add tier=db <node>
# remove a node from the Swarm
docker node rm <node>
# (node must have left via 'docker swarm leave' first)Secrets & Configs
Secrets are encrypted at rest and in transit, mounted as files under /run/secrets, and never written to image layers or env vars. They're immutable — to rotate, create a new secret and update the service. Configs are similar but for non-sensitive data like config files. Both are Swarm-only.
# create a secret from a file
docker secret create db_password ./db_password.txt
# create from stdin
echo "supersecret" | docker secret create db_password -
# use in a service
docker service create --name app --secret db_password myapp
# read in the app
# cat /run/secrets/db_password
# update a secret (immutable — create a new one)
echo "newsecret" | docker secret create db_password_v2 -
docker service update --secret-rm db_password --secret-add source=db_password_v2 app
# configs (non-sensitive, e.g. nginx.conf)
echo "server {...}" | docker config create nginx_conf -
docker service create --config nginx_conf nginx
# list and remove
docker secret ls
docker config lsSnippets de Docker relacionados
Copy-paste ready code for common tasks.
Run a Container
Run, list, stop, and remove Docker containers.
Writing a Dockerfile
Build an image from a Dockerfile with multi-instruction layers.
Image Management
Pull, list, tag, push, and prune images.
Volumes & Bind Mounts
Persist data with named volumes, anonymous volumes, and bind mounts.
Networking
Create networks, attach containers, and expose ports.
Docker Compose
Define and run multi-container apps with compose.
Multi-Stage Build
Build artifacts in one stage and copy them into a slim final image.
Exec & Logs
Run commands inside running containers and inspect logs.
Was this helpful?