Skip to content

Docker 速查表

在容器中开发、交付和运行应用程序的平台。

01

入门

基本命令

docker run 创建并启动容器。-d 在后台运行,-p 将主机端口映射到容器端口。pull 从镜像仓库(默认 Docker Hub)下载镜像。

docker
# 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 images

容器生命周期

create + start 就是 run 在底层所做的操作。stop 发送 SIGTERM 进行优雅关闭;kill 立即发送 SIGKILL。pause 使用 cgroup 冻结器——适用于在不停止进程的情况下进行快照。

docker
# 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 web

镜像基础

镜像是只读的层。rmi 在容器仍引用该镜像时会失败——先移除容器。history 显示创建每一层的指令及其大小,对优化构建很有用。

docker
# 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:latest

系统信息

docker info 显示守护进程配置:存储驱动、运行时、镜像仓库、Swarm 状态。system df 显示可回收空间——悬空镜像和已停止的容器会浪费磁盘。--format 标志使用 Go 模板进行结构化输出。

docker
# 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}}'

清理与裁剪

prune 安全地移除已停止/悬空的资源。system prune -a 还会移除未被任何容器引用的镜像。添加 --volumes 会删除未被任何容器使用的卷——不可逆的数据丢失,在生产环境中谨慎使用。

docker
# 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 --volumes

获取帮助

Docker 将命令分组到管理命名空间(docker image、docker container、docker network、docker volume)下。旧形式(docker rmi)和新形式(docker image rm)都可以使用。在任何子命令上使用 --help 可查看标志和用法。

docker
# 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/
02

镜像管理

拉取镜像

标签是可变的标签——同一个标签随时间可以指向不同的镜像。对于可重现的部署,通过摘要(@sha256:...)固定。通过 --platform 拉取可获取不同架构的镜像,适用于在 x86 主机上构建 ARM 镜像。

docker
# 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 nginx

构建镜像

最后的 . 是构建上下文——发送给守护进程的文件。用 .dockerignore 保持上下文小以加速构建。--no-cache 在调试时强制刷新层。BuildKit 启用多阶段缓存、SSH 挂载和密钥挂载。

docker
# 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 .

标记镜像

标签是指向同一镜像 ID 的别名。常见的工作流是用版本标签构建,然后添加 latest 和主版本标签。用镜像仓库 URL 标记可为推送到该仓库做准备。

docker
# 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:pinned

推送镜像

推送前必须用镜像仓库目的地标记镜像。push 只上传镜像仓库中尚不存在的层。对于使用自签名证书的私有仓库,在 /etc/docker/daemon.json 中配置 insecure-registries。

docker
# 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 logout

检查镜像

inspect 返回详细的 JSON:架构、操作系统、层、环境变量、入口点、配置。history 显示每层的命令和大小——镜像优化的基础。manifest inspect 无需拉取即可检查远程仓库。

docker
# 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:latest

保存与加载镜像

save/load 保留镜像层和历史——是离线传输镜像的正确选择。export/import 展平为单层,丢失历史。对于在线传输,优先使用仓库推送/拉取而非 tar 文件。

docker
# 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.gz
03

容器管理

运行容器

-it 组合 -i(保持 stdin 打开)和 -t(分配 tty)用于交互式会话。--rm 在退出后清理——非常适合一次性命令。--restart unless-stopped 在重启后存活但尊重手动停止。

docker
# 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 nginx

执行命令(exec)

exec 在已运行的容器内运行新进程——原始入口点继续运行。用于调试、管理任务或运行一次性脚本。默认用户是镜像的 USER;用 -u 覆盖。

docker
# 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/marker

查看日志

logs 显示容器日志驱动捕获的 stdout/stderr。应用程序应记录到 stdout 而非文件,以与 docker logs 集成。--since/--until 接受 RFC3339 时间戳或 Go 持续时间(30m、2h)。

docker
# 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 web

容器检查

inspect 返回涵盖配置、状态、网络和挂载的丰富 JSON。Go 模板提取特定字段——对脚本编写非常有用。LogPath 揭示 json-file 驱动在主机上存储日志的位置。

docker
# 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}}' web

启动、停止与重启

start 以原始配置恢复已停止的容器。update 修改运行中容器的约束和重启策略而无需重新创建——适用于在生产环境中收紧资源限制。

docker
# 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 web

复制文件

cp 在两个方向都可用,也可用于已停止的容器——便于注入热修复或提取日志。它不适用于高频同步;开发工作流请使用绑定挂载或数据卷。tar+exec 技巧可高效传输大型目录树。

docker
# 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.gz
04

Dockerfile 基础

FROM — 基础镜像

FROM 是第一条指令,定义基础层。选择最小基础镜像(alpine、distroless、scratch)以减少攻击面和大小。通过摘要固定可保证可重现构建,因为标签可能变化。

docker
# 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 final

RUN — 执行命令

每个 RUN 创建一层。用 && 组合相关命令并在同一层清理以保持镜像小。exec 形式(JSON 数组)不调用 shell,因此没有变量展开或链接——但它能正确处理信号。

docker
# 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.gz

COPY 与 ADD

COPY 是本地文件的首选——它是显式且可预测的。ADD 增加了魔法:URL 获取和 tar/zip 压缩包的自动解压。这种魔法可能让您意外;仅在需要解压时才使用 ADD。两者都创建一层。

docker
# 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 设置后续指令和容器默认的工作目录。如果目录不存在则创建。始终优先使用 WORKDIR 而非 RUN 中的 cd——cd 不会跨层持久化。使用绝对路径更清晰。

docker
# 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 RUN

用户(USER)

USER 为 RUN、CMD 和 ENTRYPOINT 设置用户(和可选的组)。以非 root 运行是关键的安全最佳实践。用户必须在镜像中存在。数字 UID 跨镜像可移植,避免名称查找问题。

docker
# 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 减少发送给守护进程的构建上下文——更快的构建、更小的上下文,并防止 .env 等密钥泄露到镜像层中。它使用与 .gitignore 类似的语法。始终排除在构建期间重新创建的 node_modules 和构建产物。

docker
# .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.db
05

Dockerfile 进阶

CMD 与 ENTRYPOINT

ENTRYPOINT 定义可执行文件;CMD 提供可覆盖的默认参数。使用 exec 形式(JSON 数组)使进程直接接收信号——shell 形式将命令包装在 /bin/sh -c 中,破坏优雅关闭。常见模式:ENTRYPOINT 脚本 + CMD 默认参数。

docker
# 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 — 环境变量

ENV 创建镜像级变量,在构建和运行时可见。使用 ARG 仅用于构建时值,以避免将配置泄露到最终镜像。在 exec 形式的 CMD/ENTRYPOINT 中,${VAR} 不会被 shell 展开——用 sh -c 包装以展开。

docker
# 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 — 构建参数

ARG 值仅在声明它的阶段构建期间存在。它们不会持久化到运行时容器——使用 ENV 将值传递到运行时。在 FROM 之前声明的 ARG 是'全局的',但必须在使用它的每个阶段中重新声明。

docker
# 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 是文档说明——它不发布端口。发布在运行时用 -p(主机:容器)或在 Compose 中用 ports: 进行。EXPOSE 帮助工具了解预期端口,并在遗留网络中启用自动链接。

docker
# 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}}' myapp

LABEL 与元数据

LABEL 为镜像附加键值对元数据。采用 OCI org.opencontainers.image.* 标签以与镜像仓库和工具互操作。标签对跟踪版本、源代码、许可和 CI 构建信息很有用。

docker
# 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]>

Dockerfile 中的 VOLUME

VOLUME 声明挂载点;Docker 在首次运行时创建匿名卷,除非您绑定主机路径。它表示该目录包含有状态数据。一个陷阱:在 COPY 之后声明 VOLUME 可能会用空卷覆盖您复制的文件。

docker
# 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 /config
06

多阶段构建

基本多阶段

多阶段构建只将最终产物复制到运行时镜像——构建工具、源代码和开发依赖项留在构建器阶段。结果:一个小巧、安全的运行时镜像。用 AS 命名阶段以便在 COPY --from 中引用。

docker
# 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;"]

选择构建阶段

--target 只构建到命名阶段,跳过后续阶段。这使单个 Dockerfile 可用于开发、测试和生产:CI 中 target 'test',生产中 'final'。BuildKit 自动并行化独立的阶段。

docker
# 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 .

精简运行时镜像

对于静态二进制文件(Go、Rust),scratch 生成尽可能小的镜像,攻击面为零。添加 CA 证书以支持 HTTPS。distroless 提供带有根 CA 和非 root 用户的最小基础,非常适合动态语言或需要无 shell 运行时的情况。

docker
# 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"]

编译型语言的构建器模式

在完整的 SDK 镜像中编译,然后将构件复制到仅运行时镜像(JRE)中。这大幅缩小镜像(JDK 约 600MB vs JRE 约 200MB)并从生产中移除构建工具。在 COPY 之前预取依赖项可改善层缓存。

docker
# 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 runtime

从外部镜像复制

COPY --from 可以从任何镜像拉取——适用于从多个基础组装工具而无需构建阶段。阶段也可以通过索引(从 0 开始)引用,但用 AS 命名更清晰。通过摘要固定外部镜像以确保可重现性。

docker
# 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 /app

BuildKit 特性

BuildKit 解锁缓存挂载(npm/pip 缓存在构建之间持久化而不膨胀镜像)、密钥挂载(凭证永远不会接触层)、用于私有仓库的 SSH 转发,以及更简洁的多行 RUN heredoc 语法。添加 # syntax 指令以固定 Dockerfile 前端版本。

docker
# 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
EOF
07

数据卷

创建与使用数据卷

命名卷由 Docker 管理,是持久化数据的推荐方式。它们在容器移除后存活,并独立于主机文件系统布局。:ro 后缀以只读方式挂载。inspect 揭示 /var/lib/docker/volumes 下的磁盘路径。

docker
# 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 mydata

列出与检查数据卷

ls 列出命名卷;inspect 显示驱动和挂载点。要查找哪些容器使用某个卷,按卷过滤 ps。卷位于 Linux 主机上的 /var/lib/docker/volumes/<name>/_data 下——通过 docker exec 访问,而非直接主机编辑。

docker
# 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 mydata

数据卷驱动

local 驱动通过挂载选项支持 NFS、块设备和 tmpfs。对于云存储(EBS、Azure Disk),使用插件驱动。驱动抽象存储,使容器在主机之间移动而无需更改 run 命令——对 Swarm/Kubernetes 至关重要。

docker
# 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 ebsvol

备份数据卷

通过在临时容器中同时挂载卷和主机目录,然后 tar 内容来备份卷。恢复则反向操作。此模式适用于任何卷驱动,是自动备份脚本的基础。

docker
# 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 /data

删除数据卷

容器引用卷时无法移除(先移除容器)。prune 只移除未使用的卷。卷删除不可逆——数据将永久丢失。在生产环境中裁剪前始终备份卷。

docker
# 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 mydata

在容器间共享数据卷

多个容器可以挂载同一个卷以共享状态。--volumes-from 克隆另一个容器的卷挂载——适用于 sidecar 模式。为只读消费者添加 :ro。协调并发写入以避免损坏;某些应用需要锁。

docker
# 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/msg
08

绑定挂载

基本绑定挂载

绑定挂载将主机路径直接映射到容器中——非常适合主机编辑即时反映的实时开发。使用绝对主机路径。:ro 后缀防止容器写回主机。现代语法使用 --mount 更清晰。

docker
# 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:/app

只读绑定挂载

只读挂载保护主机配置不被容器写入——对安全至关重要。--read-only 使整个根文件系统不可变,需要为 /tmp 和日志目录显式可写挂载。这加固了容器以防止被攻破。

docker
# 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 fail

开发工作流

挂载源代码使主机编辑在容器中即时反映。'node_modules 的匿名卷'技巧防止主机的空 node_modules 遮蔽镜像中已安装的依赖。在 Windows/Mac 上可能需要轮询以实现文件监视可靠性。

docker
# 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 start

SELinux 与 AppArmor 标签

在启用 SELinux 的主机(RHEL/CentOS/Fedora)上,绑定挂载需要 :z 或 :Z 后缀来重新标记主机路径,以便容器可以访问它。:z 在容器之间共享路径;:Z 使其私有。忘记后缀会导致'权限被拒绝'错误。

docker
# 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 myapp

挂载 vs 数据卷 vs tmpfs

数据卷:Docker 管理、可移植,最适合持久数据。绑定挂载:主机路径,最适合开发和配置注入。tmpfs:内存中,最适合密钥和临时缓存。--mount 语法比 -v 更显式、更具自文档性。

docker
# 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 nginx

Compose 中的绑定挂载

在 Compose 中,相对路径(./html)创建绑定挂载,而引用 volumes: 部分的名称(logs)创建命名卷。裸路径(/cache)创建匿名卷。长格式语法使 read_only 等选项更明确。

docker
# 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: true
09

网络

桥接网络(默认)

默认桥接网络缺少自动 DNS——容器必须使用 IP。自定义桥接网络提供基于 DNS 的服务发现:容器通过名称相互解析。这就是 Compose 自动为每个项目创建自定义网络的原因。

docker
# 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 apps

主机网络

主机网络移除网络命名空间——容器共享主机的接口和端口。它提供最佳性能但牺牲隔离和端口映射。仅在 Linux 上可用;在 Mac/Windows 上,它映射到 VM 网络而非主机。

docker
# 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 myapp

覆盖网络(Swarm)

覆盖网络跨越多个 Swarm 节点,通过内置 DNS 实现安全的跨主机通信。--attachable 允许独立容器加入覆盖网络(适用于调试)。加密保护节点间的流量;对敏感工作负载启用它。

docker
# 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 container

自定义网络

自定义网络让您控制子网、网关和隔离。--internal 阻止出站互联网访问。macvlan 给容器在物理 LAN 上的真实存在(每个都有自己的 MAC)——适用于遗留应用,但默认会破坏主机到容器的通信。

docker
# 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 mymacvlan

容器 DNS

Docker 的嵌入式 DNS(127.0.0.11)在用户定义的网络上解析容器名称。默认桥接没有 DNS。--dns 添加外部解析器;--add-host 注入静态条目。使用服务名称(而非 IP)进行容器间通信,以便重新部署不会破坏 DNS。

docker
# 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 connect/disconnect 允许将运行中的容器附加到额外网络而无需重启——适用于 sidecar。inspect 显示连接的容器、子网和驱动。prune 移除未被任何容器使用的网络。

docker
# 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 prune
10

端口映射

基本端口映射

-p 主机:容器 将主机端口映射到容器端口。如果省略主机端口,Docker 会选择一个(用 docker port 查找)。每个主机端口只能映射到一个容器——对多个实例使用反向代理或不同的主机端口。

docker
# 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 nginx

绑定到特定 IP

用 IP 前缀控制端口绑定到哪个接口。127.0.0.1 使服务仅本地可用——对不想暴露的数据库至关重要。0.0.0.0(默认)绑定到所有接口。在生产环境中始终将数据库绑定到 localhost。

docker
# 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 端口

/udp 或 /sctp 后缀选择协议;TCP 是默认的。对于需要两者的服务(如 DNS),声明两个映射。端口范围在主机和容器之间 1:1 映射——适用于 WebRTC、RTP 或被动 FTP。确保防火墙规则与协议匹配。

docker
# 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"

端口范围

端口范围 1:1 映射(主机 8000-8005 到容器 8000-8005)。-P(大写)自动将每个 EXPOSE 端口发布到随机高端口——便于快速测试。当端口随机时使用 docker ps 查看实际映射。

docker
# 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}}'

暴露与发布

EXPOSE 记录意图;-p 实际发布。在 Compose 中,expose 仅与同一网络上的其他服务共享端口,而 ports 发布到主机。这种区别对数据库很重要:内部暴露,在生产环境中永远不发布到主机。

docker
# 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}}' nginx

随机端口映射

省略主机端口让 Docker 选择一个随机临时端口(默认 32768-60999)。使用 docker port 或 docker ps 发现它。这适用于在一台主机上扩展相同服务。在 Linux 上调整 /proc/sys/net/ipv4/ip_local_port_range 中的临时范围。

docker
# 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 80
11

环境变量

设置环境变量

-e(或 --env)在容器中设置环境变量。-e VAR(无值)从主机 shell 继承变量。对于敏感值,优先使用 --env-file 或 Docker 密钥,而非普通 -e,后者在 docker inspect 和进程列表中可见。

docker
# 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 mysql

环境变量文件

--env-file 从文件加载变量,保持 run 命令整洁并将密钥排除在 shell 历史之外。Compose 自动加载顶级 .env 用于 compose 文件本身的变量替换。注释(#)和空行被忽略;引号按字面保留。

docker
# .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 myapp

Dockerfile 中的环境变量

ENV 将变量固化到镜像中,在构建和运行时可见。ARG 仅用于构建时——如果运行时需要它则转换为 ENV。在运行时用 -e 覆盖固化的值。注意:环境变量在 docker inspect 中可见,所以不要在那里存储密钥。

docker
# 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=true

动态环境变量与替换

Shell 展开($(...) 或 ${VAR})在 Docker 看到值之前在主机上发生。在 Compose 中,${VAR} 从 .env 文件替换。在 exec 形式的 CMD 中,${VAR} 不会展开——用 sh -c 包装。传递主机 UID 以在绑定挂载中获得正确的文件所有权。

docker
# 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 环境变量

Compose 的 environment 接受列表或映射语法。env_file 将值加载到容器中;.env(顶级)为 compose 文件本身的 ${} 替换提供变量。对许多变量使用映射形式更易读。

docker
# 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=mydb

密钥管理

密钥作为文件挂载在 /run/secrets 下,从不作为环境变量,在 Swarm 中传输和静止时都加密。避免将密钥固化到镜像(ENV)或通过 -e 传递——两者都通过 docker inspect 和镜像层泄露。BuildKit 密钥挂载将构建时凭证排除在层之外。

docker
# 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 recommended
12

Docker Compose 基础

基本 docker-compose.yml

Compose 在单个 YAML 文件中定义多容器应用。services 描述每个容器;volumes 和 networks 在顶层声明。depends_on 控制启动顺序。docker compose up -d 在后台启动一切。

docker
# 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 -d

Compose 命令

up 创建并启动,down 停止并移除。给 down 加 -v 删除卷(数据丢失)。exec 在运行中的服务中运行命令;run 为一次性任务启动新容器。--scale 在同一主机上启动服务的多个副本。

docker
# 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 migrate

服务依赖

depends_on 控制启动顺序,但依赖项可能未'就绪'——使用 condition: service_healthy 配合 healthcheck 等待直到数据库接受连接。service_completed_successfully 等待初始化容器。应用仍应重试连接。

docker
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_successfully

Compose 中构建

build 定义如何从源代码构建镜像;image 标记结果。context 发送给构建器(保持小)。target 选择多阶段构建阶段。--build 在 up 时强制重建。构建参数在构建时传递值。

docker
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 --build

Compose 中的数据卷

相对路径(./html)是绑定挂载;在顶级 volumes: 下声明的名称(logs)是命名卷;裸路径(/cache)是匿名卷。命名卷可以指定驱动和选项(例如 NFS)。Compose 用项目名作为卷名前缀。

docker
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,rw

Compose 中的网络

Compose 为所有服务创建默认网络,但定义自定义网络可以让您隔离各层。web 桥接 frontend 和 backend。internal: true 阻止 backend 的互联网访问。同一网络上的服务通过 DNS 相互以服务名解析。

docker
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/16
13

Docker Compose 进阶

Compose 配置文件

配置文件分组可选服务。没有配置文件的服务总是启动;有配置文件的服务仅在其配置文件用 --profile 激活时启动。这使一个 compose 文件可用于开发、测试和生产:按环境选择性地启用配置文件。

docker
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 -d

Compose 覆盖

Compose 在 docker-compose.yml 之上自动加载 docker-compose.override.yml 用于本地开发定制。对于其他环境,按顺序传递多个 -f 文件——后面的文件覆盖前面的。这干净地将基础配置与环境特定调整分离。

docker
# 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 up

Compose 中的健康检查

Compose 中的健康检查镜像 Dockerfile 的 HEALTHCHECK。start_period 在启动期间给予宽限时间。将 healthcheck 与 depends_on: condition: service_healthy 结合,使服务等待依赖项真正就绪,而不仅仅是已启动。

docker
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_healthy

扩展与副本

--scale 在一台主机上启动 N 个相同容器——服务不得绑定固定主机端口(使用随机端口或无主机映射)。deploy.replicas 键由 Swarm 遵守;对于普通 Compose 使用 --scale。负载均衡器前置于扩展的服务。

docker
# 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 ports

Compose 配置与验证

config 验证并渲染合并的 compose 文件(包括覆盖和 .env 替换),对调试极有价值。--services、--images 和 --volumes 列出资源。在 CI 中使用 -q 以在 compose 文件格式错误时快速失败。

docker
# 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 -d

多个 Compose 文件

堆叠 compose 文件分离关注点:基础配置、环境覆盖、本地调整。COMPOSE_FILE(Linux/Mac 上用冒号分隔,Windows 上用分号)避免重复 -f。像 ports 这样的列表会被追加;像 environment 这样的映射按键合并。

docker
# 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 merged
14

Docker Registry

运行本地仓库

registry:2 镜像在端口 5000 上运行私有仓库。挂载卷以持久化镜像。对于远程主机或 HTTPS,配置 TLS 证书或在 /etc/docker/daemon.json 中将仓库添加到 insecure-registries。v2 API 让您通过 curl 列出仓库。

docker
# 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/list

推送到私有仓库

推送前必须用仓库主机标记镜像。对于使用自签名证书的仓库,将主机添加到 insecure-registries 并重启守护进程(生产环境不推荐——使用真实 CA 或内部 CA)。push -a 上传仓库中的所有标签。

docker
# 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 docker

从私有仓库拉取

通过指定完整镜像名从私有仓库拉取。登录凭证存储在 ~/.docker/config.json 中(为安全使用凭证助手)。对于 CI/CD,将凭证存储为密钥并在拉取前登录。通过摘要固定以实现可重现部署。

docker
# 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.json

仓库认证

用 htpasswd 基本认证或令牌(OAuth)服务器保护仓库。对于 TLS,挂载证书并监听 443。凭证助手(credsStore)将密码存储在 OS 钥匙串中而非明文 config.json——对共享或 CI 机器至关重要。

docker
# 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)

仓库 API

Registry HTTP API v2 让您编写脚本管理仓库。_catalog 列出仓库;manifest 描述镜像层。删除需要 REGISTRY_STORAGE_DELETE_ENABLED=true 并移除清单——blob 通过垃圾回收(registry garbage-collect)清理。

docker
# 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...

垃圾回收

删除清单会留下孤立 blob,直到垃圾回收运行。停止仓库(或使用 --dry-run)以避免收集正在使用的层。在生产环境中定期安排 GC 以回收已删除或被取代镜像的磁盘。config.yml 路径是仓库的配置文件。

docker
# 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/registry
15

Docker Hub

登录与登出

优先使用访问令牌而非密码——它们可撤销且有范围。--password-stdin 从 stdin 读取,避免密码出现在 shell 历史和进程列表中。配置凭证助手将凭证存储在 OS 钥匙串中而非明文 config.json。

docker
# 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 Token

搜索镜像

docker search 从 CLI 查询 Docker Hub,可按星标、官方状态和自动构建过滤。官方镜像(library/nginx)由 Docker 审查——优先用作基础。Web UI 提供按操作系统、架构和类别的更丰富过滤。

docker
# 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=nginx

推送到 Docker Hub

将镜像标记为 username/repo:tag,然后推送以发布到 Docker Hub。仓库在首次推送时自动创建(默认公开)。push -a 上传所有标签。免费账户获得一个私有仓库;付费计划提供更多。在 Hub Web UI 中设置仓库可见性。

docker
# 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)

自动构建

Docker Hub 的自动构建链接 git 仓库并在每次推送时构建。现代工作流更喜欢 GitHub Actions 配合 buildx 和 push,提供更多控制、缓存和多架构构建。Hub 自动构建对于简单项目仍是低工作量选择。

docker
# 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)

组织与团队

组织通过基于角色的访问在团队间共享仓库。团队获得每个仓库的权限(读、写、管理)。服务账户为 CI/CD 提供非个人令牌——撤销和轮换不影响个人。使用 orgname/repo 命名空间。

docker
# 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.0

镜像信任(DCT)

Docker Content Trust(DCT)使用 Notary 对镜像标签签名,确保您拉取的是发布者推送的镜像。通过 DOCKER_CONTENT_TRUST=1 启用。启用 DCT 后,只能拉取或推送已签名的镜像。仔细管理签名密钥——丢失它们可能锁定您的镜像。

docker
# 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 CLI
16

健康检查

基本健康检查

健康检查定期运行命令;退出 0 为健康,非零为不健康。容器以 'starting' 开始,在重试成功后变为 'healthy',或在重试失败后变为 'unhealthy'。start_period 在启动期间给予宽限时间。健康状态驱动 Swarm 重启决策。

docker
# 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 | none

Dockerfile 中的健康检查

Dockerfile 中的 HEALTHCHECK 将检查固化到镜像中。使用确认应用真正服务的最轻量检查——HTTP 用 curl -f 或 wget --spider,Postgres 用 pg_isready。HEALTHCHECK NONE 禁用继承的检查。过宽的检查会导致虚假的 'unhealthy' 抖动。

docker
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}}' myapp

健康检查选项

interval 设置检查频率;timeout 限定每次检查;retries 是连续失败阈值。start_period 在启动期间忽略失败,防止应用预热时出现虚假 'unhealthy'。调整这些以快速捕获真实停机而不在负载下抖动。

docker
# 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: 30s

检查健康状态

ps 在状态旁边显示 (healthy)/(unhealthy)。Health.Log 记录最后 5 次检查输出——对调试检查为何失败极有价值。通过 exec 手动运行检查命令以查看实际错误。按 health 过滤 ps 以快速找到病态容器。

docker
# 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 健康检查

Compose 健康检查镜像 Dockerfile。test 接受数组(exec 形式)或 CMD-SHELL 字符串。disable: true 移除继承的检查。将 healthcheck 与 depends_on: condition: service_healthy 配对,使服务等待依赖项真正就绪,而不仅仅是已启动。

docker
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: true

健康检查最佳实践

检查一个验证关键依赖项(DB、缓存)的真实 /healthz 端点——裸 TCP 检查可以在应用故障时通过。保持检查轻量快速;它们在每个 interval 运行。对慢启动运行时(JVM、Rails)使用 start_period,以免在启动期间被标记为不健康。

docker
# 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'))
17

资源限制

内存限制

--memory 设置硬限制;超过它触发 OOM kill。--memory-swap 限制内存+swap 总量;将其设置为等于内存可禁用 swap。--memory-reservation 是内核的软提示。对延迟敏感工作负载禁用 swap 以获得可预测性能。

docker
# 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 限制

--cpus 将 CPU 限制设置为核心数的一小部分(1.5 = 1.5 核)。--cpu-shares 是用于争用的相对权重,不是硬上限。--cpuset-cpus 将容器固定到特定核心,适用于性能隔离。Quota/period 是底层 cgroup 形式。

docker
# 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 nginx

内存与 Swap 行为

memory-swap 是内存+swap 的总量。将其设置为等于内存可禁用 swap。-1 允许无限 swap。Swappiness(0-100)控制内核交换倾向;较低的值有利于将页面保留在 RAM 中。内核内存限制是高级功能,很少需要。

docker
# 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 资源限制

deploy.resources.limits 设置硬上限;reservations 设置软保证(用于调度)。Swarm 原生遵守这些。对于普通 Compose(非 Swarm),在 v2 格式中使用顶级 mem_limit 和 cpus。Reservations 让容器即使在空闲时也声明最小值。

docker
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 与重启行为

当内存耗尽时,内核 OOM-killer 选择受害者。oom-kill-disable 保护容器但可能导致主机崩溃——仅与主机内存限制一起使用。oom-score-adj 偏向选择:正值=先被杀,负值=受保护。检查 OOMKilled 以诊断重启。

docker
# 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>

检查资源使用

docker stats 流式传输每个容器的实时 CPU、内存、网络和磁盘 IO——快速分诊的首选。--no-stream 为脚本提供单次快照。对于更深入的分析,抓取 /sys/fs/cgroup 或通过 cAdvisor 暴露 Prometheus 指标。比较使用量与限制以合理调整大小。

docker
# 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}}' web
18

安全

非 root 用户

以非 root 运行限制了应用被攻破时的爆炸半径。在 Dockerfile 中创建专用用户并用 USER 切换到它。如果必须绑定特权端口,使用反向代理(nginx)或授予 NET_BIND_SERVICE 能力,而不是以 root 运行。

docker
# 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 nonroot

只读根文件系统

只读根文件系统防止攻击者在被攻破时写入恶意软件或修改配置。配合 tmpfs 用于临时目录(/tmp、/cache)和卷用于持久数据。应用必须为此设计——某些框架写入意外路径需要 tmpfs 挂载。

docker
# 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 paths

Linux 能力(Capabilities)

默认情况下 Docker 授予有限的 Linux 能力集。丢弃 ALL 并只添加回应用所需的——最小权限原则。--privileged 授予所有能力和主机设备访问;完全避免它,除了受信任的容器运行时。默认丢弃 NET_RAW。

docker
# 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 过滤容器可以进行的系统调用——默认配置文件阻止约 44 个危险系统调用。AppArmor 限制文件和能力访问。no-new-privileges 防止 setuid 二进制文件授予新权限——对任何容器都是廉价、高价值的加固。

docker
# 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 nginx

镜像扫描

在推送前扫描镜像中的已知漏洞(CVE)。docker scout(前身为基于 snyk 的 docker scan)是内置的。Trivy 和 Grype 是流行的替代方案。将扫描集成到 CI 中以阻止高严重性 CVE 的镜像。随着新 CVE 披露,定期重新扫描基础镜像。

docker
# 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 CVEs

Docker 内容信任与加固

内容信任确保您拉取已签名镜像。加固守护进程:禁用容器间通信(icc=false),启用 live-restore 使容器在守护进程重启后存活,并启用用户命名空间(userns-remap)将容器 root 映射到非 root 主机 UID 以获得更强隔离。

docker
# 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"
19

日志与监控

查看日志

docker logs 读取日志驱动捕获的 stdout/stderr。应用应记录到 stdout——而非文件——以与 docker logs 集成。--tail 和 --since 限制输出。Compose logs 跨服务聚合。默认 json-file 驱动在未限制时会无限增长。

docker
# 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 db

日志驱动

json-file 驱动在无轮转时无限增长——设置 max-size 和 max-file 以防止磁盘耗尽。对于生产环境,将日志发送到中央系统(fluentd、splunk、awslogs、gelf)。在 daemon.json 中设置全局默认值,使每个容器默认获得轮转。

docker
# 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" } }

日志标签与属性

tag 模板自定义日志标识符——使用 {{.Name}} 生成可读的容器名称而非截断的 ID。对于 fluentd/splunk,标签和环境变量附加到日志记录以进行过滤。设置守护进程级 tag 模板,使所有容器一致地标记。

docker
# 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 nginx

统计与资源监控

stats 流式传输 CPU、内存、网络和块 IO。docker events 是守护进程事件的实时流——对告警脚本有用。docker top 显示容器内的进程;docker diff 显示自镜像创建以来的文件系统更改(A=添加,C=更改,D=删除)。

docker
# 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 web

Docker 事件

docker events 是生命周期事件的实时流:create、start、stop、pause、die、destroy,以及镜像和网络事件。按类型、事件或容器过滤。它是响应容器死亡或自动重启失败服务的告警脚本的基础。

docker
# 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"
done

Prometheus 与 Grafana 技术栈

cAdvisor 以 Prometheus 格式暴露每个容器的指标(CPU、内存、网络、文件系统)。Prometheus 抓取并存储它们;Grafana 可视化。这是 Docker 的事实上的开源监控技术栈。添加 node-exporter 获取主机指标,alertmanager 用于告警。

docker
# 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:3000
20

Docker Swarm

初始化 Swarm

swarm init 将 Docker 主机变为 Swarm 管理器。加入令牌让工作节点和额外管理器加入。保管好管理器令牌——它们可以控制集群。运行奇数个管理器(3 或 5 个)以实现 Raft 共识容忍。工作节点运行任务;管理器调度它们。

docker
# 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 leave

部署服务

服务是 Swarm 对容器的抽象。复制服务运行 N 个相同任务;全局服务在每个节点上运行一个任务。update 默认以零停机时间推出新镜像。scale 实时更改副本数。如果节点故障,Swarm 处理重新调度。

docker
# 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 web

扩展与更新

scale 实时更改副本数。update 执行滚动更新——parallelism 控制一次更新多少任务,delay 间隔它们,failure-action=rollback 在出错时自动回退。Swarm 保持旧任务运行直到新任务健康,实现零停机部署。

docker
# 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 web

堆栈部署

堆栈是部署到 Swarm 的 Compose 文件。deploy: 键配置副本、更新/回退策略、放置约束和资源——全部是 Swarm 特定的。docker stack deploy 声明式地应用规范。堆栈将相关服务、网络和密钥分组。

docker
# 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 mystack

节点管理

在维护前 drain 节点以将其任务优雅地重新调度到别处。Active/pause/drain 控制调度。标签启用放置约束(例如只在标记 tier=db 的节点上运行 DB)。仅在节点通过 docker swarm leave 离开 Swarm 后移除它。

docker
# 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)

密钥与配置

密钥在传输和静止时加密,作为文件挂载在 /run/secrets 下,从不写入镜像层或环境变量。它们是不可变的——要轮换,创建新密钥并更新服务。Config 类似但用于非敏感数据如配置文件。两者都是 Swarm 专有的。

docker
# 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 ls

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。