Docker 中级指南

本文面向已掌握 Docker 基础操作的读者,系统讲解 Dockerfile 指令全集、多阶段构建、Docker Compose 完整配置、构建最佳实践、数据管理、网络进阶、日志管理与资源限制。 FROM 是每个 Dockerfile 的起点,声明基础镜像。 语法 参数 scratch 基础镜像 scratch 是 Docker 内置的空镜像,不包含任何文件系统内容。适用于完全静态编译的二进制文件(如 Go 程序)。 ARG 与 FROM 联动 ARG 在 FROM 之前声明时,作用域仅限于 FROM 指令本身,不会传入后续构建阶段。若要在构建阶段内继续

分享

官方文档:https://docs.docker.com/
适用版本:Docker Engine 24+(2026-05-07 核实)

本文面向已掌握 Docker 基础操作的读者,系统讲解 Dockerfile 指令全集、多阶段构建、Docker Compose 完整配置、构建最佳实践、数据管理、网络进阶、日志管理与资源限制。


1. Dockerfile 完整指令参考

FROM

FROM 是每个 Dockerfile 的起点,声明基础镜像。

语法

FROM [--platform=<platform>] <image>[:tag|@digest] [AS name]

参数

参数 类型 默认值 说明
--platform string 宿主机平台 指定目标平台,如 linux/amd64linux/arm64windows/amd64
image string 必填 基础镜像名,可附带 registry 地址
:tag string latest 镜像标签,生产环境应指定确定版本
@digest string - 用 SHA256 摘要锁定精确版本,比 tag 更可靠
AS name string - 为该阶段命名,供多阶段构建中 COPY --from 引用

scratch 基础镜像

scratch 是 Docker 内置的空镜像,不包含任何文件系统内容。适用于完全静态编译的二进制文件(如 Go 程序)。

FROM scratch
COPY myapp /myapp
ENTRYPOINT ["/myapp"]

ARG 与 FROM 联动

ARGFROM 之前声明时,作用域仅限于 FROM 指令本身,不会传入后续构建阶段。若要在构建阶段内继续使用该变量,需在 FROM 之后重新声明一个同名 ARG(可不带默认值)。

ARG BASE_VERSION=3.11
FROM python:${BASE_VERSION}-slim

# 此处 BASE_VERSION 已失效,需重新声明
ARG BASE_VERSION
RUN echo "Building on Python ${BASE_VERSION}"

示例

# 锁定摘要,完全可重现
FROM node:20.11.0-alpine3.19@sha256:abc123... AS builder

# 多平台构建
FROM --platform=linux/arm64 debian:bookworm-slim AS arm-stage

RUN

执行命令并创建新镜像层。

两种形式

形式 语法 说明
shell 形式 RUN command 默认通过 /bin/sh -c 执行,支持 shell 变量展开、管道、通配符
exec 形式 RUN ["executable", "arg1", "arg2"] 直接执行,不启动 shell,无变量展开,JSON 数组格式,需用双引号

--mount 选项

RUN --mount 在构建时挂载文件系统,不会写入最终镜像层。

# 语法
RUN --mount=type=<TYPE>[,option=value,...] <command>
选项 类型/可选值 默认值 说明
type bind / cache / secret / ssh / tmpfs bind 挂载类型
target string - 挂载到容器内的路径
source string - 宿主机路径(bind)或缓存 ID(cache)
from string - 来源构建阶段名称(bind 类型)
readonly bool false 设为 true 则以只读方式挂载
id string target 路径 缓存键,type=cache 时用于区分不同缓存槽
sharing shared / private / locked shared 并发构建时的缓存共享策略
uid int 0 挂载点的属主用户 ID
gid int 0 挂载点的属主组 ID
mode octal 0755 挂载点权限(tmpfs 类型)
size bytes - tmpfs 大小限制
required bool false type=secret 时,若 secret 不存在是否报错

各类型典型用法:

# cache:复用包管理器缓存,加速后续构建
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# cache:pip 缓存
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

# secret:注入敏感信息,不留入镜像层
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm install

# bind:从另一构建阶段读取文件
RUN --mount=type=bind,from=builder,source=/app/dist,target=/tmp/dist \
    cp -r /tmp/dist /app/

# ssh:使用宿主机 SSH agent 克隆私有仓库
RUN --mount=type=ssh \
    git clone [email protected]:org/private-repo.git

--network 选项

说明
default 使用构建时的默认网络(通常有外网访问)
none 无网络访问,提升安全性与可重现性
host 使用宿主机网络命名空间
RUN --network=none pip install --no-index --find-links=/wheels -r requirements.txt

--security 选项

说明
sandbox 默认,受限沙箱环境
insecure 以提升权限运行(需要 --allow security.insecure 构建标志)

Heredoc 语法

BuildKit 支持在 RUN 中使用 heredoc,适合编写多行脚本而不用大量 &&

RUN <<EOF
set -e
apt-get update
apt-get install -y curl git
rm -rf /var/lib/apt/lists/*
EOF

最佳实践:合并 RUN 减少层数

# 不推荐:每条 RUN 产生一个独立层
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

# 推荐:合并为一层,并清理缓存
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        git \
    && rm -rf /var/lib/apt/lists/*

COPY

将文件从构建上下文或其他构建阶段复制到镜像中。

语法

COPY [OPTIONS] <src>... <dest>

参数

参数 类型 默认值 说明
--from string 构建上下文 指定来源为其他构建阶段名称或外部镜像名
--chown string - 设置文件属主,格式 user:group,仅 Linux
--chmod string - 设置文件权限,如 7550644
--link bool false 创建独立文件系统层,提升缓存复用率,即使前面的层变化也不会失效
--parents bool false 保留源文件的目录结构(从 Dockerfile 1.7+ 支持)
--exclude string - 排除匹配 glob 模式的文件,可多次使用
# 从另一阶段复制构建产物
COPY --from=builder /app/dist /app/dist

# 设置属主与权限
COPY --chown=node:node --chmod=644 package*.json ./

# 保留目录结构
COPY --parents src/components/**/*.tsx /app/

# 排除测试文件
COPY --exclude=**/*.test.ts src/ /app/src/

# 使用 --link 提升缓存复用(推荐)
COPY --link package.json package-lock.json ./

路径规则

  • src 支持 Go filepath 通配符(*?**
  • dest/ 结尾视为目录;不以 / 结尾时,若复制单文件则视为目标文件名
  • 目标目录不存在会自动创建

ADD

功能是 COPY 的超集,支持 URL 下载和自动解压 tar 包。

语法

ADD [OPTIONS] <src>... <dest>
ADD [OPTIONS] <git ref> <dir>

参数

参数 类型 默认值 说明
--chown string - COPY --chown
--chmod string - COPY --chmod
--link bool false COPY --link
--checksum string - 校验 URL 下载文件的 SHA256,格式 sha256:abc...
--keep-git-dir bool false 克隆 git 仓库时保留 .git 目录
--exclude string - 排除文件(BuildKit)

ADD vs COPY 对比

维度 COPY ADD
本地文件复制 支持 支持
URL 下载 不支持 支持
tar 自动解压 不支持 支持(.tar、.tar.gz、.tar.bz2 等)
安全风险 URL 来源需谨慎
推荐场景 绝大多数情况 需要解压 tar 或校验 URL 文件时
# 下载并校验文件
ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d \
    https://github.com/moby/moby/releases/download/v27.0.0/moby-27.0.0.tgz /tmp/moby.tgz

# 自动解压本地 tar(ADD 特有)
ADD rootfs.tar.xz /

ARG vs ENV

维度 ARG ENV
作用时机 仅构建时 构建时 + 运行时
是否保存到镜像 否(不会出现在运行时环境)
docker history 可见 是(值可被看到)
运行时覆盖方式 不支持 -e KEY=VALUE--env-file
构建时覆盖方式 --build-arg KEY=VALUE --build-arg(需 ARG 配合)
默认值 可在 Dockerfile 中设置 可在 Dockerfile 中设置
安全性 不适合存放密钥(history 可见) 同左,均不安全
典型用途 版本号、构建开关、平台参数 应用运行时配置
ARG NODE_VERSION=20
ARG BUILD_ENV=production

FROM node:${NODE_VERSION}-alpine

# FROM 之后 ARG 需重新声明才能使用
ARG BUILD_ENV
ENV NODE_ENV=${BUILD_ENV}

CMD vs ENTRYPOINT

ENTRYPOINT 定义容器的主执行程序,CMD 提供默认参数。两者均有 shell 形式和 exec 形式。

组合效果矩阵

ENTRYPOINT CMD 实际执行
未设置 ["cmd", "arg"] cmd arg
未设置 cmd arg(shell 形式) /bin/sh -c cmd arg
["ep"](exec) 未设置 ep
["ep"](exec) ["cmd", "arg"] ep cmd arg
["ep"](exec) cmd arg(shell 形式) ep /bin/sh -c cmd arg(通常非预期)
ep(shell 形式) 任意 /bin/sh -c ep(CMD 被忽略)
["ep", "arg1"](exec) ["arg2"] ep arg1 arg2

推荐模式

ENTRYPOINT 使用 exec 形式指定固定的可执行程序,CMD 提供可被 docker run 覆盖的默认参数:

ENTRYPOINT ["nginx", "-g", "daemon off;"]
CMD []

# 或
ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0", "--port", "8000"]

docker run myimage --port 9000 会用 --port 9000 替换 CMD,最终执行 python app.py --port 9000


HEALTHCHECK

声明容器健康检查命令,Docker daemon 会定期执行并更新容器状态(healthy / unhealthy / starting)。

语法

HEALTHCHECK [OPTIONS] CMD <command>
HEALTHCHECK NONE   # 禁用从父镜像继承的健康检查

参数

参数 类型 默认值 说明
--interval duration 30s 相邻两次检查的间隔时间
--timeout duration 30s 单次检查的超时时间,超时视为失败
--start-period duration 0s 容器启动宽限期,期间失败不计入 retries
--start-interval duration 5s 宽限期内的检查间隔(Docker 25.0+)
--retries int 3 连续失败达到此次数才判定为 unhealthy

CMD 的退出码含义:0 = healthy,1 = unhealthy,2 = reserved(不使用)。

# HTTP 接口健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# 无 curl 时用 wget
HEALTHCHECK --interval=15s --timeout=5s \
    CMD wget -qO- http://localhost:8080/ping | grep -q '"status":"ok"' || exit 1

# 检查进程是否存在
HEALTHCHECK --interval=10s \
    CMD pgrep nginx || exit 1

ONBUILD

ONBUILD 声明的指令不在当前镜像构建时执行,而是在以此镜像为 FROM 的子镜像构建时触发。

语法

ONBUILD <Dockerfile_INSTRUCTION>

典型场景:制作团队基础镜像,子项目只需 FROM 即可自动完成代码注入和依赖安装。

# 父镜像 Dockerfile(my-node-base)
FROM node:20-alpine
WORKDIR /app
ONBUILD COPY package*.json ./
ONBUILD RUN npm ci
ONBUILD COPY . .

# 子项目 Dockerfile(只需两行)
FROM my-node-base
CMD ["node", "server.js"]

注意:ONBUILD ONBUILD 不合法;ONBUILD FROMONBUILD MAINTAINER 也不允许。


SHELL

更改后续 shell 形式指令(RUNCMDENTRYPOINT)使用的默认 shell。

# Linux 切换到 bash(默认 /bin/sh)
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]
RUN echo "Now using bash"

# Windows 切换到 PowerShell
SHELL ["powershell", "-Command"]
RUN Write-Host "PowerShell"

STOPSIGNAL

设置 docker stop 时发送给容器主进程的信号。

# 默认
STOPSIGNAL SIGTERM

# 让 Nginx 优雅退出(SIGQUIT)
STOPSIGNAL SIGQUIT
常用信号 含义
SIGTERM 默认,请求程序终止
SIGQUIT 带 core dump 的退出(Nginx 用此进行优雅关闭)
SIGINT 等同 Ctrl+C
SIGUSR1 Nginx reload 配置

LABEL

为镜像添加键值对元数据,推荐遵循 OCI 标准标签。

OCI 标准标签

标签键 说明
org.opencontainers.image.title 镜像人类可读名称
org.opencontainers.image.description 简短描述
org.opencontainers.image.version 应用版本
org.opencontainers.image.created 创建时间(RFC 3339)
org.opencontainers.image.authors 作者信息
org.opencontainers.image.url 项目主页
org.opencontainers.image.source 源代码仓库 URL
org.opencontainers.image.licenses SPDX 许可证标识
org.opencontainers.image.revision Git commit SHA
LABEL \
    org.opencontainers.image.title="My App" \
    org.opencontainers.image.description="Production API server" \
    org.opencontainers.image.version="2.1.0" \
    org.opencontainers.image.created="2026-03-08T00:00:00Z" \
    org.opencontainers.image.authors="[email protected]" \
    org.opencontainers.image.source="https://github.com/org/repo" \
    org.opencontainers.image.licenses="MIT"

2. 多阶段构建(Multi-stage Builds)

为什么要用多阶段构建

构建工具链(编译器、构建依赖、测试框架)不应出现在生产镜像中,但传统单阶段 Dockerfile 很难将二者分离。多阶段构建允许在一个 Dockerfile 中定义多个阶段,最终镜像只包含最后阶段的内容。

典型收益:

场景 单阶段大小 多阶段大小
Node.js 应用 ~1.2 GB ~150 MB
Go 应用 ~800 MB ~10 MB
Java/Maven ~600 MB ~200 MB

基本语法

FROM base-image AS stage-name
# ... 构建步骤

FROM another-image AS final
COPY --from=stage-name /path/in/stage /path/in/final

Node.js 应用示例

# 阶段 1:安装依赖并构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN npm run build

# 阶段 2:生产运行时
FROM node:20-alpine AS production
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]

Go 应用示例(完全静态二进制)

FROM golang:1.22-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
    go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-w -s" -o myapp ./cmd/server

# 使用 scratch 空镜像
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /build/myapp /myapp
EXPOSE 8080
ENTRYPOINT ["/myapp"]

Java/Maven 应用示例

FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /build
COPY pom.xml ./
RUN --mount=type=cache,target=/root/.m2 \
    mvn dependency:go-offline -q
COPY src ./src
RUN --mount=type=cache,target=/root/.m2 \
    mvn package -DskipTests -q

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN addgroup -S spring && adduser -S spring -G spring
COPY --from=builder --chown=spring:spring /build/target/app.jar ./app.jar
USER spring
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

--target 指定构建到某个阶段

# 只构建到 builder 阶段,用于调试
docker build --target builder -t myapp:debug .

从外部镜像 COPY

--from 不仅可以引用本 Dockerfile 中的阶段,还可以引用任意外部镜像:

# 从官方 Nginx 镜像复制默认配置
COPY --from=nginx:1.25-alpine /etc/nginx/nginx.conf /etc/nginx/nginx.conf

# 从 distroless 镜像复制证书
COPY --from=gcr.io/distroless/base /etc/ssl/certs /etc/ssl/certs

并行阶段构建

BuildKit 会自动分析阶段依赖关系,将没有依赖关系的阶段并行构建,无需额外配置:

FROM node:20 AS frontend-builder
# ... 前端构建(与 backend-builder 并行)

FROM golang:1.22 AS backend-builder
# ... 后端构建(与 frontend-builder 并行)

FROM nginx:alpine AS final
COPY --from=frontend-builder /app/dist /usr/share/nginx/html
COPY --from=backend-builder /build/api /usr/local/bin/api

3. Docker Compose 完整配置参考

顶级键结构

# version 字段已废弃,现代 Compose 无需填写
services:      # 服务定义(必填)
networks:      # 网络定义
volumes:       # 卷定义
configs:       # 配置定义(Swarm/Compose)
secrets:       # 密钥定义(Swarm/Compose)

services 完整配置

镜像与构建

参数 类型 默认值 说明
image string - 使用的镜像名及标签
build string/object - 构建上下文路径(字符串)或构建配置(对象)
build.context string . 构建上下文目录
build.dockerfile string Dockerfile 指定 Dockerfile 文件名或路径
build.dockerfile_inline string - 内联 Dockerfile 内容
build.args map/list - 传递给 --build-arg 的构建参数
build.target string - 多阶段构建的目标阶段
build.cache_from list - 指定缓存来源镜像列表
build.cache_to list - 指定缓存输出目标
build.shm_size string/int - 构建容器的共享内存大小
build.labels map/list - 为构建镜像添加元数据
build.platforms list - 目标平台列表(多平台构建)
build.network string - 构建时使用的网络模式
build.no_cache bool false 禁用构建缓存
build.pull bool false 强制拉取最新基础镜像
pull_policy string missing always / never / missing / build / if_not_present
services:
  app:
    build:
      context: .
      dockerfile: docker/Dockerfile.prod
      args:
        - NODE_VERSION=20
        - BUILD_ENV=production
      target: production
      cache_from:
        - myregistry.io/myapp:cache
      shm_size: 128m
    pull_policy: always

容器行为

参数 类型 默认值 说明
container_name string 自动生成 自定义容器名,设置后不能横向扩展
command string/list - 覆盖镜像的 CMD
entrypoint string/list - 覆盖镜像的 ENTRYPOINT
restart string no 重启策略:no / always / on-failure[:N] / unless-stopped
stop_signal string SIGTERM 停止容器时发送的信号
stop_grace_period duration 10s 发送 SIGKILL 前等待容器优雅退出的时间
init bool false 使用 init 进程(tini),解决僵尸进程问题
tty bool false 分配伪终端(等同 docker run -t
stdin_open bool false 保持 stdin 开启(等同 docker run -i
read_only bool false 以只读文件系统运行容器
user string - 运行用户,格式 useruser:groupuid:gid
working_dir string - 容器内工作目录
platform string - 目标平台,如 linux/amd64
privileged bool false 以特权模式运行(拥有宿主机所有能力)
cap_add list - 添加 Linux capabilities
cap_drop list - 移除 Linux capabilities
security_opt list - 安全选项,如 no-new-privileges:true
sysctls map/list - 设置内核参数
ulimits object - 设置资源限制(nofile、nproc 等)
shm_size string/int - /dev/shm 大小
ipc string - IPC 命名空间:shareable / host / service:[name]
pid string - PID 命名空间:host
services:
  api:
    command: ["python", "-m", "uvicorn", "main:app", "--reload"]
    restart: unless-stopped
    stop_grace_period: 30s
    init: true
    read_only: true
    user: "1000:1000"
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE

端口与网络

参数 类型 默认值 说明
ports list - 端口映射,短语法或长语法
expose list - 声明容器监听的端口,不发布到宿主机
networks map/list default 连接到的网络列表
network_mode string bridge host / none / service:[name]
hostname string 容器 ID 容器主机名
domainname string - 容器域名
dns string/list - 自定义 DNS 服务器
dns_search string/list - DNS 搜索域
dns_opt list - DNS 选项
extra_hosts map/list - 添加到 /etc/hosts 的条目
mac_address string - 容器 MAC 地址

ports 长语法

ports:
  - target: 80          # 容器端口
    host_ip: 127.0.0.1  # 绑定的宿主机 IP
    published: "8080"   # 宿主机端口(字符串支持范围如 "8080-8090")
    protocol: tcp       # tcp 或 udp
    mode: host          # host(单机发布)或 ingress(Swarm 负载均衡)
services:
  web:
    ports:
      - "8080:80"          # 短语法
      - "127.0.0.1:9000:9000"  # 绑定本地地址
    networks:
      frontend:
        ipv4_address: 172.20.0.10
      backend:
    extra_hosts:
      - "db.internal:192.168.1.100"

环境变量

参数 类型 默认值 说明
environment map/list - 设置环境变量,list 格式 KEY=VALUE 或仅 KEY(从宿主机继承)
env_file string/list/object - 从文件加载环境变量
services:
  app:
    environment:
      DATABASE_URL: postgres://user:pass@db/mydb
      DEBUG: "false"
      # 从宿主机继承(不写值)
      AWS_ACCESS_KEY_ID:

    # env_file 支持 required/optional(Compose 2.24+)
    env_file:
      - path: .env
        required: true
      - path: .env.local
        required: false

.env 文件格式:每行 KEY=VALUE# 开头为注释,空行忽略。

卷挂载

短语法

volumes:
  - myvolume:/app/data          # 命名卷
  - ./config:/app/config:ro     # bind mount,只读
  - /tmp                        # 匿名卷(不推荐)

长语法参数

参数 类型 默认值 说明
type string volume volume / bind / tmpfs / npipe / cluster
source string - 卷名(volume 类型)或宿主机路径(bind 类型)
target string - 容器内挂载路径
read_only bool false 只读挂载
bind.propagation string rprivate bind 传播模式:shared / slave / private / rshared / rslave / rprivate
bind.create_host_path bool false 若宿主机路径不存在则自动创建
bind.selinux string - SELinux 标签:z(共享)/ Z(私有)
volume.nocopy bool false 不从容器复制初始数据到新卷
volume.subpath string - 挂载卷内的子路径
tmpfs.size string/int - tmpfs 大小限制,如 64m
tmpfs.mode int 1777 tmpfs 权限
services:
  app:
    volumes:
      - type: volume
        source: app-data
        target: /app/data
        volume:
          nocopy: true
      - type: bind
        source: ./nginx/conf.d
        target: /etc/nginx/conf.d
        read_only: true
        bind:
          selinux: z
      - type: tmpfs
        target: /tmp
        tmpfs:
          size: 64m

资源限制

参数 类型 默认值 说明
deploy.resources.limits.cpus string - CPU 上限,如 "0.5" 表示半核
deploy.resources.limits.memory string - 内存硬限制,如 512m1g
deploy.resources.limits.pids int - 进程数上限
deploy.resources.reservations.cpus string - CPU 软预留
deploy.resources.reservations.memory string - 内存软预留
services:
  api:
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512m
        reservations:
          cpus: "0.25"
          memory: 128m

注意:deploy.resourcesdocker compose up 中也生效(不需要 Swarm 模式)。

依赖与健康检查

depends_on 参数

参数 类型 默认值 说明
condition string service_started 依赖条件
restart bool false 依赖服务重启时是否重启当前服务
required bool true 依赖服务不存在时是否报错
condition 值 说明
service_started 依赖容器已启动(不保证就绪)
service_healthy 依赖容器 healthcheck 返回 healthy
service_completed_successfully 依赖容器正常退出(适用于 init 容器)

healthcheck 参数

参数 类型 默认值 说明
test string/list - 检查命令,list 形式:["CMD", "curl", "-f", "http://localhost"]
interval duration 30s 检查间隔
timeout duration 30s 超时时间
retries int 3 失败多少次判定 unhealthy
start_period duration 0s 启动宽限期
start_interval duration 5s 宽限期内检查间隔
disable bool false 禁用健康检查
services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  api:
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

日志配置

参数 类型 默认值 说明
logging.driver string json-file 日志驱动名称
logging.options map - 驱动特定的选项

json-file 驱动选项:

选项 类型 默认值 说明
max-size string 无限制 单个日志文件最大大小,如 10m
max-file string 1 保留的日志文件数量
compress string unset 是否压缩轮转的文件
labels string - 收集容器标签(逗号分隔)
env string - 收集环境变量(逗号分隔)
mode string blocking blocking / non-blocking
max-buffer-size string 1m non-blocking 模式的缓冲大小
services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"

profiles(条件启动服务)

profiles 允许将服务分组,使用 --profile 标志选择性启动:

services:
  app:
    image: myapp
    # 不设置 profiles,默认总是启动

  debug-tools:
    image: busybox
    profiles: [debug]    # 仅在 --profile debug 时启动

  docs:
    image: mkdocs
    profiles: [docs]

  loadtest:
    image: k6
    profiles: [test, perf]   # 多个 profile
# 启动默认服务 + debug 组
docker compose --profile debug up

# 启动所有 profile
docker compose --profile debug --profile docs up

extends(继承配置)

# base.yml
services:
  base-service:
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

# docker-compose.yml
services:
  api:
    extends:
      file: base.yml
      service: base-service
    image: myapi:latest
    ports:
      - "8000:8000"

networks 配置

参数 类型 默认值 说明
driver string bridge 网络驱动:bridge / overlay / host / none / macvlan
driver_opts map - 传给驱动的选项
external bool false 使用预先创建的外部网络,不由 Compose 管理
name string 项目名_网络名 自定义网络实际名称,避免项目名前缀
ipam object - IP 地址管理配置
ipam.driver string default IPAM 驱动
ipam.config list - 子网配置列表
ipam.config[].subnet string - 子网 CIDR,如 172.20.0.0/16
ipam.config[].gateway string - 网关地址
ipam.config[].ip_range string - 可分配 IP 范围
internal bool false 禁止容器访问外部网络
attachable bool false Swarm 外的独立容器可加入此网络
labels map/list - 网络元数据标签
enable_ipv6 bool false 启用 IPv6
networks:
  frontend:
    driver: bridge
    name: myapp_frontend    # 固定网络名,不受项目名影响
    ipam:
      config:
        - subnet: 172.20.0.0/24
          gateway: 172.20.0.1

  backend:
    driver: bridge
    internal: true          # 禁止外部访问

  existing-net:
    external: true          # 引用预存在的网络
    name: production_network

volumes 配置

参数 类型 默认值 说明
driver string local 卷驱动,local 或第三方插件
driver_opts map - 驱动选项(local 驱动支持 NFS 等)
external bool false 引用预存在的卷,不由 Compose 管理
name string 项目名_卷名 自定义卷实际名称
labels map/list - 卷元数据标签

local 驱动挂载 NFS:

driver_opts 键 说明
type 文件系统类型,如 nfsnfs4tmpfs
o 挂载选项,逗号分隔
device 设备路径或 NFS 服务器地址
volumes:
  app-data:
    driver: local
    name: myapp_data    # 不带项目名前缀

  nfs-data:
    driver: local
    driver_opts:
      type: nfs
      o: "addr=192.168.1.100,rw,nfsvers=4"
      device: ":/exports/data"

  external-volume:
    external: true
    name: production_uploads

secrets 配置

参数 类型 默认值 说明
file string - 从宿主机文件读取 secret 内容
external bool false 引用 Swarm 中已存在的 secret
name string - 自定义 secret 实际名称
secrets:
  db_password:
    file: ./secrets/db_password.txt

  api_key:
    external: true
    name: production_api_key

services:
  app:
    secrets:
      # 短语法
      - db_password
      # 长语法
      - source: api_key
        target: /run/secrets/api_key
        uid: "1000"
        gid: "1000"
        mode: 0400

Secret 默认挂载到容器的 /run/secrets/<secret_name>


configs 配置

configssecrets 类似,但用于非敏感配置文件。挂载权限默认为 0444(可读)。

configs:
  nginx_conf:
    file: ./nginx/nginx.conf

services:
  nginx:
    image: nginx:alpine
    configs:
      - source: nginx_conf
        target: /etc/nginx/nginx.conf
        mode: 0444

4. 镜像构建最佳实践

选择合适的基础镜像

基础镜像类型 典型大小 说明 适用场景
ubuntu:22.04 ~80 MB 完整 Ubuntu,工具齐全 开发、调试
debian:bookworm ~50 MB 标准 Debian 通用
debian:bookworm-slim ~30 MB 精简 Debian 生产
alpine:3.19 ~7 MB musl libc,极小 对体积敏感,注意 musl 兼容性
gcr.io/distroless/base ~20 MB 无 shell、无包管理器 安全优先的生产环境
scratch 0 MB 空镜像 静态编译的二进制

选择建议:

  • 优先选择官方镜像的 -slim 变体
  • Alpine 适合简单应用,但 C 扩展(如某些 Python 包)需要编译,可能踩坑
  • distroless 最安全,但调试困难(无 shell)

构建缓存优化

Docker 逐层检查,一旦某层缓存失效,之后所有层都重新构建。

缓存失效规则:

指令 缓存失效条件
FROM 基础镜像变化
COPY / ADD 源文件内容变化(checksum)
RUN 命令字符串变化,或前面的层失效
ARG 传入的值变化
ENV 值变化

正确顺序(以 Node.js 为例):

WORKDIR /app

# 先复制依赖声明文件(变化频率低)
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci

# 再复制源码(变化频率高)
COPY . .
RUN npm run build

若先 COPY . .npm ci,每次代码变动都会重新安装依赖。

使用 RUN --mount=type=cache 跨构建复用缓存:

# pip 缓存(跨构建持久化)
RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \
    pip install -r requirements.txt

# Go 模块缓存
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go build -o server ./cmd/server

使用 .dockerignore

.dockerignore 排除不需要进入构建上下文的文件,减少发送给 Docker daemon 的数据量,同时防止不必要的缓存失效:

# .dockerignore
.git
.gitignore
node_modules
dist
*.log
.env
.env.*
!.env.example
coverage/
.DS_Store
Thumbs.db

安全最佳实践

不以 root 运行:

# 创建非特权用户
RUN addgroup --system --gid 1001 appgroup && \
    adduser --system --uid 1001 --ingroup appgroup --no-create-home appuser

USER appuser

不在镜像中硬编码密钥:

# 错误做法
RUN pip install --extra-index-url https://user:[email protected]/simple/ mypackage

# 正确做法:使用 secret
RUN --mount=type=secret,id=pypi_token \
    PIP_EXTRA_INDEX_URL=https://$(cat /run/secrets/pypi_token)@private.pypi.io/simple/ \
    pip install mypackage

构建时传入:docker build --secret id=pypi_token,src=./pypi_token.txt .

使用固定版本 tag:

# 不推荐
FROM node:latest
FROM python:3

# 推荐
FROM node:20.11.0-alpine3.19
FROM python:3.11.8-slim-bookworm

5. 数据管理进阶

Volume 详细操作

创建 NFS 卷:

docker volume create \
    --driver local \
    --opt type=nfs \
    --opt o=addr=192.168.1.100,rw,nfsvers=4 \
    --opt device=:/exports/data \
    nfs-data

inspect 查看挂载点:

docker volume inspect myvolume
# 关注 Mountpoint 字段,通常为 /var/lib/docker/volumes/<name>/_data

卷备份与恢复:

# 备份:运行临时容器,将卷内容打包并输出到标准输出
docker run --rm \
    -v myvolume:/data:ro \
    -v $(pwd):/backup \
    alpine \
    tar czf /backup/myvolume-backup.tar.gz -C /data .

# 恢复:创建新卷并解压
docker volume create myvolume-restored
docker run --rm \
    -v myvolume-restored:/data \
    -v $(pwd):/backup:ro \
    alpine \
    sh -c "cd /data && tar xzf /backup/myvolume-backup.tar.gz"

匿名卷的问题:

# 每次 docker run 使用 -v /data 会创建新的匿名卷
docker run -v /data myimage   # 产生随机 ID 的卷

# 使用 docker volume ls 可以看到大量匿名卷
# 清理:docker volume prune(谨慎!)

应优先使用命名卷,便于管理和迁移。

Bind Mount 注意事项

SELinux/AppArmor 标签(仅 Linux):

后缀 含义
:z 重新标记内容为共享 SELinux 标签,多个容器可同时访问
:Z 重新标记为私有 SELinux 标签,仅当前容器可访问
docker run -v ./data:/app/data:z myimage

权限对齐:

若容器以 UID 1000 运行,宿主机目录需有对应权限:

# 宿主机
chown -R 1000:1000 ./app-data

# 或在 Dockerfile 中指定与宿主机一致的 UID
RUN useradd -u 1000 appuser

6. 网络进阶

自定义 bridge 网络(推荐方式)

默认 bridge 网络与自定义 bridge 网络的区别:

特性 默认 bridge 自定义 bridge
DNS 解析(容器名) 不支持(需 --link 支持(容器名直接解析)
网络隔离 所有容器共享 不同网络相互隔离
动态连接 不支持 docker network connect
配置灵活性 高(子网、MTU 等)
# 创建自定义 bridge 网络
docker network create \
    --driver bridge \
    --subnet 172.20.0.0/16 \
    --gateway 172.20.0.1 \
    --opt com.docker.network.bridge.name=br-myapp \
    mynet

# 容器加入自定义网络后可直接用容器名通信
docker run --name db --network mynet postgres:16
docker run --name api --network mynet myapi
# api 容器内可直接 ping db、连接 db:5432

网络类型完整说明

类型 说明 适用场景 限制
bridge 软件定义桥接,Docker 默认 单机多容器相互通信 仅单机
host 共享宿主机网络命名空间 高性能、端口直接监听宿主机 仅 Linux,无网络隔离
overlay 基于 VXLAN 的跨主机网络 Docker Swarm 多节点 需要 Swarm 或 key-value store
macvlan 容器拥有独立 MAC 和 IP 直连物理网络、遗留应用 需混杂模式,宿主机间通信受限
ipvlan 共享 MAC,独立 IP 物理接口数量受限时 类似 macvlan,更少 MAC 占用
none 仅 loopback,无外部连接 完全网络隔离的容器 无法与外界通信

容器间通信

# 同网络:用容器名直接通信(推荐)
docker exec api curl http://db:5432

# 跨网络连接
docker network connect backend api-container

# --link(已废弃,不推荐使用)
docker run --link db:database myapi

7. 日志管理

日志驱动对比

驱动 说明 持久化 性能
json-file 默认,JSON 格式本地文件,支持 docker logs
journald 写入 systemd journal
syslog 通过 syslog 协议发送 取决于配置
fluentd 发送到 Fluentd 聚合器 是(外部)
awslogs 发送到 AWS CloudWatch
splunk 发送到 Splunk HTTP 事件收集器
gelf Graylog 扩展日志格式 是(外部)
loki 发送到 Grafana Loki(插件) 是(外部)
none 不记录任何日志 最高

生产环境 json-file 推荐配置(防止磁盘写满):

// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3",
    "compress": "true"
  }
}

或在 docker run 中单独指定:

docker run \
    --log-driver json-file \
    --log-opt max-size=10m \
    --log-opt max-file=3 \
    myimage

8. 资源限制

内存限制

参数 说明 示例
--memory / -m 硬限制,容器使用内存上限 -m 512m
--memory-reservation 软限制,内存紧张时触发 --memory-reservation 256m
--memory-swap 内存 + swap 总上限;-1 表示无限 swap --memory-swap 1g
--memory-swappiness swap 使用倾向(0-100),0 尽量不用 swap --memory-swappiness 0
--oom-kill-disable 禁用 OOM Killer(危险,可能导致宿主机不稳定) --oom-kill-disable
--oom-score-adj 调整 OOM 优先级(-1000~1000) --oom-score-adj 500

OOM Killer 说明:

当容器内存超过 --memory 限制时,Linux OOM Killer 会终止容器内的进程。可通过 docker inspect 查看 OOMKilled 字段判断是否被 OOM 终止。

docker inspect --format='{{.State.OOMKilled}}' mycontainer

CPU 限制

参数 说明 示例
--cpus CPU 核心数上限(浮点数) --cpus 0.5(半核)
--cpu-shares 相对权重,默认 1024,仅在 CPU 竞争时生效 --cpu-shares 512
--cpuset-cpus 绑定到指定 CPU 核心 --cpuset-cpus "0,2""0-3"
--cpuset-mems 绑定到指定 NUMA 内存节点 --cpuset-mems "0"
--cpu-period CFS 调度周期(微秒,默认 100000) --cpu-period 100000
--cpu-quota 周期内可用 CPU 时间(微秒) --cpu-quota 50000(= 0.5 核)
# 限制最多使用 1.5 个 CPU 核心,绑定到 0、1 号核
docker run --cpus 1.5 --cpuset-cpus "0,1" myimage

Compose 中的资源限制示例

services:
  api:
    image: myapi
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 1g
          pids: 100
        reservations:
          cpus: "0.5"
          memory: 256m
    # 或使用旧式写法(Compose v2 兼容)
    mem_limit: 1g
    cpus: 2.0

9. 踩坑与注意事项

depends_on 只等启动,不等就绪

depends_on 的默认 condition: service_started 仅表示容器进程已启动,数据库可能尚未接受连接。必须配合 healthcheckcondition: service_healthy 使用,或在应用层实现重试逻辑。

Compose 网络名称带项目名前缀

默认情况下,Compose 创建的网络名为 <项目目录名>_default。若多个 Compose 项目需要共享网络,或希望固定网络名,在 networks 配置中设置 name 字段:

networks:
  default:
    name: myapp_network    # 固定名称,不受目录名影响

卷名默认带项目名前缀

与网络同理,卷实际名称为 <项目名>_<卷名>。引用另一 Compose 项目创建的卷时,使用 external: truename

volumes:
  uploads:
    external: true
    name: production_myapp_uploads

容器时区默认 UTC

容器内时区默认为 UTC,若应用依赖本地时区:

# 方法一:Dockerfile 中设置
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 方法二:挂载宿主机时区文件(Linux)
# volumes:
#   - /etc/localtime:/etc/localtime:ro
#   - /etc/timezone:/etc/timezone:ro
# Compose 中
environment:
  TZ: Asia/Shanghai

Linux bind mount 权限问题

容器以 root 写入 bind mount 的文件后,宿主机上该文件属主为 root,普通用户无法删除:

# 在容器内以非 root 运行,或手动修复权限
sudo chown -R $(id -u):$(id -g) ./data

CMD exec 形式无 shell 变量展开

# 错误:exec 形式不经过 shell,$PORT 不会被展开
CMD ["node", "server.js", "--port", "$PORT"]

# 正确:使用 shell 形式
CMD node server.js --port $PORT

# 或显式调用 sh -c
CMD ["sh", "-c", "node server.js --port $PORT"]

COPY --chown 被 volume mount 覆盖

COPY --chown=appuser:appgroup 在构建时设置文件属主,但若运行时以 bind mount 覆盖该路径,宿主机文件的属主生效,容器内权限设置失效。需确保宿主机目录有正确权限。

ENTRYPOINT shell 形式导致信号无法传递

# 错误:shell 形式下 PID 1 是 /bin/sh,不是应用进程
# docker stop 发送 SIGTERM 给 sh,应用无法收到信号,导致强制 kill
ENTRYPOINT python app.py

# 正确:exec 形式,应用作为 PID 1 直接接收信号
ENTRYPOINT ["python", "app.py"]

多行 ARG 作用域

每个 FROM 之后,之前的 ARG 失效,需要重新声明(无需重复设置默认值):

ARG VERSION=1.0
FROM alpine:${VERSION}
# 此处 VERSION 失效
ARG VERSION    # 重新声明,继承之前的默认值
RUN echo "Version: ${VERSION}"

最佳实践

多阶段构建分离 build 环境和 runtime 环境:将编译所需的工具链(gcc、npm、Maven)留在 builder 阶段,最终镜像只包含运行时产物,体积可缩小 5-10 倍,攻击面也大幅减小。

Docker Compose 中用具名 volume,不用匿名 volume:具名 volume(db_data:/var/lib/postgresql/data)可以被 docker volume ls 管理,匿名 volume 删除容器时难以追踪,容易造成孤儿 volume 堆积。

healthcheck 定义容器健康检查,让 Compose 感知服务真正就绪depends_on 只等容器启动,不等服务可用(如 PostgreSQL 初始化需要几秒)。配合 condition: service_healthy 可避免应用启动时数据库尚未就绪的竞态。

services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5
  app:
    depends_on:
      db:
        condition: service_healthy

网络隔离:为每个项目用独立的自定义网络:Compose 默认为项目创建独立网络,但多项目共用时容易混用。显式定义网络名称并用 internal: true 隔离不需要对外暴露的后端服务。


常见陷阱

陷阱:多阶段构建后镜像体积没有减小

现象: 使用了多阶段构建,但最终镜像大小和单阶段差不多。

原因: COPY --from=builder 复制了构建产物,但同时也把不必要的依赖或调试工具(如 node_modules 的 devDependencies)一并复制进来。

解决: 在 builder 阶段只安装生产依赖(npm ci --omit=dev),final 阶段只 COPY 最终产物目录,不 COPY node_modules 整体。

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev   # 只安装生产依赖
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules

陷阱:容器内时区与宿主机不一致

现象: 应用日志时间戳比实际早/晚 N 小时,cron 任务在错误时间触发。

原因: Docker 容器默认使用 UTC 时区,宿主机可能是 CST(UTC+8)或其他时区。

解决: 在 Dockerfile 中安装 tzdata 并设置环境变量,或在 compose.yaml 中挂载宿主机时区文件。

ENV TZ=Asia/Shanghai
RUN apk add --no-cache tzdata
# compose.yaml 方案
volumes:
  - /etc/localtime:/etc/localtime:ro
  - /etc/timezone:/etc/timezone:ro

陷阱:构建缓存失效导致每次都重新安装依赖

现象: 修改任意源码文件后,docker build 重新执行 npm installpip install,构建时间很长。

原因: COPY . . 放在 RUN npm install 之前,导致任何文件改动都让 npm install 层的缓存失效。

解决: 先单独 COPY package*.json ./,再 RUN npm install,最后才 COPY . .。这样只有 package.json 变动时才重新安装,源码变动不影响依赖层缓存。


参见

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog