Docker 常用运维指令手册
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)
目录
- 版本与生命周期
- 安装部署
- 文件布局与运行机制
- daemon.json 配置详解
- 服务管理与启动排障
- 存储驱动与磁盘治理
- 镜像管理
- 容器运维
- 卷与绑定挂载
- 网络
- Dockerfile 指令全解
- 构建与 buildx
- Compose
- Swarm 集群
- 日志与监控
- 备份、迁移与升级
- 卸载与清理
- 故障速查
- 安全加固
- 附录
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/
三个关键结论:
- 镜像层只读共享。10 个容器用同一镜像,镜像层只存一份。
- 容器写时复制(CoW)。修改一个 100M 的文件,实际只复制被改的块,但
docker ps -s会显示整个文件大小。 - 删文件不释放空间。容器内
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,它有两个特殊行为:
- 不响应默认信号处理。PID 1 对未显式注册 handler 的信号会忽略,导致
docker stop等 10s 后强杀。 - 不回收僵尸进程。应用 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 的
dockerzone 由 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 十条硬规则
- 生产禁用
--privileged,禁止挂载/var/run/docker.sock。 - 镜像必须来自内部仓库,禁止
docker pull任意公网镜像上生产。 - 不用
latesttag,用 digest 或不可变 tag。 - 容器内不以 root 运行。
- 根文件系统只读。
- 资源限制必配(内存、CPU、PID)。
- 日志必须轮转。
- 密钥用 secret 注入,不写
ENV。 - 定期重建基础镜像(CVE 累积)。
- 远程 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 与信号)。把这三块吃透,剩下都是查文档的事。