最佳实践与陷阱汇总
本文件汇总 Docker 全生命周期的最佳实践、常见陷阱和安全建议。
镜像构建最佳实践
最小化镜像
# ❌ 差
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3
# ✅ 好
FROM python:3.11-slim # 使用 slim/alpine 基础镜像| 基础镜像 | 大小 | 适用 |
|---|---|---|
ubuntu:22.04 |
~77 MB | 通用,包多 |
debian:slim |
~30 MB | 通用精简版 |
alpine:3.18 |
~7 MB | 极致精简(使用 musl libc) |
scratch |
0 MB | 无依赖的静态二进制 |
🚨 alpine 陷阱:Alpine 使用 musl libc 而非 glibc,某些预编译的二进制程序可能无法运行。此外 DNS 解析行为也有差异。
层缓存优化
# ❌ 差:依赖文件变了 → go mod download 缓存失效
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go mod download
RUN go build -o server .
# ✅ 好:只复制依赖文件 → 只有 go.mod 变了才重新下载
FROM golang:1.21
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download # ← 这步被缓存,除非 go.mod/go.sum 变化
COPY . .
RUN go build -o server . # ← 这步被缓存,除非源码变化💡 原则:把变化最频繁的步骤(如 COPY 源码)放在最后,让稳定步骤(如下载依赖)充分利用缓存。
减少镜像层数
# ❌ 多 RUN = 多层
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN rm -rf /var/lib/apt/lists/*
# ✅ 合并 + 清理
RUN apt-get update && \
apt-get install -y --no-install-recommends curl vim && \
rm -rf /var/lib/apt/lists/*⚡
--no-install-recommends不安装推荐的额外包,可显著减小镜像体积。
使用 .dockerignore
# .dockerignore
**/node_modules
**/.git
**/README.md
**/.env
**/dist
**/.cache
**/*.log
.git
.idea
.vscode容器运行最佳实践
不要以 root 运行
# ✅ 创建应用用户
RUN groupadd -r --gid 1000 appuser && \
useradd -r --uid 1000 -g appuser appuser
USER appuser
# ✅ 在 docker run 时指定
docker run --user 1000:1000 my_app正确选择重启策略
| 重启策略 | 适用场景 |
|---|---|
no |
开发调试 |
always |
始终需要运行的守护进程(数据库、Web 服务) |
unless-stopped |
同 always,但允许手动停止(推荐) |
on-failure |
批处理任务(失败时希望重试几次) |
资源限制
# 避免一个容器占满宿主机资源
docker run --memory 256m --memory-swap 512m --cpus 0.5 my_app| 参数 | 说明 |
|---|---|
--memory / -m |
内存上限 |
--memory-swap |
内存+Swap 上限 |
--cpus |
CPU 配额(1.5 = 1.5 核) |
--cpuset-cpus |
绑定特定 CPU 核心 |
--blkio-weight |
磁盘 IO 权重 |
--pids-limit |
进程数上限 |
时区和语言设置
FROM alpine:3.18
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone && \
apk del tzdata
ENV LANG=C.UTF-8安全最佳实践
敏感信息管理
# ❌ 不要在 Dockerfile 中硬编码密码
# ENV DB_PASSWORD=secret123
# ❌ 不要在 docker run 的 -e 中用明文
# 它会出现在 docker inspect 和历史中
# ✅ 使用 Docker Secrets(Swarm)
echo "secret123" | docker secret create db_password -
docker service create --secret db_password ...
# ✅ 使用 Docker Compose 的 secrets
# secrets:
# db_password:
# file: ./secrets/db_password.txt
# ✅ 使用外部密钥管理服务(Vault、AWS Secrets Manager)镜像漏洞扫描
# Docker Scout(内置)
docker scout quickview nginx:latest
docker scout cves nginx:latest
# Trivy(第三方)
trivy image nginx:latest
# Snyk
snyk container test nginx:latest💡 安全建议:定期扫描生产镜像的 CVE 漏洞,使用固定版本号而非
latest,及时更新基础镜像。
文件系统只读
# 以只读模式运行容器(/tmp 除外)
docker run --read-only --tmpfs /tmp --tmpfs /run nginx能力限制
# 移除所有 capabilities,再按需添加
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginxLinux Capabilities 允许以非 root 身份获取特定权限:
| Capability | 用途 |
|---|---|
NET_BIND_SERVICE |
绑定 1024 以下端口 |
NET_RAW |
使用原始套接字 |
SYS_PTRACE |
进程追踪 |
SYS_TIME |
修改系统时间 |
Dockerfile 陷阱速查
| 🚨 陷阱 | 说明 | ✅ 正确做法 |
|---|---|---|
latest 飘移 |
今天拉的和明天拉的可能不一样 | 使用固定版本号 nginx:1.25.3 |
| CMD 多次定义 | 只有最后一个生效 | 只写一个 CMD |
| Shell 形式 CMD | 信号传递有问题 | 用 Exec 形式 CMD ["exe", "arg1"] |
| ADD 从 URL 下载 | 无缓存校验 | 用 RUN curl/wget |
apt-get update 独立层 |
缓存导致过时 | 合并到同一个 RUN |
| 上下文目录过大 | 整个目录发给 daemon | 用 .dockerignore |
| VOLUME 后写数据 | 修改会被丢弃 | VOLUME 前完成数据写入 |
| 复制后用 chmod | 每次都产生新的层(copy-on-write) | 如果可能,在 COPY 时用 --chown |
Compose 陷阱速查
| 🚨 陷阱 | 说明 | ✅ 正确做法 |
|---|---|---|
depends_on 不管应用就绪 |
容器启动了 ≠ 数据库可用了 | 加 healthcheck + condition: service_healthy |
| 直接 scale Web | 端口冲突 | 用反向代理分发,或只 scale 无端口映射的服务 |
未指定 restart |
进程异常退出后容器不动 | 加 restart: unless-stopped |
| env 和 env_file 混用 | 优先级混乱 | 明确一个来源,需要覆盖时用 environment |
down 不删卷 |
旧卷占用空间 | 需要时用 down -v 或手动管理 |
| 网络连接外网问题 | internal: true 导致无法拉取依赖 |
区分前后端网络 |
| 版本号空缺 | image: nginx 拉 latest |
写死 image: nginx:1.25 |
调试与排错
常用调试命令
# 查看容器启动失败原因
docker logs <id>
# 覆盖 ENTRYPOINT 手动调试
docker run -it --rm --entrypoint /bin/sh my_app
# 查看容器退出码
docker inspect -f '{{.State.ExitCode}}' <id>
docker inspect -f '{{.State.Error}}' <id>
# 对比容器和镜像的差异
docker diff <id>
# 查看容器的完整启动命令
docker inspect -f '{{.Path}} {{.Args}}' <id>
# 查看容器资源使用
docker stats --no-stream
# 导出容器文件系统
docker export <id> | tar -tv | less常见退出码
| 退出码 | 含义 |
|---|---|
| 0 | 正常退出 |
| 1 | 应用错误 |
| 125 | docker run 自身失败 |
| 126 | 命令无法执行(权限问题) |
| 127 | 命令未找到 |
| 137 | 被 SIGKILL 终止(通常 OOM) |
| 139 | SIGSEGV 段错误 |
| 143 | 被 SIGTERM 终止 |
🚨 137 退出码通常是因为容器内存不足被内核 OOM Killer 杀掉。检查
--memory限制,或增大内存。
OOM 调试
# 查看是否有 OOM 事件
docker inspect --format='{{.State.OOMKilled}}' <container_id>
# 查看系统日志
dmesg | grep oom性能优化清单
| 方面 | 建议 |
|---|---|
| 镜像 | 多阶段构建,alpine/slim 基础镜像,清理包缓存 |
| 构建 | 利用层缓存,.dockerignore,BuildKit 缓存挂载 |
| 存储 | overlay2 驱动,命名卷优于绑定挂载(IO 方面) |
| 网络 | 用自定义 bridge 代替默认 bridge(DNS 解析开销) |
| 日志 | 限制日志大小 --log-opt max-size=10m --log-opt max-file=3 |
| 资源 | 始终设置 --memory 和 --cpus 限制 |
| 启动 | 使用健康检查避免将流量路由到未就绪容器 |
日常运维 Checklist
- 定期
docker system prune清理无用资源 - 定期扫描镜像安全漏洞(
docker scout/trivy) - 生产环境所有镜像使用固定 tag,不用
latest - 所有服务配置
restart和healthcheck - 设置资源限制(memory / cpus)
- 容器以非 root 用户运行
- 敏感信息不写入镜像或环境变量
- 日志配置轮转策略,防止磁盘写满
- 卷数据定期备份