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/amd64、linux/arm64、windows/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 联动
ARG 在 FROM 之前声明时,作用域仅限于 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 | - | 设置文件权限,如 755、0644 |
--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 FROM 和 ONBUILD MAINTAINER 也不允许。
SHELL
更改后续 shell 形式指令(RUN、CMD、ENTRYPOINT)使用的默认 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 | - | 运行用户,格式 user、user:group、uid: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 | - | 内存硬限制,如 512m、1g |
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.resources 在 docker 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 |
文件系统类型,如 nfs、nfs4、tmpfs |
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 配置
configs 与 secrets 类似,但用于非敏感配置文件。挂载权限默认为 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 仅表示容器进程已启动,数据库可能尚未接受连接。必须配合 healthcheck 和 condition: service_healthy 使用,或在应用层实现重试逻辑。
Compose 网络名称带项目名前缀
默认情况下,Compose 创建的网络名为 <项目目录名>_default。若多个 Compose 项目需要共享网络,或希望固定网络名,在 networks 配置中设置 name 字段:
networks:
default:
name: myapp_network # 固定名称,不受目录名影响
卷名默认带项目名前缀
与网络同理,卷实际名称为 <项目名>_<卷名>。引用另一 Compose 项目创建的卷时,使用 external: true 加 name:
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 install 或 pip install,构建时间很长。
原因: COPY . . 放在 RUN npm install 之前,导致任何文件改动都让 npm install 层的缓存失效。
解决: 先单独 COPY package*.json ./,再 RUN npm install,最后才 COPY . .。这样只有 package.json 变动时才重新安装,源码变动不影响依赖层缓存。