Skip to content
最佳实践与陷阱汇总

最佳实践与陷阱汇总

本文件汇总 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 nginx

Linux 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
  • 所有服务配置 restarthealthcheck
  • 设置资源限制(memory / cpus)
  • 容器以非 root 用户运行
  • 敏感信息不写入镜像或环境变量
  • 日志配置轮转策略,防止磁盘写满
  • 卷数据定期备份