Docker 常用运维指令手册

运维指令手册 62.7 KB 原文

Docker 常用运维指令手册

适用版本:Docker Engine 20.10 / 23.x / 24.x / 25.x / 26.x / 27.x / 28.x / 29.x(当前稳定) 宿主系统:RHEL 8/9/10、Ubuntu 20.04/22.04/24.04、Debian 11/12/13、openEuler、SLES 15 约定:# root 或 sudo,$ 普通用户,<xxx> 为占位符,<container> 可写容器名或 ID 前几位。 差异标注:正文用 > **版本差异** 标注跨版本不一致之处,无标注表示各版本通用。 更新时间:2026-11(版本事实核对至 2026-11-05)


目录

  1. 版本与生命周期
  2. 安装部署
  3. 文件布局与运行机制
  4. daemon.json 配置详解
  5. 服务管理与启动排障
  6. 存储驱动与磁盘治理
  7. 镜像管理
  8. 容器运维
  9. 卷与绑定挂载
  10. 网络
  11. Dockerfile 指令全解
  12. 构建与 buildx
  13. Compose
  14. Swarm 集群
  15. 日志与监控
  16. 备份、迁移与升级
  17. 卸载与清理
  18. 故障速查
  19. 安全加固
  20. 附录

1. 版本与生命周期

1.1 版本编号规则

Docker 在 2019 年把 18.09 这种日期版本改成了 YY.MM,2021 年又改成纯语义化的 MAJOR.MINOR.PATCH:

版本段 含义 例子
MAJOR 每年一个大版本,允许破坏性变更 24 → 25 → 26 → 27 → 28 → 29
MINOR 每月一个,新特性 + 修复 29.9
PATCH 安全与 bug 修复 29.9.3

维护策略:只有最新的 MAJOR 分支收安全补丁。29.x 发布后,28.x 基本不再更新(除非重大 CVE)。所以"锁版本"要锁到 29.9.3 这一级,同时保留一个季度评估升级的窗口。

1.2 当前版本事实(2026-11)

项目 状态
当前大版本 Docker Engine 29.x,最新补丁 29.9.3(2026-11-03)
内置组件 containerd 2.4.x、runc 1.4.x、BuildKit 0.3x
29.7 新增 实验性 embedded containerd(containerd 跑在 dockerd 进程内,而非独立进程)
29.7 新增 default-stop-timeout daemon 参数;--mount type=image 转正式
镜像存储 29.x 逐步以 containerd image store 为主,docker info 的 Storage Driver 在两种模式下含义不同
Compose Compose v2 插件(docker compose),v1(Python docker-compose)已彻底停止维护

版本差异:24.x 及更早用 graphdriver(overlay2)存镜像;启用 containerd image store 后显示 overlayfs,并支持多平台镜像同存、docker buildx 直接推送 attestation。切换方式在 /etc/docker/daemon.json 中加 "features": {"containerd-snapshotter": true}(切换后旧镜像不可见,需重新拉取,生产慎用)。

1.3 大版本之间的关键变更(升级前必读)

变更点 引入版本 影响
docker-compose v1 停止维护 23.x 起 脚本里的 docker-compose 要改 docker compose
cgroup v2 支持完善 20.10+ 老内核(CentOS 7)仍走 v1
默认 iptables 后端切换 nftables 兼容 28.x+ firewalld 冲突表现变化
--link 彻底移除 25.x 老 compose 文件报错
DOCKER_CONTENT_TRUST 默认仍关 — 内容信任需自建 notary
Registry v1 API 移除 17.12 起 老私有仓库无法拉取
静态二进制不再自带完整 man 页 26.x 离线环境查文档要另带
docker system prune 默认不再删卷 长期 删卷必须显式 --volumes

1.4 版本识别

docker version
# Client: Docker Engine - Community
#  Version:           29.9.3
#  API version:       1.52 (downgraded from 1.53)
# Server: Docker Engine - Community
#  Engine:
#   Version:          29.9.3
#   API version:      1.53 (minimum version 1.44)

docker info
docker info --format '{{.ServerVersion}} | {{.Driver}} | {{.CgroupDriver}} | {{.CgroupVersion}} | {{.DockerRootDir}}'
docker buildx version
docker compose version
containerd --version
runc --version

客户端与服务端版本不一致是常见现象(比如本机 CLI 29.x 连远端 daemon 24.x),此时 API 版本会自动协商,但新 CLI 的部分子命令在旧 daemon 上会报 client version too new。

# 显式降 API 版本
DOCKER_API_VERSION=1.43 docker ps

# 连远程 daemon(需 daemon 开 2376 TLS,绝不开 2375)
export DOCKER_HOST=tcp://10.0.0.50:2376
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=/root/.docker
docker info

2. 安装部署

2.1 安装方式选择

方式 适用 可升级 可回滚 推荐度
官方仓库(dnf/apt) 有网/内网有镜像源 是 是(降版本) ★★★★★
离线 rpm/deb 包 内网隔离 是 是 ★★★★★
官方静态二进制 无包管理系统、定制系统 手工替换 手工 ★★★
get.docker.com 脚本 测试、临时环境 是(永远最新) 否 ★(生产禁用)
发行版自带 docker.io 包 图省事 跟随发行版 是 ★★(版本旧、路径不同)

2.2 安装前检查

# 内核:Docker 最低 3.10,overlay2 建议 ≥ 4.0,cgroup v2 需 ≥ 5.4
uname -r
# cgroup 版本:cgroup2fs = v2,tmpfs = v1
stat -fc %T /sys/fs/cgroup/

# 必需模块
lsmod | grep -E 'overlay|br_netfilter'
modprobe overlay && modprobe br_netfilter
echo -e "overlay\nbr_netfilter" >/etc/modules-load.d/docker.conf

# 转发(容器出网 + 端口映射必需)
sysctl net.ipv4.ip_forward
cat >/etc/sysctl.d/99-docker.conf <<'EOF'
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sysctl --system

# 磁盘:/var/lib/docker 建议独立分区,≥ 100G
df -h /var/lib

2.3 RHEL / CentOS / Rocky / Alma / openEuler

# 1) 清理冲突包(podman/buildah 与 docker 的 runc 会互相覆盖)
dnf remove -y podman buildah runc docker docker-client docker-common \
  docker-latest docker-logrotate docker-engine

# 2) 加仓库
dnf install -y dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 国内加速(二选一)
# dnf config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# sed -i 's|download.docker.com|mirrors.aliyun.com/docker-ce|g' /etc/yum.repos.d/docker-ce.repo

# 3) 安装
dnf install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

# 4) 启动
systemctl enable --now docker

# 5) 验证
docker run --rm hello-world

2.4 Ubuntu / Debian

apt update && apt install -y ca-certificates curl gnupg

# GPG key
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
  | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg

# 仓库(Ubuntu)
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
  > /etc/apt/sources.list.d/docker.list

# 仓库(Debian 把 ubuntu 换成 debian,其余相同)

apt update
apt install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin

systemctl enable --now docker

坑点:Ubuntu 自带的 docker.io 包与官方 docker-ce 冲突,装之前先 apt remove -y docker.io docker-doc docker-compose podman-docker containerd runc。

2.5 离线安装

方式一:rpm/deb 全依赖离线包

# 找一台同版本、同架构、有网的机器
dnf install -y 'dnf-command(download)'
dnf download --resolve --alldeps --destdir=/tmp/docker-rpms \
  docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

tar czf docker-offline-29.9.3-el9.tar.gz -C /tmp docker-rpms
scp docker-offline-29.9.3-el9.tar.gz root@target:/opt/

# 目标机
tar xzf /opt/docker-offline-29.9.3-el9.tar.gz -C /tmp
dnf install -y /tmp/docker-rpms/*.rpm --disablerepo='*'
systemctl enable --now docker

Debian 系同理,用 apt-get install --download-only -o Dir::Cache::archives=/tmp/debs ...。

方式二:静态二进制

VER=29.9.3
curl -fLO https://download.docker.com/linux/static/stable/x86_64/docker-${VER}.tgz
tar xzf docker-${VER}.tgz
install -m 755 docker/* /usr/bin/

# 还需要单独准备 containerd 与 runc(见第 3 章说明)

静态包不带 systemd 单元,需手写(见 5.1)。

2.6 安装后必做的四件事

# 1) 非 root 用户可用(代价见 19.2,谨慎授权)
usermod -aG docker deploy
newgrp docker            # 或重新登录

# 2) 镜像加速(国内)
mkdir -p /etc/docker
cat >/etc/docker/daemon.json <<'EOF'
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://dockerproxy.com"
  ],
  "log-driver": "json-file",
  "log-opts": { "max-size": "50m", "max-file": "5" },
  "data-root": "/data/docker",
  "live-restore": true
}
EOF
systemctl restart docker

# 3) 日志轮转(上面已配,验证)
docker info | grep -A3 'Logging Driver'

# 4) 开机自启 + 健康检查
systemctl is-enabled docker
docker run --rm --name t alpine:3.20 echo ok

2.7 版本锁定与升级

# RHEL:锁定,防 dnf update 误升
dnf versionlock add docker-ce docker-ce-cli containerd.io
dnf versionlock list

# 查看可升级版本
dnf list docker-ce --showduplicates | sort -r | head

# 指定版本安装(升级/回滚同一命令)
dnf install -y docker-ce-29.9.3 docker-ce-cli-29.9.3 containerd.io-2.4.2

# Debian
apt-mark hold docker-ce docker-ce-cli containerd.io
apt-mark unhold docker-ce && apt install -y docker-ce=5:29.9.3-1~ubuntu.24.04~noble

升级前确认 live-restore: true 已生效(docker info | grep -i 'live restore'),否则 systemctl restart docker 会把所有容器停掉。


3. 文件布局与运行机制

3.1 进程与组件关系

docker CLI  ──HTTP(unix socket /var/run/docker.sock)──▶  dockerd
                                                          │
                                                          ├─ containerd(独立进程,systemd 管理)
                                                          │     └─ containerd-shim-runc-v2(每容器一个)
                                                          │            └─ runc → 业务进程 PID 1
                                                          └─ docker-proxy(端口映射,userland-proxy=true 时)
进程 作用 挂了会怎样
dockerd API 服务、镜像/容器/网络/卷管理 所有 docker 命令报 "Cannot connect"
containerd 镜像分发、容器执行、CRI dockerd 起不来,容器无法创建
containerd-shim-runc-v2 每个容器的父进程,负责回收与日志 该容器状态异常
runc 一次性创建 namespace/cgroup 后退出 创建失败,报 OCI runtime error
docker-proxy 用户态端口转发 端口映射失效(userland-proxy: false 时不启动)
# 看进程树
ps -ef | grep -E 'dockerd|containerd|shim'
pstree -p $(pgrep -o dockerd)

# 容器的真实 PID 与 namespace
docker inspect -f '{{.State.Pid}}' web
ls -l /proc/$(docker inspect -f '{{.State.Pid}}' web)/ns/

3.2 关键路径

路径 内容 能否删
/etc/docker/daemon.json 主配置 改前备份
/etc/docker/certs.d/<registry>/ 私有仓库 CA 谨慎
/var/lib/docker/ 数据根(可配 data-root) 停服后可删(数据丢失)
/var/lib/docker/overlay2/ 镜像层与容器可写层 由 docker 管理,勿手删
/var/lib/docker/containers/<id>/ 容器元数据 + json 日志 勿手删
/var/lib/docker/volumes/ 具名卷 含业务数据
/var/lib/docker/image/ 镜像元数据索引 勿手删
/var/lib/docker/network/ 网络配置 勿手删
/var/lib/docker/buildkit/ 构建缓存 可清(buildx prune)
/var/run/docker.sock API socket 重启重建
/var/run/docker.pid PID 文件 重启重建

3.3 手工改数据目录的后果

# 想省空间把 overlay2 软链到别处 —— 不要这么做
# 错误做法:mv /var/lib/docker /data/ && ln -s /data/docker /var/lib/docker
# 正确做法:改 daemon.json 的 data-root(见 4.2),停 docker.socket 后 rsync

坑点:/var/lib/docker 做软链后,SELinux 上下文与 overlayfs 挂载路径都会出问题,表现为容器启动报 invalid argument。必须用 data-root。


4. daemon.json 配置详解

配置文件路径:默认 /etc/docker/daemon.json,也可用 dockerd --config-file 指定。修改后:可热加载的用 systemctl reload docker,其余需 restart。

4.1 生产模板(含注释说明见下表)

{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5",
    "compress": "true"
  },
  "data-root": "/data/docker",
  "storage-driver": "overlay2",
  "storage-opts": ["overlay2.override_kernel_check=true"],
  "live-restore": true,
  "default-ulimits": {
    "nofile": { "Name": "nofile", "Hard": 65536, "Soft": 65536 },
    "nproc":  { "Name": "nproc",  "Hard": 32768, "Soft": 32768 }
  },
  "max-concurrent-downloads": 10,
  "max-concurrent-uploads": 5,
  "registry-mirrors": ["https://docker.m.daocloud.io"],
  "insecure-registries": ["harbor.internal:80"],
  "default-address-pools": [
    { "base": "172.30.0.0/16", "size": 24 }
  ],
  "bip": "172.17.0.1/16",
  "fixed-cidr": "172.17.0.0/16",
  "iptables": true,
  "ip6tables": false,
  "userland-proxy": false,
  "default-runtime": "runc",
  "runtimes": {
    "nvidia": {
      "path": "nvidia-container-runtime",
      "runtimeArgs": []
    }
  },
  "metrics-addr": "127.0.0.1:9323",
  "experimental": true,
  "features": { "containerd-snapshotter": false }
}

提醒:daemon.json 不能有重复键(default-address-pools、log-opts 这类容易复制粘贴写两遍),部分版本会直接启动失败。改完务必 dockerd --validate。

4.2 参数分类详解

存储类

参数 默认 说明
data-root /var/lib/docker 数据根目录。改前必须停 docker.socket
storage-driver overlay2 可选 overlay2、fuse-overlayfs、btrfs、zfs、vfs。vfs 慢但兼容一切
storage-opts 空 overlay2 的 size= 可限制单容器可写层(需 xfs + pquota)
features.containerd-snapshotter false 启用 containerd 镜像存储(多架构同存)

日志类

参数 说明
log-driver json-file(默认)、local、journald、syslog、fluentd、gelf、awslogs、splunk、none
log-opts.max-size 单文件上限,必配,否则无上限
log-opts.max-file 保留文件数
log-opts.compress 轮转后 gzip 压缩,省 60%+ 空间
log-opts.labels 附加容器 label 到日志(fluentd/等驱动)

网络类

参数 默认 说明
bip 172.17.0.1/16 默认 bridge 网段,撞内网必须改
fixed-cidr 空 限定容器 IP 范围(需在 bip 子网内)
default-address-pools 空 自定义网络的网段池,防与 VPC 冲突
iptables true 关掉后端口映射失效,仅在交给外部防火墙管理时关
ip-forward true 内核转发开关的 docker 侧控制
userland-proxy true false 时纯 iptables 转发,性能好但要求内核 ≥ 4.19
default-network-opts 空 给自定义网络设默认参数(28.x+)

安全类

参数 说明
userns-remap "default" 或 "user:group",开启用户命名空间隔离
no-new-privileges 全局禁止 setuid 提权(28.x+)
seccomp-profile 自定义 seccomp JSON 路径
authorization-plugins 接入opa/authz 插件
tls、tlsverify、tlscacert 远程 API 的 TLS 配置

运行类

参数 说明
live-restore daemon 重启时容器不退出。升级不中断的关键
default-ulimits 容器默认 ulimit,覆盖宿主继承值
default-runtime 默认 OCI 运行时
runtimes 注册额外运行时(nvidia、kata、runsc)
default-shm-size /dev/shm 默认 64M,机器学习/浏览器类应用必须调大
init 全局给容器加 tini 作为 PID 1,回收僵尸进程
max-concurrent-downloads 并发拉取层数,默认 3;29.x 起 0 = 不限
metrics-addr Prometheus 指标地址,需配 experimental: true
shutdown-timeout daemon 停止时等容器退出的秒数,默认 15
default-stop-timeout 未显式指定 --stop-timeout 的容器默认超时(29.7+)

4.3 配置生效方式

# 语法校验(有错会直接报,不要盲改盲重启)
dockerd --validate --config-file=/etc/docker/daemon.json

# 热加载(SIGHUP):日志驱动、registry-mirrors、insecure-registries 等
systemctl reload docker

# 完整重启(data-root、storage-driver、iptables 等)
systemctl restart docker

# 查看实际生效配置
docker info
dockerd --config-file=/etc/docker/daemon.json --validate

4.4 代理配置

mkdir -p /etc/systemd/system/docker.service.d
cat >/etc/systemd/system/docker.service.d/proxy.conf <<'EOF'
[Service]
Environment="HTTP_PROXY=http://proxy.internal:3128"
Environment="HTTPS_PROXY=http://proxy.internal:3128"
Environment="NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,.internal"
EOF
systemctl daemon-reload && systemctl restart docker

# 验证 daemon 的代理(不是 shell 的)
systemctl show docker --property=Environment

# 客户端代理(build 时拉取基础镜像走这里)
mkdir -p ~/.docker
cat >~/.docker/config.json <<'EOF'
{
  "proxies": {
    "default": {
      "httpProxy": "http://proxy.internal:3128",
      "httpsProxy": "http://proxy.internal:3128",
      "noProxy": "localhost,127.0.0.1,harbor.internal"
    }
  }
}
EOF

4.5 私有仓库与镜像加速

# 加速:只对 docker.io 生效
"registry-mirrors": ["https://docker.m.daocloud.io", "https://dockerproxy.com"]

# 非 HTTPS 仓库(内网 Harbor 用 80 端口时)
"insecure-registries": ["harbor.internal:80", "10.0.0.50:5000"]

# 自签 CA(推荐方式,不加 insecure)
mkdir -p /etc/docker/certs.d/harbor.internal
cp harbor.crt /etc/docker/certs.d/harbor.internal/ca.crt
systemctl restart docker

# 登录(凭据存 ~/.docker/config.json,base64 等同明文)
docker login harbor.internal -u admin --password-stdin < ~/.pass
chmod 600 ~/.docker/config.json

# 多仓库凭据可用 credential helper(避免明文)
# "credsStore": "desktop" 或 "secretservice"

4.6 常见配置错误与后果

配置 错误写法 后果
log-opts 用 max_size 不生效,日志无限增长
bip 与 default-address-pools 重叠 网络创建失败
data-root 指向 NFS overlay2 不支持 NFS,容器起不来
iptables: false 无替代方案时 端口映射静默失效
userns-remap 开启后旧容器 直接不可见(ID 映射变了)
JSON 注释 // 或 # dockerd 启动失败 invalid character
尾随逗号 {"a":1,} 同上
# 启动失败时最快的定位
journalctl -u docker -n 30 --no-pager
dockerd --debug --config-file=/etc/docker/daemon.json   # 前台跑,直接看报错

5. 服务管理与启动排障

5.1 systemd 单元结构

官方包自带两个单元:docker.service(daemon)与 docker.socket(socket 激活)。

systemctl cat docker.service
systemctl cat docker.socket

# 关键行
# ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
# Requires=containerd.service        ← 静态二进制方案常漏这句
# KillMode=process                   ← 保证 restart 时容器不被一起杀
# Delegate=yes                       ← 让 cgroup 委派给容器
# LimitNOFILE=infinity

socket 的作用:有请求到达时才拉起 daemon。副作用是你以为停了 docker,其实 socket 又把它拉起来了:

systemctl stop docker            # 可能被 socket 重新激活
systemctl stop docker.socket     # 真正停住

5.2 常用管理命令

systemctl start docker
systemctl stop docker.socket docker
systemctl restart docker
systemctl reload docker                 # SIGHUP,热加载部分配置
systemctl status docker -l --no-pager
systemctl is-active docker
systemctl is-enabled docker
systemctl show docker --property=Environment
systemctl list-dependencies docker

5.3 启动失败的分层定位

# 第 1 层:单元本身
systemctl status docker -l
journalctl -u docker -n 50 --no-pager

# 第 2 层:配置语法
dockerd --validate --config-file=/etc/docker/daemon.json

# 第 3 层:前台运行看真实报错(最快)
systemctl stop docker.socket docker
dockerd --debug 2>&1 | head -50

# 第 4 层:依赖服务
systemctl status containerd
journalctl -u containerd -n 50

# 第 5 层:文件系统与端口
df -h /var/lib/docker
ls -l /var/run/docker.sock
ss -lntp | grep 2375
mount | grep -E 'overlay|/var/lib/docker'

典型报错对照

报错 原因 处理
failed to start daemon: error initializing graphdriver 存储驱动不支持当前 FS 换 overlay2 或检查 xfs ftype=1
devmapper: Thin Pool ... no space devicemapper 元数据满 迁到 overlay2
failed to start daemon: pid file found 上次异常退出留下 pid rm -f /var/run/docker.pid
Error starting daemon: error initializing network controller: list bridge addresses failed 网段耗尽/冲突 改 default-address-pools
iptables failed: iptables --wait -t filter -N DOCKER 规则链残留或权限 重启 docker;iptables -t filter -X DOCKER
failed to load listeners: can't create unix socket socket 文件残留 rm -f /var/run/docker.sock
Unit docker.service not found 静态二进制未写单元 见 5.1 手写

5.4 live-restore 深入

# 开启
# daemon.json: "live-restore": true
docker info | grep -i 'live restore'

# 效果:restart docker 时容器继续跑;但以下情况不生效
# - 容器使用了 --network host 之外的自定义网络(早期版本会断网)
# - daemon 升级跨大版本时有兼容风险
# - swarm 模式不支持 live-restore

# 验证
docker run -d --name t nginx:1.25
systemctl restart docker
docker ps                    # t 应该还在,且 PID 未变
docker inspect -f '{{.State.StartedAt}}' t

5.5 日志与调试

journalctl -u docker -f
journalctl -u docker --since "10 min ago" | grep -iE 'error|fail|panic'
journalctl -u docker -o json-pretty | tail -20

# 打开 debug 日志(临时,量大)
# daemon.json: "debug": true   然后 systemctl reload docker
# 或:systemctl edit docker 加 Environment=DOCKER_OPTS=--debug

# API 层面调试
curl --unix-socket /var/run/docker.sock http://localhost/version | jq
curl --unix-socket /var/run/docker.sock http://localhost/containers/json | jq '.[].Names'

# 跟踪某个操作
DOCKER_CLI_DEBUG=1 docker pull nginx:1.25

6. 存储驱动与磁盘治理

6.1 overlay2 原理(必须理解,否则排障全靠猜)

/var/lib/docker/overlay2/
├── l/                  # 短链接(层目录名缩写),被 lowerdir 引用
├── <layer-id>/         # 每个镜像层
│   ├── diff/           # 该层的文件内容
│   ├── link            # 短链接名
│   └── lower           # 父层引用
└── <container-layer>/  # 容器可写层(merged 目录是最终视图)
    ├── diff/           # 容器写入的文件
    ├── merged/         # 联合挂载点(容器内看到的 /)
    ├── upper/ -> diff
    └── work/

三个关键结论:

  1. 镜像层只读共享。10 个容器用同一镜像,镜像层只存一份。
  2. 容器写时复制(CoW)。修改一个 100M 的文件,实际只复制被改的块,但 docker ps -s 会显示整个文件大小。
  3. 删文件不释放空间。容器内 rm 一个镜像层里的文件只是打 whiteout 标记,磁盘占用不变。
# 查看挂载关系
mount | grep overlay
docker inspect <container> -f '{{json .GraphDriver.Data}}' | jq

6.2 存储驱动对比

驱动 性能 适用 备注
overlay2 好 默认,绝大多数场景 需内核 ≥ 4.0,xfs 需 ftype=1
fuse-overlayfs 中 rootless、内核不支持 overlay2 用户态,略慢
btrfs 好 宿主已用 btrfs 需 btrfs subvolume
zfs 好 宿主已用 zfs 依赖 zfs 模块
vfs 差(每次全量复制) 测试、极端兼容 空间占用大
devicemapper — 已移除(24.x 前) 见到就迁
docker info | grep -i 'storage driver'
xfs_info /var/lib/docker | grep ftype     # 必须 ftype=1

坑点:xfs 建文件系统时若没开 ftype=1(mkfs.xfs -n ftype=1),overlay2 会直接拒绝工作。CentOS 7 默认 ftype=0,这是老系统迁 Docker 的经典拦路虎。

6.3 磁盘占用分析

# 总览
docker system df
docker system df -v

# 分层看
du -sh /var/lib/docker/*
du -sh /var/lib/docker/overlay2 | sort -h
du -sh /var/lib/docker/volumes/* | sort -h | tail -10
du -sh /var/lib/docker/containers/* | sort -h | tail -10

# 容器可写层大小排行
docker ps -s --format 'table {{.Names}}\t{{.Size}}' | sort -k2 -h

# 日志排行(最高频的元凶)
ls -lhS /var/lib/docker/containers/*/*-json.log | head -10

6.4 限制单容器可写层大小(xfs + pquota)

# 1) 文件系统需挂 pquota
mount -o remount,rw,pquota /data
# /etc/fstab: /dev/vg/data /data xfs defaults,pquota 0 0

# 2) daemon.json
# "storage-opts": ["overlay2.size=20G"]

# 3) 单容器覆盖
docker run --storage-opt size=10G ...

6.5 清理策略(分级执行)

# 0 级:只看不动
docker system df -v

# 1 级:安全清理(不影响业务)
docker container prune -f
docker image prune -f
docker builder prune -f --keep-storage 10GB
docker network prune -f

# 2 级:清理无引用镜像(可能影响下次启动速度)
docker image prune -a -f --filter "until=720h"

# 3 级:危险(会删数据)
docker volume prune            # 无主卷
docker system prune -a --volumes   # 全清

# 定时清理(crontab)
# 30 3 * * 0 /usr/bin/docker image prune -a -f --filter "until=336h" >> /var/log/docker-prune.log 2>&1

6.6 空间没释放的四种原因

# 1) 删除的文件仍被进程持有
lsof +L1 | grep -i docker

# 2) 容器被删但卷还在
docker volume ls -f dangling=true

# 3) overlay2 里有孤儿层(异常退出导致)
docker system prune -a      # 仍不释放时需停服人工核对

# 4) 日志未轮转(最常见)
find /var/lib/docker/containers -name '*-json.log' -size +1G -exec truncate -s 0 {} \;

7. 镜像管理

7.1 命名规则与 digest

[registry[:port]/][namespace/]repository[:tag][@digest]
nginx:1.25                                   # docker.io/library/nginx:1.25
harbor.internal:8443/web/app:v1.0.0
nginx@sha256:abc123...                       # 精确到内容,生产锁定用它
# 取 digest(推送到仓库后才有远端 digest)
docker inspect --format='{{index .RepoDigests 0}}' nginx:1.25
docker buildx imagetools inspect nginx:1.25   # 看多架构 manifest 与各平台 digest

7.2 查询与拉取

docker images
docker images -a
docker images --digests
docker images --filter "dangling=true"
docker images --filter "before=nginx:1.24"
docker images --filter "reference=harbor.internal/*"
docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}'

docker pull nginx:1.25
docker pull --platform linux/arm64 nginx:1.25
docker pull --quiet nginx:1.25
docker pull nginx@sha256:abc...

# 拉取策略(compose/k8s 里常用)
docker pull --disable-content-trust nginx:1.25

7.3 打标签与推送

docker tag nginx:1.25 harbor.internal/web/nginx:1.25.3
docker push harbor.internal/web/nginx:1.25.3
docker push --all-tags harbor.internal/web/nginx
docker push harbor.internal/web/nginx@sha256:abc...   # 推到指定 digest

# 批量:给所有本地镜像加前缀
for img in $(docker images --format '{{.Repository}}:{{.Tag}}' | grep -vE '<none>|harbor'); do
  docker tag "$img" "harbor.internal/mirror/${img}"
done

# 批量推送到镜像仓库(并行,注意别打爆仓库)
docker images --format '{{.Repository}}:{{.Tag}}' | grep '^harbor.internal' \
  | xargs -P4 -I{} docker push {}

7.4 导入导出

# 镜像(保留元数据,推荐)
docker save -o nginx.tar nginx:1.25
docker save nginx:1.25 redis:7.4 | gzip > images.tar.gz
docker load -i nginx.tar
docker load < images.tar.gz

# 容器文件系统快照(丢 CMD/ENV/tag)
docker export -o rootfs.tar <container>
docker import rootfs.tar myapp:snapshot
docker import -c 'CMD ["/bin/sh"]' rootfs.tar myapp:snapshot

# 对比
# save/load:镜像迁移、离线交付、备份
# export/import:排查、取证、极简镜像基座

7.5 镜像检查

docker image inspect nginx:1.25
docker image inspect -f '{{.Config.Cmd}} {{.Config.Entrypoint}}' nginx:1.25
docker image inspect -f '{{range .Config.Env}}{{println .}}{{end}}' nginx:1.25
docker history nginx:1.25 --no-trunc --human
docker image inspect -f '{{.Architecture}}/{{.Os}}' nginx:1.25

# 不启动容器看文件
docker cp nginx:1.25:/etc/nginx/nginx.conf ./
docker run --rm -it --entrypoint sh nginx:1.25
docker run --rm --entrypoint cat nginx:1.25 /etc/os-release

# 找泄漏的密钥
docker history --no-trunc myapp:1.0 | grep -iE 'password|token|secret|key='

7.6 删除

docker rmi nginx:1.25
docker rmi -f nginx:1.25                 # 强制(正在被容器引用时会留下 <none>)
docker image prune                       # 悬空
docker image prune -a --filter "until=168h"

# 删不掉的原因
docker ps -a --filter ancestor=nginx:1.25     # 有容器在用
docker image inspect nginx:1.25 -f '{{.RepoTags}}'   # 多 tag 指向同一 ID

坑点:一个镜像 ID 有多个 tag 时,docker rmi <id> 只会失败或删掉一个 tag。正确做法是 docker rmi tag1 tag2,或先 docker image prune。

7.7 镜像仓库直连操作

# 查仓库里的 tag(Harbor/registry v2 API)
curl -su admin:pwd https://harbor.internal/v2/web/app/tags/list | jq
curl -su admin:pwd https://harbor.internal/service/token?service=harbor-registry\&scope=repository:web/app:pull | jq

# 删除远端 tag(registry 原生 API)
curl -su admin:pwd -I -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
  https://harbor.internal/v2/web/app/manifests/1.0.0 | grep -i docker-content-digest
curl -su admin:pwd -X DELETE https://harbor.internal/v2/web/app/manifests/sha256:xxx

8. 容器运维

8.1 run 参数完整清单

docker run -d \
  --name web \
  --restart unless-stopped \
  --stop-timeout 30 \
  -p 8080:80 \
  -p 127.0.0.1:9090:9090 \
  -e TZ=Asia/Shanghai \
  --env-file /opt/app/.env \
  -v /data/web:/usr/share/nginx/html:ro \
  -v web-logs:/var/log/nginx \
  --mount type=bind,src=/data/conf,dst=/etc/nginx/conf.d,readonly \
  --network appnet \
  --network-alias nginx \
  --hostname web01 \
  --dns 10.0.0.2 --dns-search internal \
  --add-host db.internal:10.0.0.30 \
  --cpus 2 \
  --cpu-shares 1024 \
  --cpuset-cpus 0-1 \
  -m 2g \
  --memory-swap 2g \
  --memory-reservation 1g \
  --pids-limit 200 \
  --ulimit nofile=65536:65536 \
  --shm-size 256m \
  --log-driver json-file --log-opt max-size=50m --log-opt max-file=5 \
  --health-cmd "curl -fs http://127.0.0.1/healthz || exit 1" \
  --health-interval 30s --health-timeout 3s \
  --health-retries 3 --health-start-period 30s \
  --read-only \
  --tmpfs /tmp:rw,size=64m \
  --security-opt no-new-privileges \
  --cap-drop ALL --cap-add NET_BIND_SERVICE \
  --user 1000:1000 \
  --init \
  --label env=prod --label owner=ops \
  nginx:1.25

参数分组记忆

组 参数
身份 --name、--hostname、--label、--cidfile
生命周期 -d、-it、--rm、--restart、--stop-timeout、--init
资源 -m、--memory-swap、--cpus、--cpuset-cpus、--pids-limit、--ulimit、--shm-size
存储 -v、--mount、--tmpfs、--volumes-from、--read-only
网络 -p、--network、--network-alias、--dns、--add-host、--expose
环境 -e、--env-file、-w、--user
安全 --privileged、--cap-add/drop、--security-opt、--userns、--ipc、--pid
健康 --health-cmd/interval/timeout/retries/start-period
日志 --log-driver、--log-opt

8.2 restart 策略

策略 行为 适用
no(默认) 不重启 一次性任务
on-failure[:N] 非 0 退出时重启,最多 N 次 批处理
always 总重启(含 daemon 重启后) 常驻服务
unless-stopped 除手动 stop 外总重启 生产首选
docker update --restart=unless-stopped web
# 注意:与 --rm 互斥;容器被 docker stop 后 always 也会在 daemon 重启时拉起

8.3 资源限制(cgroup v2 注意点)

# 内存
-m 2g                       # 硬限制,超了 OOMKilled
--memory-reservation 1g     # 软限制,内存紧张时回收
--memory-swap 2g            # 内存+swap 总上限,等于 -m 即禁用 swap
--oom-kill-disable          # 禁止 OOM Killer(危险,可能拖死宿主)

# CPU
--cpus 1.5                  # 等价 quota=150000 period=100000
--cpu-shares 512            # 相对权重,仅在争抢时生效
--cpuset-cpus 0-3           # 绑核
--cpu-quota / --cpu-period  # 精细控制

# 进程与文件
--pids-limit 200            # 防 fork 炸弹
--ulimit nofile=65536:65536
--ulimit core=0             # 关 core dump

# 块 IO(需宿主支持)
--device-read-bps /dev/sda:10mb
--device-write-iops /dev/sda:100
# 查看实际生效的限制
docker inspect web -f '{{.HostConfig.Memory}} {{.HostConfig.NanoCpus}} {{.HostConfig.PidsLimit}}'
cat /sys/fs/cgroup/system.slice/docker-<id>.scope/memory.max      # cgroup v2
cat /sys/fs/cgroup/memory/docker/<id>/memory.limit_in_bytes       # cgroup v1

8.4 健康检查

# 镜像内置
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
  CMD curl -fs http://127.0.0.1:8080/health || exit 1

# 运行时覆盖
docker run --health-cmd "..." --health-interval 10s nginx:1.25
docker run --no-healthcheck nginx:1.25

# 查看
docker ps                                    # STATUS 列显示 (healthy)/(unhealthy)
docker inspect web -f '{{json .State.Health}}' | jq
docker inspect web -f '{{range .State.Health.Log}}{{.Start}} {{.ExitCode}} {{.Output}}{{end}}'

坑点:--health-cmd 在容器内执行,镜像里没有 curl/wget 时会一直 unhealthy。用 CMD-SHELL 时注意 shell 是否存在(alpine 有 sh,distroless 没有)。

8.5 PID 1 与信号

容器里的第一个进程就是 PID 1,它有两个特殊行为:

  1. 不响应默认信号处理。PID 1 对未显式注册 handler 的信号会忽略,导致 docker stop 等 10s 后强杀。
  2. 不回收僵尸进程。应用 fork 子进程后不 wait,僵尸会堆积。
# 症状:docker stop 总是等满 10s 才停
docker stop -t 60 web            # 给足优雅退出时间

# 解决 1:入口用 exec 形式(让业务进程成为 PID 1 并正确接收信号)
# 错误:CMD java -jar app.jar     → 实际是 sh -c 作 PID 1
# 正确:CMD ["java","-jar","app.jar"]
# 正确:CMD ["sh","-c","exec java -jar app.jar"]

# 解决 2:加 --init(注入 tini 作 PID 1,回收僵尸 + 转发信号)
docker run --init ...

# 手工发信号
docker kill -s SIGTERM web
docker kill -s SIGHUP web        # nginx reload
docker kill -s SIGUSR1 web

8.6 日常操作

docker ps
docker ps -a
docker ps -q
docker ps --filter "status=exited"
docker ps --filter "exited=137"
docker ps --filter "label=env=prod"
docker ps --filter "name=web"
docker ps --filter "ancestor=nginx:1.25"
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}\t{{.Image}}'
docker ps --size                        # 含可写层大小(慢)

docker start/stop/restart/kill/pause/unpause/rename/update
docker rm / docker rm -f / docker rm -v   # -v 同时删匿名卷
docker wait web                            # 阻塞等退出,返回退出码
docker attach web                          # 接管前台(Ctrl+P Ctrl+Q 脱离)
docker top web
docker diff web
docker commit -m "msg" -a "author" web myapp:hotfix

8.7 退出码判读

码 含义 排查方向
0 正常退出 —
1 应用错误 看日志
125 docker 自身失败 参数错误
126 命令不可执行 权限/文件系统 noexec
127 命令不存在 ENTRYPOINT 路径错
128+N 被信号 N 终止 143=SIGTERM,130=SIGINT
134 SIGABRT 断言失败、glibc 崩溃
137 SIGKILL OOM Killer 或 docker kill
139 SIGSEGV 段错误、二进制不兼容
143 SIGTERM 正常 stop
docker inspect web -f '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}'
dmesg -T | grep -i 'killed process' | tail

9. 卷与绑定挂载

9.1 选择依据

需求 选择
数据库数据、需备份 具名卷
配置文件、代码 绑定挂载(只读)
多容器共享数据 具名卷 + --volumes-from 或同名挂载
临时敏感数据 --tmpfs
跨主机共享 NFS/CSI 卷驱动

9.2 卷操作

docker volume create myvol
docker volume create --driver local \
  --opt type=none --opt device=/data/myvol --opt o=bind myvol-bind
docker volume create --driver local \
  --opt type=nfs --opt o=addr=10.0.0.40,rw,nfsvers=4.1 \
  --opt device=:/export/data nfsvol

docker volume ls
docker volume ls -f dangling=true
docker volume ls -f name=mysql
docker volume inspect myvol
docker volume rm myvol
docker volume prune            # 危险

# 卷的物理路径
docker volume inspect myvol -f '{{.Mountpoint}}'

9.3 三种挂载写法对比

# -v:简洁,路径不存在自动创建(目录属主 root)
-v /data/html:/usr/share/nginx/html:ro

# --mount:语义明确,路径不存在直接报错(推荐生产)
--mount type=bind,src=/data/html,dst=/usr/share/nginx/html,readonly

# 具名卷两种写法
-v web-logs:/var/log/nginx
--mount type=volume,src=web-logs,dst=/var/log/nginx

# tmpfs
--tmpfs /tmp:rw,size=64m,mode=1777
--mount type=tmpfs,dst=/tmp,tmpfs-size=64m

必记:-v 的 :z/:Z 是 SELinux 标签开关,:ro 是只读。写 -v /data:/data:z 时 z 表示多容器共享标签,Z 表示独占。写错不会报错,只会权限失败。

9.4 备份与恢复

# 备份具名卷
docker run --rm -v myvol:/data:ro -v $(pwd):/backup alpine \
  tar czf /backup/myvol-$(date +%F).tar.gz -C /data .

# 恢复(先清空目标,避免残留)
docker run --rm -v myvol:/data -v $(pwd):/backup alpine \
  sh -c "rm -rf /data/* /data/.[!.]* 2>/dev/null; tar xzf /backup/myvol-2026-11-05.tar.gz -C /data"

# 卷之间复制
docker run --rm -v src:/from:ro -v dst:/to alpine sh -c "cp -a /from/. /to/"

# 备份绑定挂载(注意保留属主与 ACL)
tar czf /backup/appdata.tar.gz -C /data appdata

9.5 权限问题定位

# 容器内 UID 与宿主机属主不匹配
docker run --rm -v /data:/data alpine ls -ln /data
docker run --rm -v /data:/data alpine id

# 解决:对齐 UID
chown -R 1000:1000 /data
docker run --user 1000:1000 ...

# SELinux
ls -Z /data
chcon -Rt container_file_t /data
semanage fcontext -a -t container_file_t "/data(/.*)?" && restorecon -Rv /data

# 只读文件系统报错(--read-only)
docker run --read-only --tmpfs /tmp --tmpfs /run nginx:1.25

10. 网络

10.1 网络模式

模式 命令 隔离性 端口映射 场景
bridge 默认 / --network bridge 独立 netns 需要 通用
自定义 bridge --network appnet 独立 netns + 内置 DNS 需要 生产必用
host --network host 无隔离 不需要(直接占宿主端口) 高性能、大量端口
none --network none 只有 lo 无 纯计算、安全隔离
container --network container:web 共享 web 的 netns 跟随 web sidecar
overlay -d overlay 跨主机 swarm 路由网格 Swarm
macvlan -d macvlan 独立 MAC,二层直通 不需要 需要同网段真实 IP
ipvlan -d ipvlan 共享 MAC 不需要 受限环境

10.2 自定义网络(生产标准做法)

docker network create \
  --driver bridge \
  --subnet 172.20.0.0/16 \
  --gateway 172.20.0.1 \
  --ip-range 172.20.5.0/24 \
  --opt "com.docker.network.bridge.name"="br-app" \
  --opt "com.docker.network.driver.mtu"="1500" \
  appnet

docker network ls
docker network inspect appnet
docker network connect --alias db2 --ip 172.20.5.10 appnet db
docker network disconnect -f appnet db
docker network rm appnet
docker network prune

默认 bridge 与自定义网络的关键差别:默认 bridge 上容器只能靠 IP 互访,自定义网络才有内嵌 DNS(容器名/别名解析)。这是"为什么我的容器 ping 不通服务名"的根因。

10.3 端口映射细节

-p 8080:80                  # 宿主所有 IP:8080 → 容器 80
-p 127.0.0.1:8080:80        # 只绑本机(数据库必用)
-p 8080:80/tcp -p 8080:80/udp
-p 8080-8085:80-85          # 范围
-p 80                       # 随机宿主端口
-P                          # 映射所有 EXPOSE

docker port web
docker inspect web -f '{{json .NetworkSettings.Ports}}' | jq

映射优先级:userland-proxy: true 时 docker-proxy 进程 + iptables 双份;false 时仅 iptables DNAT,性能更好。

10.4 iptables 链结构(排障必看)

nat 表
├── PREROUTING → DOCKER(DNAT 到容器 IP)
├── OUTPUT     → DOCKER(本机访问映射端口)
└── POSTROUTING → POSTROUTING_direct / MASQUERADE(容器出网)

filter 表
├── FORWARD → DOCKER-USER → DOCKER-ISOLATION-STAGE-1 → DOCKER
└── INPUT   → DOCKER(早期版本)
iptables -t nat -L DOCKER -n -v --line-numbers
iptables -t filter -L DOCKER-USER -n -v
iptables -t filter -L FORWARD -n -v
iptables -t nat -S | grep -E 'DNAT|MASQUERADE'

# nftables 系统(RHEL 9+/Ubuntu 22.04+)
nft list ruleset | grep -A10 docker

重要:Docker 自己管理 DOCKER、DOCKER-ISOLATION-* 链,不要手工改,要加规则请放 DOCKER-USER 链(它在 DOCKER 之前生效,且 Docker 不会清)。

# 例:禁止某网段访问所有容器端口
iptables -I DOCKER-USER -s 192.168.50.0/24 -j DROP

# 持久化(否则重启丢失)
iptables-save >/etc/sysconfig/iptables        # RHEL
netfilter-persistent save                     # Debian

10.5 firewalld / ufw 集成

# RHEL:docker 会把 docker0 放进 trusted 区,但自定义网桥不会
firewall-cmd --permanent --zone=trusted --add-interface=docker0
firewall-cmd --permanent --zone=trusted --add-interface=br-app
firewall-cmd --reload

# 放行业务端口(容器端口映射到宿主后,仍需放行宿主端口)
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload
firewall-cmd --list-all

# Ubuntu ufw:默认 FORWARD DROP 会让容器没网
sed -i 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/' /etc/default/ufw
ufw reload
ufw allow 8080/tcp
ufw status verbose

坑点:firewalld 的 docker zone 由 Docker 自动维护,但 firewall-cmd --reload(尤其是 --complete-reload)会清掉 Docker 的 iptables 规则,导致容器瞬间断网。生产环境改防火墙后必须验证容器连通性。

10.6 DNS

docker run --dns 10.0.0.2 --dns 8.8.8.8 --dns-search internal nginx:1.25
docker run --add-host db.internal:10.0.0.30 nginx:1.25
docker run --hostname web01 nginx:1.25

# daemon 级默认 DNS
# daemon.json: "dns": ["10.0.0.2"], "dns-search": ["internal"]

# 容器内 DNS 解析
docker exec web cat /etc/resolv.conf      # 127.0.0.11 是 docker 内嵌 DNS
docker exec web nslookup other-container  # 仅自定义网络可用
docker exec web getent hosts db           # 走 /etc/hosts + DNS

注意:--network host 的容器直接使用宿主 /etc/resolv.conf,不受 --dns 影响。

10.7 网络排障流程

# 1) 容器自身
docker exec web ss -lntp
docker exec web curl -v http://127.0.0.1:80

# 2) 容器到宿主
docker exec web ip route
docker exec web ping -c2 <host-ip>

# 3) 宿主到容器
ip route | grep docker
curl -v http://<container-ip>:80

# 4) 外部到映射端口
ss -lntp | grep <port>
iptables -t nat -L DOCKER -n -v | grep <port>
telnet <host-ip> <port>

# 5) 进 namespace 抓包(宿主看不到容器回环)
PID=$(docker inspect -f '{{.State.Pid}}' web)
nsenter -t $PID -n ss -lntp
nsenter -t $PID -n tcpdump -i eth0 -nn -A port 80

# 6) 内核转发与 conntrack
sysctl net.ipv4.ip_forward
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

11. Dockerfile 指令全解

11.1 指令速查

指令 作用 常见坑
FROM 基础镜像 不支持变量在 --platform 之前
ARG 构建参数 构建结束后消失,但会留在 docker history
ENV 运行时环境变量 出现在镜像元数据,不可放密钥
LABEL 元数据 推荐 OCI 标准 label
WORKDIR 工作目录 不存在会自动创建
COPY 复制文件 支持 --from、--chmod、--link
ADD 复制 + 解压 + URL 能解 tar、能下 URL,建议只用 COPY
RUN 构建时执行 每层一个容器,合并以减少层数
CMD 默认命令 只最后一个生效,被 docker run 参数覆盖
ENTRYPOINT 入口 exec 形式才正确接收信号
EXPOSE 声明端口 仅是文档作用,不实际发布
VOLUME 声明匿名卷 声明后该目录的后续修改会被丢弃
USER 运行用户 必须在需要 root 的操作之后
HEALTHCHECK 健康检查 只有最后一条生效
SHELL 改变 RUN 的 shell Windows 镜像常用
STOPSIGNAL 停止信号 默认 SIGTERM
ONBUILD 继承时触发 隐式行为,慎用
MAINTAINER 已废弃 用 LABEL maintainer=

11.2 CMD 与 ENTRYPOINT 的四种组合

# 1) 只有 CMD(exec 形式,推荐)
CMD ["nginx", "-g", "daemon off;"]
# docker run img            → nginx -g "daemon off;"
# docker run img bash        → bash(CMD 被整体替换)

# 2) 只有 CMD(shell 形式)
CMD nginx -g "daemon off;"
# 实际执行 /bin/sh -c "nginx -g ...",PID 1 是 sh,信号收不到

# 3) ENTRYPOINT + CMD(最佳实践)
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
# docker run img            → nginx -g "daemon off;"
# docker run img -v         → nginx -v(CMD 被替换,ENTRYPOINT 保留)

# 4) ENTRYPOINT + 需要覆盖入口
# docker run --entrypoint /bin/sh img

11.3 缓存机制与层顺序

# 错:任何源码改动都导致依赖重装
COPY . .
RUN npm ci

# 对:依赖清单先复制,源码后复制
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

缓存失效规则:只要某一层的输入(文件内容、命令字符串、ARG 值)变了,该层及之后所有层都重建。

# 用 ARG 打破缓存(有意识地强制重装)
ARG CACHE_BUST=1
RUN apt-get update && apt-get install -y curl
# docker build --build-arg CACHE_BUST=$(date +%s) .

11.4 多阶段构建

# 阶段 1:构建
FROM golang:1.24-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o /out/app .

# 阶段 2:运行(最终镜像只有二进制)
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
# 只构建某阶段(调试用)
docker build --target builder -t app:builder .

# 从其他镜像取文件
COPY --from=nginx:1.25 /etc/nginx/nginx.conf /etc/nginx/

11.5 BuildKit 专属能力

# syntax=docker/dockerfile:1.9
FROM node:22-alpine

# 1) 缓存挂载:依赖包跨构建复用,不进镜像层
RUN --mount=type=cache,target=/root/.npm npm ci

# 2) 密钥挂载:用完即弃,不进层、不进 history
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci

# 3) 绑定挂载(构建时读宿主文件,不复制)
RUN --mount=type=bind,source=./scripts,target=/scripts /scripts/gen.sh

# 4) SSH 转发(拉私有 git 依赖)
RUN --mount=type=ssh git clone git@github.com:org/private.git

# 5) heredoc 写多行文件
COPY <<EOF /etc/app/config.yaml
server:
  port: 8080
EOF

# 6) 直接执行内联脚本
RUN <<EOF
set -e
apk add --no-cache curl
curl -fsSL https://example.com/install.sh | sh
EOF
docker build --secret id=npmrc,src=$HOME/.npmrc .
docker build --ssh default .

11.6 镜像瘦身清单

手段 收益 代价
多阶段构建 最大(省掉工具链) 构建时间略增
alpine 基础镜像 500M → 5M musl 与 glibc 不兼容
distroless 更小 + 更安全 无 shell,排障难
合并 RUN + 清缓存 每层几十~几百 M 可读性下降
--no-install-recommends 减少无用依赖 可能缺运行库
.dockerignore 防误打包 无
剥离符号表 -s -w 二进制小 30% 无栈信息
strip / upx 进一步压缩 可能影响兼容
# 分析每层大小
docker history --no-trunc --human myapp:1.0.0
# 交互式分析(推荐)
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock \
  wagoodman/dive:latest myapp:1.0.0

11.7 Dockerfile 静态检查

docker run --rm -i hadolint/hadolint < Dockerfile
# 常见告警
# DL3008 pin 版本
# DL3018 apk 未 pin
# DL3009 未清理 apt 缓存
# DL4006 管道未设 pipefail
# DL3025 用 JSON 形式写 CMD/ENTRYPOINT

12. 构建与 buildx

12.1 构建基础

docker build -t myapp:1.0.0 .
docker build -t myapp:1.0.0 -f docker/Dockerfile.prod .
docker build -t myapp:1.0.0 --build-arg VERSION=1.0.0 .
docker build --no-cache -t myapp:1.0.0 .
docker build --pull -t myapp:1.0.0 .          # 强制拉最新基础镜像
docker build --progress=plain .               # 完整日志
docker build --quiet .                        # 只输出镜像 ID(CI 用)
docker build --label git.commit=$(git rev-parse --short HEAD) .
docker build --iidfile /tmp/iid .             # 输出镜像 ID 到文件

12.2 Builder 管理

docker buildx ls
docker buildx create --name multi --driver docker-container --use
docker buildx inspect --bootstrap
docker buildx use multi
docker buildx rm multi

# 可用驱动
# docker        :用 daemon 内置 BuildKit,不支持多架构
# docker-container:起一个 buildkitd 容器,支持多架构、cache 导出
# remote        :连远程 buildkitd
# kubernetes    :在 k8s 里跑构建

12.3 多架构构建

# 1) 注册 QEMU(跨架构模拟)
docker run --privileged --rm tonistiigi/binfmt --install all
ls /proc/sys/fs/binfmt_misc/ | grep -E 'aarch64|arm'

# 2) 创建 builder
docker buildx create --name multi --driver docker-container --use --bootstrap

# 3) 构建并推送(多架构必须 --push 或 -o,不能直接 load 到本地)
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t harbor.internal/web/app:1.0.0 \
  --push .

# 4) 验证
docker buildx imagetools inspect harbor.internal/web/app:1.0.0

# 单平台 load 到本地
docker buildx build --platform linux/amd64 -t app:1.0.0 --load .

12.4 构建缓存

# 本地缓存查看与清理
docker buildx du
docker buildx prune
docker buildx prune --keep-storage 10GB
docker buildx prune --filter until=168h

# 导出缓存到仓库(CI 跨机复用,最实用的优化)
docker buildx build \
  --cache-to type=registry,ref=harbor.internal/cache/app:buildcache,mode=max \
  --cache-from type=registry,ref=harbor.internal/cache/app:buildcache \
  -t app:1.0.0 --push .

# 本地目录缓存
docker buildx build \
  --cache-to type=local,dest=/tmp/buildcache \
  --cache-from type=local,src=/tmp/buildcache .

# 内联缓存(简单但只存最终层)
docker build --build-arg BUILDKIT_INLINE_CACHE=1 -t app:1.0.0 .

12.5 供应链元数据

# 生成 SBOM 与来源证明(attestation)
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --sbom=true \
  --provenance=mode=max \
  -t harbor.internal/web/app:1.0.0 --push .

docker buildx imagetools inspect harbor.internal/web/app:1.0.0 --format '{{json .SBOM}}'
docker buildx imagetools inspect harbor.internal/web/app:1.0.0 --format '{{json .Provenance}}'

版本差异:Docker 29.x 的 docker image inspect 增加了 attestations 端点(可通过 API 拉取 SLSA 来源与 SPDX SBOM)。旧版本只能借助 buildx imagetools 或 cosign 查看。


13. Compose

13.1 文件结构(v2 完整示例)

name: webstack

x-common: &common
  restart: unless-stopped
  logging:
    driver: json-file
    options: { max-size: "50m", max-file: "5" }
  networks: [back]

services:
  web:
    <<: *common
    image: harbor.internal/web/nginx:1.25.3
    ports: ["80:80", "443:443"]
    volumes:
      - ./conf/nginx.conf:/etc/nginx/nginx.conf:ro
      - web-logs:/var/log/nginx
    depends_on:
      api: { condition: service_healthy }
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 20s
    deploy:
      resources:
        limits: { cpus: "2", memory: 2G }
        reservations: { cpus: "0.5", memory: 512M }
    networks: [front, back]

  api:
    <<: *common
    image: harbor.internal/web/api:1.0.0
    env_file: [./conf/api.env]
    environment:
      TZ: Asia/Shanghai
      SPRING_PROFILES_ACTIVE: prod
    expose: ["8080"]
    volumes:
      - type: volume
        source: api-data
        target: /app/data
    healthcheck:
      test: ["CMD-SHELL", "curl -fs http://127.0.0.1:8080/actuator/health || exit 1"]
      interval: 30s
    profiles: ["app"]

  migrate:
    image: harbor.internal/web/api:1.0.0
    command: ["java", "-jar", "app.jar", "--migrate-only"]
    depends_on:
      db: { condition: service_healthy }
    restart: "no"
    profiles: ["tools"]

  db:
    image: mysql:8.4
    command: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_0900_ai_ci"]
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
      interval: 10s
      retries: 10
      start_period: 30s
    secrets: [db_root]

volumes:
  web-logs:
  api-data:
  db-data:

networks:
  front:
    driver: bridge
  back:
    driver: bridge
    internal: true

secrets:
  db_root:
    file: ./secrets/db_root.txt

13.2 命令全集

# 生命周期
docker compose up -d
docker compose up -d --build
docker compose up -d --force-recreate
docker compose up -d --no-deps api          # 只更新 api,不动依赖
docker compose up --abort-on-container-exit # 任一退出即全停(CI 用)
docker compose down
docker compose down -v                      # 删卷(危险)
docker compose down --rmi all
docker compose down --remove-orphans
docker compose stop / start / restart
docker compose pause / unpause
docker compose kill
docker compose rm -f api

# 查看
docker compose ps
docker compose ps -a
docker compose ps --format json
docker compose logs -f --tail 200 api
docker compose logs --since 10m --until 5m web
docker compose top
docker compose stats
docker compose images
docker compose port web 80
docker compose config                     # 校验 + 展开变量
docker compose config --services
docker compose config --volumes
docker compose ls                         # 所有 compose 项目
docker compose ls -a

# 执行
docker compose exec api sh
docker compose exec -u root api bash
docker compose run --rm api /bin/sh -c "ls /app"
docker compose run --rm --no-deps migrate
docker compose cp api:/app/logs ./logs
docker compose pull
docker compose push
docker compose build --no-cache api

# 扩缩
docker compose up -d --scale api=3

13.3 关键机制

depends_on 的两种语义

depends_on:
  - db                     # 只保证启动顺序,不等就绪
depends_on:
  db:
    condition: service_healthy       # 等健康(推荐)
    # service_started / service_completed_successfully

profiles:按需启停一组服务

docker compose --profile tools up -d migrate
docker compose --profile "*" up -d

多文件覆盖:后者覆盖前者(同名字段合并,ports 是追加)

docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose --env-file .env.prod up -d

变量优先级(高 → 低):docker compose run -e > shell 环境变量 > .env 文件 > compose 文件中的 environment 默认值。

# .env 自动加载,compose.yaml 中用 ${TAG}
docker compose config | grep image        # 确认替换结果

13.4 开机自启

Compose 没有 daemon,靠 restart 策略不够(宿主机重启后容器不会自动起,除非 daemon 起来且策略是 always/unless-stopped——实际上这两个策略在 daemon 启动时会拉起容器)。

两种做法:

# 做法 A:restart: unless-stopped(最简单,容器级自启)
# 局限:依赖 compose 网络先创建;顺序不保证

# 做法 B:systemd 包一层(推荐,顺序可控)
cat >/etc/systemd/system/webstack.service <<'EOF'
[Unit]
Description=WebStack (docker compose)
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/webstack
ExecStart=/usr/bin/docker compose up -d --remove-orphans
ExecStop=/usr/bin/docker compose down
ExecReload=/usr/bin/docker compose up -d --remove-orphans
TimeoutStartSec=600

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now webstack
systemctl status webstack
journalctl -u webstack -f

13.5 Compose 常见问题

问题 原因 处理
service "x" depends on undefined service 服务名拼错或 profile 未启用 检查拼写;--profile
改了镜像 tag 但容器没变 compose 认为配置未变 up -d --force-recreate 或先 pull
端口冲突 两个项目都映射同一端口 改端口或用 --scale 时去掉固定端口
--scale 报错 服务设了 container_name 或固定端口 删掉 container_name,用 expose
网络残留 down 时未删网络 down --remove-orphans,或 docker network prune
变量为空 .env 不在工作目录 --env-file 显式指定
构建很慢 没有 .dockerignore 加 .dockerignore

14. Swarm 集群

虽然新项目基本都上 Kubernetes,但 Swarm 内置、零依赖,小规模仍有一席之地。

14.1 初始化

# manager
docker swarm init --advertise-addr 10.0.0.11
# 输出 join token

# worker 加入
docker swarm join --token SWMTKN-1-xxx 10.0.0.11:2377

# manager 加入(高可用,需奇数个)
docker swarm join-token manager

# 查看
docker node ls
docker info | grep -A5 Swarm

14.2 服务与栈

docker service create --name web --replicas 3 -p 80:80 nginx:1.25
docker service ls
docker service ps web
docker service logs -f web
docker service scale web=5
docker service update --image nginx:1.26 web
docker service update --rollback web
docker service rm web

# 栈(compose v3 部署)
docker stack deploy -c docker-stack.yml webstack
docker stack ls
docker stack services webstack
docker stack ps webstack
docker stack rm webstack

14.3 节点运维

docker node ls
docker node inspect <node> --pretty
docker node update --availability drain <node>     # 排空
docker node update --availability active <node>    # 恢复
docker node update --label-add disktype=ssd <node>
docker node promote <node>
docker node demote <node>
docker node rm <node>

14.4 退出与清理

docker swarm leave                 # worker
docker swarm leave --force         # manager(最后一个)
docker stack rm webstack
docker service rm $(docker service ls -q)
docker network rm $(docker network ls -q -f driver=overlay)

15. 日志与监控

15.1 日志驱动对比

驱动 docker logs 特点 适用
json-file 支持 默认,无上限需配轮转 通用
local 支持 二进制压缩,省 20~40% 追求磁盘效率
journald 支持 进 systemd journal 已统一 journald
syslog 不支持 转发到 syslog 传统日志中心
fluentd/fluent-bit 不支持 结构化转发 EFK
gelf 不支持 Graylog 已有 Graylog
awslogs/gcplogs 不支持 云日志 云环境
splunk 不支持 Splunk HEC 已有 Splunk
none 不支持 不记录 高吞吐无关日志
# 全局默认
# daemon.json: "log-driver": "json-file", "log-opts": {...}

# 单容器
docker run --log-driver local --log-opt max-size=50m --log-opt max-file=5 nginx:1.25

# 查看某容器用的驱动
docker inspect web -f '{{.HostConfig.LogConfig.Type}} {{json .HostConfig.LogConfig.Config}}'

15.2 日志查询

docker logs web
docker logs -f --tail 100 web
docker logs --since 30m web
docker logs --since "2026-11-05T08:00:00" --until "2026-11-05T09:00:00" web
docker logs -t web                     # 带时间戳
docker logs 2>&1 web | grep -iE 'error|exception'

# 多容器聚合
for c in $(docker ps -q); do
  echo "===== $(docker inspect -f '{{.Name}}' $c) ====="
  docker logs --tail 20 $c 2>&1 | tail -20
done

# 落盘位置
ls -lh /var/lib/docker/containers/*/*-json.log

15.3 事件流

docker events
docker events --filter 'type=container'
docker events --filter 'event=die' --filter 'event=oom'
docker events --since '1h' --until '10m'
docker events --format '{{.Time}} {{.Type}} {{.Action}} {{.Actor.Attributes.name}} {{.Actor.Attributes.exitCode}}'

15.4 指标

# daemon 指标(Prometheus 格式)
# daemon.json: "metrics-addr": "0.0.0.0:9323", "experimental": true
curl -s localhost:9323/metrics | grep -E '^docker_(container|image|engine)'

# 容器指标
docker stats --no-stream
docker stats --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}'

# cAdvisor
docker run -d --name cadvisor --restart=always \
  -p 8081:8080 \
  -v /:/rootfs:ro -v /var/run:/var/run:ro -v /sys:/sys:ro \
  -v /var/lib/docker/:/var/lib/docker:ro -v /dev/disk/:/dev/disk:ro \
  --privileged --device=/dev/kmsg \
  gcr.io/cadvisor/cadvisor:v0.49.1
curl -s localhost:8081/metrics | head

15.5 巡检脚本

#!/usr/bin/env bash
# /opt/scripts/docker-daily-check.sh
set -uo pipefail
echo "===== $(date '+%F %T') docker check ====="

systemctl is-active docker >/dev/null || echo "!! docker 未运行"
echo "--- 磁盘"; df -h /var/lib/docker | tail -1
echo "--- 空间明细"; docker system df
echo "--- 非运行容器"
docker ps -a --filter "status=exited" --format '{{.Names}}\t{{.Status}}' | head -20
echo "--- unhealthy"
docker ps --filter health=unhealthy --format '{{.Names}}\t{{.Status}}'
echo "--- 重启次数 >3"
docker ps -a --format '{{.Names}}\t{{.Status}}' | grep -E 'Restarting|\([4-9]\)'
echo "--- 大日志"
find /var/lib/docker/containers -name '*-json.log' -size +500M -printf '%s\t%p\n' 2>/dev/null
echo "--- 悬空镜像数"; docker images -f dangling=true -q | wc -l
echo "--- 异常事件(近 24h)"
docker events --since 24h --until 0s --filter 'event=oom' --filter 'event=die' --format '{{.Time}} {{.Action}} {{.Actor.Attributes.name}}' 2>/dev/null | tail -10

16. 备份、迁移与升级

16.1 备份什么

对象 是否必须 方式
自有镜像 是 push 到 Harbor,或 docker save
具名卷数据 是 tar 容器挂载,或应用层 dump
compose / run 脚本 是 Git 仓库
daemon.json 是 Git / 配置管理
/etc/docker/certs.d 是 加密备份
容器可写层 否 不应有状态,有状态说明设计有问题
# 全量镜像备份
docker images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>' \
  | xargs -r docker save | gzip > images-$(date +%F).tar.gz

# 只备份自有镜像
docker images --format '{{.Repository}}:{{.Tag}}' | grep '^harbor.internal' \
  | xargs -r docker save | gzip > private-images-$(date +%F).tar.gz

# 所有具名卷
for v in $(docker volume ls -q); do
  docker run --rm -v "$v":/data:ro -v /backup/volumes:/backup alpine \
    tar czf "/backup/$v.tar.gz" -C /data . 2>/dev/null && echo "ok $v"
done

16.2 迁移到新机器

# 旧机:导出
docker save -o /backup/images.tar $(docker images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>')
docker ps -a --format '{{.Names}}\t{{.Image}}\t{{.Ports}}' > /backup/containers.txt
docker inspect $(docker ps -aq) > /backup/containers-inspect.json   # 记录完整参数

# 新机:安装同版本 → 导入
dnf install -y docker-ce-29.9.3 docker-ce-cli-29.9.3 containerd.io
docker load -i /backup/images.tar
# 按 compose 或 run 脚本重建容器,卷单独恢复

绝对不要整体拷贝 /var/lib/docker。容器元数据里绑定了 network sandbox ID、cgroup 路径、日志文件句柄,跨机恢复经常出现:

  • 容器起不来(failed to create task: network sandbox not found)
  • 镜像层校验失败
  • 卷挂载点错乱

正确路径是「镜像 save/load + 卷 tar + 配置重建」。

16.3 升级

# 升级前检查
docker info | grep -i 'live restore'        # 必须 true
docker ps -q | wc -l                        # 记录容器数
docker version --format '{{.Server.Version}}'
systemctl status docker | head -3

# 升级(RHEL)
dnf versionlock delete docker-ce docker-ce-cli containerd.io
dnf update -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl restart docker
docker version

# 验证
docker ps -q | wc -l                        # 数量应一致
docker run --rm hello-world

# Debian
apt-mark unhold docker-ce docker-ce-cli containerd.io
apt update && apt install -y --only-upgrade docker-ce docker-ce-cli containerd.io

回滚

dnf downgrade -y docker-ce-29.9.2 docker-ce-cli-29.9.2 containerd.io-2.4.1
systemctl restart docker
# 注意:新版本改过数据格式(如 containerd image store)时可能无法回滚

17. 卸载与清理

卸载不可逆。执行前三件事:① 数据已备份;② 业务已迁移或停机窗口已批;③ docker info / docker ps -a / daemon.json 已留档。

17.1 标准卸载流程

# 1) 记录现场
docker info > /backup/docker-info-$(date +%F).txt
docker ps -a --no-trunc > /backup/docker-ps.txt
cp /etc/docker/daemon.json /backup/
docker images --digests > /backup/docker-images.txt

# 2) 停服务(注意 socket)
systemctl stop docker docker.socket containerd
systemctl disable docker docker.socket containerd

# 3) 卸载软件包(RHEL)
dnf remove -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin \
  docker-ce-rootless-extras docker-scan-plugin

# Debian
# apt purge -y docker-ce docker-ce-cli containerd.io \
#   docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

# 4) 删数据(★ 不可逆)
rm -rf /var/lib/docker
rm -rf /var/lib/containerd

# 5) 删配置
rm -rf /etc/docker
rm -f /etc/systemd/system/docker.service.d/*.conf
rm -f /etc/systemd/system/docker.service /etc/systemd/system/docker.socket
rm -f /etc/apt/sources.list.d/docker.list /etc/apt/keyrings/docker.gpg
rm -f /etc/yum.repos.d/docker-ce.repo
rm -f /etc/modules-load.d/docker.conf /etc/sysctl.d/99-docker.conf
systemctl daemon-reload

17.2 残留清理(重装前必做)

# 进程
ps -ef | grep -E 'dockerd|containerd|docker-proxy' | grep -v grep
pkill -9 dockerd 2>/dev/null

# socket 与 pid
rm -f /var/run/docker.sock /var/run/docker.pid /var/run/docker.sock.raw
rm -rf /run/docker /run/containerd

# 网桥与网卡
ip link show | grep -E 'docker|br-|veth'
ip link delete docker0 2>/dev/null
for i in $(ip -o link show | awk -F': ' '/br-/{print $2}'); do ip link delete "$i"; done
ip link delete vethxxx 2>/dev/null

# iptables 链(Docker 不会自动清)
for t in nat filter mangle; do
  for c in DOCKER DOCKER-USER DOCKER-ISOLATION-STAGE-1 DOCKER-ISOLATION-STAGE-2 \
           DOCKER-FORWARD DOCKER-BRIDGE; do
    iptables -t $t -F $c 2>/dev/null
    iptables -t $t -X $c 2>/dev/null
  done
done

# nftables
nft list ruleset | grep -i docker
# 若有 docker 表:nft delete table ip docker

# 用户组
getent group docker && groupdel docker

# 挂载残留
mount | grep -E '/var/lib/docker|overlay'
cat /proc/mounts | grep docker | awk '{print $2}' | xargs -r umount -l

# 验证
which docker || echo "已卸载"
systemctl list-unit-files | grep -i docker

17.3 只清数据不卸载(重置到出厂)

# 停所有容器
docker ps -aq | xargs -r docker stop
docker ps -aq | xargs -r docker rm -f

# 删所有镜像/卷/网络/缓存
docker images -aq | xargs -r docker rmi -f
docker volume ls -q | xargs -r docker volume rm
docker network prune -f
docker builder prune -a -f

# 彻底重来(等价于重装数据层)
systemctl stop docker.socket docker containerd
rm -rf /var/lib/docker /var/lib/containerd
systemctl start containerd docker

18. 故障速查

18.1 服务与启动

现象 排查 处理
Cannot connect to the Docker daemon systemctl status docker 启动服务;检查 DOCKER_HOST
permission denied ... docker.sock ls -l /var/run/docker.sock usermod -aG docker $USER 后重新登录
dockerd 起不来 dockerd --debug 前台 看第一条 error,多为 daemon.json 语法或存储驱动
重启后容器消失 docker ps -a 有但 stopped restart 策略为 no
重启后容器全挂 docker ps -a 空 data-root 丢了 / 挂载未恢复

18.2 网络

现象 排查 处理
容器内通 IP 不通域名 cat /etc/resolv.conf 用自定义网络;--dns
容器不能出网 sysctl net.ipv4.ip_forward 置 1;检查 ufw FORWARD
宿主访问不了映射端口 ss -lntp、iptables -t nat -L DOCKER 检查 firewalld;端口是否绑 127.0.0.1
外部访问不了,本机可以 iptables -L DOCKER-USER -n -v 云安全组 / 宿主防火墙
容器间不通 docker network inspect 不在同一网络
端口被占 ss -lntp | grep :8080 停占位进程或改端口
网络创建失败 docker network ls、IP 池 网段耗尽或冲突
大量 TIME_WAIT ss -s 开 net.ipv4.tcp_tw_reuse;检查短连接
conntrack 满 dmesg | grep conntrack 调大 nf_conntrack_max

18.3 存储与磁盘

现象 排查 处理
no space left on device df -h、docker system df 清镜像/缓存/日志
磁盘满但 du 对不上 lsof +L1 有进程持有已删文件,重启对应容器
容器写不进去 df -h、mount | grep ro 磁盘满导致自动 remount ro
read-only file system dmesg | tail 文件系统错误,需 fsck
镜像拉取慢 docker pull 手工测 配 registry-mirrors
overlay2 报错 xfs_info | grep ftype 需 ftype=1

18.4 容器运行

现象 排查 处理
容器秒退 docker logs、docker inspect -f '{{.State.ExitCode}}' 前台进程不驻留 / 启动即报错
OOMKilled docker inspect -f '{{.State.OOMKilled}}' 调大 -m;修内存泄漏;调 JVM 堆
docker stop 等 10s docker top 看 PID 1 入口没 exec;加 --init
时区不对 docker exec date -e TZ=Asia/Shanghai + 挂 /etc/localtime
中文乱码 docker exec locale 镜像装 langpacks-zh_CN / locales,设 LANG
挂载权限拒绝 ls -Z、ls -ln SELinux :z 或 chown
容器内 DNS 慢 docker exec time nslookup x 换 DNS;关 ndots:5 的重复查询

18.5 构建

现象 排查 处理
failed to solve: failed to fetch oauth token 网络/登录 docker login;配代理
no space left docker buildx du buildx prune
缓存不命中 --progress=plain 看 layer 调整 COPY 顺序
exec format error docker inspect 架构 --platform
构建很慢 --progress=plain 用 cache mount;换国内源

19. 安全加固

19.1 容器运行时加固

docker run \
  --read-only --tmpfs /tmp:rw,size=64m \
  --user 1000:1000 \
  --cap-drop ALL --cap-add NET_BIND_SERVICE \
  --security-opt no-new-privileges \
  --security-opt seccomp=/etc/docker/seccomp.json \
  --security-opt apparmor=docker-default \
  --pids-limit 200 \
  -m 2g --cpus 2 \
  nginx:1.25
手段 防的是什么
--user 非 root 容器逃逸后直接拿宿主 root
--read-only 落 webshell、篡改二进制
--cap-drop ALL 滥用 CAP_SYS_ADMIN 等特权能力
no-new-privileges setuid 提权
seccomp 危险系统调用(默认已拦 44 个)
apparmor/SELinux 越权访问宿主文件
--pids-limit fork 炸弹
不挂 docker.sock 通过 socket 控制宿主

19.2 docker 组的安全代价

# 任何能执行 docker 命令的用户,等价于宿主 root
docker run -v /:/host -it alpine chroot /host sh
# 已经是 root shell 了

# 缓解手段
# 1) 严格控组:getent group docker
# 2) 用 rootless Docker(dockerd-rootless-setuptool.sh install)
# 3) 开启 userns-remap,容器内 root 映射到宿主的非特权 UID
# daemon.json: "userns-remap": "default"

userns-remap 的代价:旧容器不可见、部分镜像(需真实 root 的)跑不了、卷权限要重新映射。

# 开启后
grep dockremap /etc/subuid /etc/subgid
docker run --rm alpine id          # uid=0 但宿主上对应 100000+

19.3 镜像与供应链

# 漏洞扫描
docker scout cves myapp:1.0.0
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image --severity HIGH,CRITICAL myapp:1.0.0

# 基线检查
docker run --rm --net host --pid host --userns host --cap-add audit_control \
  -v /etc:/etc:ro -v /var/lib:/var/lib:ro -v /var/run/docker.sock:/var/run/docker.sock:ro \
  -v /usr/lib/systemd:/usr/lib/systemd:ro -v /etc/audit:/etc/audit:ro \
  docker/docker-bench-security

# 内容信任(需 notary 服务)
export DOCKER_CONTENT_TRUST=1
docker pull harbor.internal/web/app:1.0.0

# 只用可信基础镜像
# - 内部 Harbor 代理缓存 + 白名单
# - 禁止 docker.io 直连(daemon.json 不配 mirror 时也无法拉取)

19.4 十条硬规则

  1. 生产禁用 --privileged,禁止挂载 /var/run/docker.sock。
  2. 镜像必须来自内部仓库,禁止 docker pull 任意公网镜像上生产。
  3. 不用 latest tag,用 digest 或不可变 tag。
  4. 容器内不以 root 运行。
  5. 根文件系统只读。
  6. 资源限制必配(内存、CPU、PID)。
  7. 日志必须轮转。
  8. 密钥用 secret 注入,不写 ENV。
  9. 定期重建基础镜像(CVE 累积)。
  10. 远程 API 只开 TLS(2376),永不开 2375。

20. 附录

20.1 退出码

码 含义
0 成功
1 应用错误
125 docker 自身错误
126 命令不可执行
127 命令不存在
128+N 信号 N
130 SIGINT(Ctrl+C)
134 SIGABRT
137 SIGKILL / OOM
139 SIGSEGV
143 SIGTERM

20.2 关键路径与端口

项 值
配置 /etc/docker/daemon.json
数据根 /var/lib/docker(可配)
容器日志 /var/lib/docker/containers/<id>/<id>-json.log
卷 /var/lib/docker/volumes/<name>/_data
构建缓存 /var/lib/docker/buildkit
socket /var/run/docker.sock
API 端口 2375(明文,禁用)/ 2376(TLS)
指标端口 9323(metrics-addr)
Swarm 2377(管理)/ 7946(节点通信)/ 4789(overlay VXLAN)

20.3 常用格式化输出

docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
docker ps --format '{{.Names}} {{.Label "env"}}'
docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}'
docker inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -q)
docker inspect -f '{{.Name}} {{.State.Status}} {{.RestartCount}}' $(docker ps -aq)
docker stats --no-stream --format '{{.Name}} {{.CPUPerc}} {{.MemUsage}}'
docker system df --format '{{.Type}}\t{{.TotalCount}}\t{{.Size}}\t{{.Reclaimable}}'

20.4 一键排障组合拳

# 容器起不来时的 5 条命令
docker ps -a | grep <name>
docker logs --tail 100 <name>
docker inspect <name> -f '{{json .State}}' | jq
docker inspect <name> -f '{{json .Config}}' | jq
docker events --since 10m --filter "container=<name>"
# 宿主整体体检
docker info
docker system df -v
docker ps -a --filter "status=exited"
docker images -f dangling=true
df -h /var/lib/docker
free -h
uptime
dmesg -T | tail -30

20.5 与工作区其他手册的配合

  • 宿主机操作系统:包管理、防火墙、SELinux、磁盘 → CentOS-7-10-常规运维指令手册.md / Ubuntu-常用运维指令手册.md
  • 容器编排:Kubernetes-常用运维指令手册.md
  • 无 daemon 运行时:Podman-常用运维指令手册.md
  • 低层运行时:containerd-常用运维指令手册.md
  • 私有仓库:Harbor-镜像仓库运维指令手册.md
  • 数据库容器:MySQL-常用运维指令手册.md(容器内 /var/lib/mysql ↔ 宿主 /data/mysql)

一句话总结:Docker 的运维难点集中在三处——存储(overlay2 空间与日志轮转)、网络(iptables 链与 firewalld 冲突)、生命周期(PID 1 与信号)。把这三块吃透,剩下都是查文档的事。

Docker 常用运维指令手册 - 知识库 - 起航社区