Docker 系列(八):多阶段构建、镜像优化与生产安全
2026年7月21日 · 15 分钟
从「能跑」到「能上生产」
前七篇让你能构建、编排应用。但直接把开发镜像丢上线,往往又大又不安全。这一篇把 生产环境真正在乎的东西讲透:体积、安全、稳定性。
一、多阶段构建:镜像瘦身利器
问题:编译型/构建型应用需要一堆构建工具(编译器、devDependencies),但运行时根本 用不到。如果全打进镜像,体积会非常臃肿。
多阶段构建(multi-stage build) 的思路:用一个「构建阶段」编译产物,再把产物拷进 一个干净的「运行阶段」,构建工具全部丢弃。
# ---------- 阶段 1:构建 ----------
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci # 装全部依赖(含 devDependencies)
COPY . .
RUN npm run build # 产出 dist/
# ---------- 阶段 2:运行(干净的最小镜像)----------
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev # 只装生产依赖
COPY --from=builder /app/dist ./dist # 只拷构建产物
CMD ["node", "dist/server.js"]
关键在 COPY --from=builder:只从构建阶段挑出需要的产物,编译工具、源码、
devDependencies 统统留在被丢弃的 builder 阶段。最终镜像可能从 800MB 缩到 150MB。
你博客项目的 Dockerfile 用的正是这个思路——三阶段(deps / builder / runner), 最终只拷贝 Next.js 的
standalone产物,镜像非常小。
二、更多瘦身技巧
-
选对基础镜像:
alpine>slim> 完整版。 -
合并 RUN 层并清理缓存:
RUN apt-get update && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* # 同层清理,才能真正减小体积注意:清理必须和安装在同一条 RUN里。分成两条 RUN 的话,删除只是在上层「遮盖」, 底层文件仍占体积。
-
用 .dockerignore 排除
node_modules、.git、日志等(第四篇讲过)。 -
用
docker history/dive工具 分析每层占用,找出「体积大户」。
三、安全:别用 root 跑容器
默认情况下容器内进程以 root 运行,一旦被攻破,风险极大。生产镜像应该创建普通用户:
FROM node:22-alpine AS runner
WORKDIR /app
# 创建非 root 用户和组
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nextjs
COPY --chown=nextjs:nodejs . .
USER nextjs # 切换到非 root 用户
CMD ["node", "server.js"]
其他安全要点:
- 最小权限:只装必需的包,减少攻击面。
- 不要把密钥打进镜像:用环境变量或 secrets 注入,别
COPY .env(记得加进.dockerignore)。 - 固定基础镜像版本:用
node:22.11-alpine而非node:latest,避免不可控更新。 - 只读文件系统(进阶):
docker run --read-only,让容器无法被写入。
四、稳定性:健康检查与重启策略
在 Dockerfile 里加 HEALTHCHECK
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1
Docker 会定期探测,docker ps 的 STATUS 会显示 healthy / unhealthy,编排系统可
据此自动重启或摘流。
重启策略
docker run --restart unless-stopped ... # 除非手动停,否则挂了就重启
| 策略 | 行为 |
|---|---|
no | 默认,不自动重启 |
on-failure | 仅非正常退出时重启 |
always | 总是重启(包括 Docker 重启后) |
unless-stopped | 总是重启,但你手动停的除外(生产常用) |
五、资源限制
别让单个容器吃光宿主机资源:
docker run -d \
--memory="512m" \ # 内存上限 512MB
--cpus="1.5" \ # 最多用 1.5 个 CPU 核
myapp
Compose 里对应:
services:
web:
deploy:
resources:
limits:
memory: 512M
cpus: "1.5"
六、镜像安全扫描
上线前扫一遍已知漏洞:
docker scout cves myapp:latest # Docker 官方扫描工具
# 或社区流行的 trivy
trivy image myapp:latest
把扫描接入 CI,发现高危漏洞就阻断发布。
七、串起来:这正是你博客的部署链路
回顾我们给博客搭的 CI/CD,其实就是这套生产实践的落地:
git push
→ GitHub Actions 用【多阶段 Dockerfile】构建【小体积】镜像
→ 推送到镜像仓库(GHCR)
→ 服务器 docker pull + docker run(--restart unless-stopped)
→ Nginx 反向代理(容器只绑 127.0.0.1,不裸奔)
每一环都对应本系列的知识点:镜像分层、多阶段瘦身、非 root、重启策略、网络安全。
本篇小结
- 多阶段构建:构建产物与构建工具分离,镜像体积大幅缩小。
- 瘦身:选 alpine、同层清理缓存、用
.dockerignore。 - 安全:非 root 运行、不打包密钥、固定版本、最小权限。
- 稳定:
HEALTHCHECK+--restart unless-stopped+ 资源限制。 - 上线前用
docker scout/trivy扫描漏洞并接入 CI。
系列结语
从「容器是什么」到「生产级部署」,八篇走完,你已经具备了在真实项目里用好 Docker 的 完整能力:
- 理解容器与镜像的本质
- 熟练操作容器生命周期
- 编写高效的 Dockerfile
- 持久化数据、连通网络
- 用 Compose 编排多服务
- 按生产标准优化与加固
接下来最好的学习方式就是——找一个你自己的项目,把它 Docker 化并部署上线。动手, 才是掌握 Docker 的唯一捷径。