Skip to content
Docker 与 Nginx 部署 · 第 3 篇 / 共 5 篇
领域运维与部署
专题容器与站点部署专题
当前序列Docker 与 Nginx 部署
阅读位置第 3 篇 / 共 5 篇当前专题第 1 个序列 / 共 4 个序列

Dockerfile 怎么写更稳:基础指令、分层思路和多阶段构建

会写一个能跑的 Dockerfile 不难,难的是把它写得更小、更稳,也更适合团队长期维护。

如果你后面准备把 Spring Boot、网关、任务服务逐步挂到现在这台腾讯云服务器上,多阶段构建几乎是最值得先养成的习惯。

先说结论

Dockerfile 最容易踩坑的地方通常不是语法,而是构建思路:

  • 基础镜像选得过重
  • 构建工具和运行环境混在一起
  • 不必要文件全进了镜像
  • 层太多,构建缓存也用不好

所以更推荐的思路是:

  • 构建阶段负责编译和打包
  • 运行阶段只保留真正需要的产物
  • 依赖下载尽量吃到缓存
  • 最终镜像尽量只保留运行时

一、先理解几个基础指令

FROM

指定基础镜像:

dockerfile
FROM eclipse-temurin:21-jdk

WORKDIR

设置工作目录:

dockerfile
WORKDIR /app

COPY

把文件复制进镜像:

dockerfile
COPY target/app.jar /app/app.jar

RUN

在镜像构建阶段执行命令:

dockerfile
RUN apt-get update

ENV

定义环境变量:

dockerfile
ENV TZ=Asia/Shanghai

ENTRYPOINT

定义容器启动入口:

dockerfile
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

二、为什么分层思路很重要

Docker 镜像不是一个整体文件,而是一层一层叠起来的。

这意味着:

  • 指令顺序会影响缓存命中
  • 经常变化的内容应尽量放后面
  • 大量无关文件进入上下文会拖慢构建

例如 Java 项目如果每次都把整个工程一股脑 COPY . .,那么哪怕只是改了一行业务代码,前面依赖下载那层也可能重新失效。

三、一个单阶段 Dockerfile 的基本写法

dockerfile
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/demo.jar /app/demo.jar
ENTRYPOINT ["java", "-jar", "/app/demo.jar"]

这能跑,但有个前提:

  • Jar 已经在宿主机提前打好了

如果你希望镜像构建过程自己完成编译,或者想把 CI、测试环境、生产环境构建链路统一起来,就更适合用多阶段构建。

四、多阶段构建为什么更适合 Java

典型写法:

dockerfile
FROM maven:3.9.9-eclipse-temurin-21 AS builder
WORKDIR /workspace
COPY pom.xml .
RUN mvn -B -q -DskipTests dependency:go-offline
COPY src ./src
RUN mvn -B -DskipTests clean package

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=builder /workspace/target/demo.jar /app/demo.jar
ENTRYPOINT ["java", "-jar", "/app/demo.jar"]

这个写法的好处很明显:

  • Maven 和编译缓存只存在于构建阶段
  • 最终运行镜像更小
  • 运行环境更纯净
  • 安全面和维护成本都更好控制

这里多加一步 dependency:go-offline 的意义是:

  • pom.xml 不变时,依赖层更容易命中缓存
  • 业务代码变化不会让依赖下载反复执行
  • CI 构建时间通常会更稳定

五、放到真实部署链路里,Dockerfile 负责的边界是什么

这点特别容易混。

对你现在这类服务器环境,更推荐把职责拆清楚:

  • Dockerfile 负责“镜像怎么构建”
  • Compose 或 docker run 负责“容器怎么运行”
  • Nginx 负责“入口、域名、HTTPS、静态资源或反向代理”
  • 宿主机目录负责“配置、日志、挂载数据、备份”

也就是说,不要在 Dockerfile 里试图把所有运行期问题都解决掉,比如:

  • 写死数据库地址
  • 写死环境端口
  • 把生产配置直接打进镜像
  • 把日志目录写成容器内临时路径却不挂载

Dockerfile 越像“可复用构建说明书”,后面越容易长期维护。

六、Java 项目写 Dockerfile 的几个实用建议

1. 尽量使用运行时镜像

如果只是启动 Jar,运行阶段优先考虑 jre 或更轻量的运行时镜像,而不是完整 jdk

2. 配合 .dockerignore

把这些目录排除掉通常很有必要:

  • .git
  • node_modules
  • logs
  • 本地临时文件

否则构建上下文会变大,速度也会受影响。

3. 不要把配置硬编码死

例如:

  • 端口
  • Profile
  • 外部服务地址

更适合通过环境变量或挂载配置文件传入。

4. 明确区分构建和运行责任

Dockerfile 负责镜像构建,真正的端口、挂载目录、网络和重启策略,通常应该放到运行层,比如 Compose。

5. 应用最好不要默认以 root 运行

如果镜像要长期在线上跑,更稳的做法是:

  • 创建专用用户
  • 只给应用目录必要权限

例如:

dockerfile
RUN useradd -r -s /sbin/nologin appuser
USER appuser

对于纯个人项目这不是强制项,但如果后面会继续接更多服务,这一步很值得尽早养成。

6. 用固定版本,不要长期依赖模糊标签

像下面这种写法虽然方便:

dockerfile
FROM maven:latest

但它会让构建结果变得不稳定。更推荐固定大版本甚至小版本,至少要让团队知道“这次构建到底基于什么环境”。

七、一个更贴近 Spring Boot 的多阶段示例

dockerfile
FROM maven:3.9.9-eclipse-temurin-21 AS builder
WORKDIR /workspace

COPY pom.xml .
RUN mvn -B -q -DskipTests dependency:go-offline

COPY src ./src
RUN mvn -B -DskipTests clean package

FROM eclipse-temurin:21-jre
WORKDIR /app

RUN useradd -r -s /sbin/nologin appuser
COPY --from=builder /workspace/target/*.jar /app/app.jar
RUN chown -R appuser:appuser /app

USER appuser
ENV TZ=Asia/Shanghai
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

如果后面你的知识库旁边再挂一个 Java API 服务,这类 Dockerfile 就已经很适合作为起点了。

八、常见误区

1. 把整个 JDK、源码、Maven 仓库都带进最终镜像

这会让镜像明显变大,也没必要。

2. 为了方便直接 COPY . .

短期方便,长期很容易把无关文件也带进去。

3. 把“构建成功”和“运行稳定”混为一谈

镜像能 build 通过,不代表启动时目录、端口、配置和依赖一定都正常。

4. 以为 Dockerfile 越“全能”越好

真正稳定的部署链路,往往是每层只处理自己的职责,而不是把构建、配置、证书、数据都硬塞进一个 Dockerfile 里。

一句话总结

Dockerfile 的目标不只是“打出一个镜像”,而是把构建过程写成一份可维护、可复用、可复现的产物说明。

对 Java 项目来说,多阶段构建几乎是最值得优先掌握的一步,因为它能同时改善镜像体积、清晰度和部署稳定性。

延伸阅读相关文章优先当前专题,再补跨专题关联。
同一序列 · 顺着当前主线继续读Nginx 两个最常见的用法适合把容器基础、Compose、Dockerfile 和 Nginx 站点部署放在一组里连续看。容器与站点部署专题 · Docker 与 Nginx 部署同一序列 · 回看前文会更完整Docker Compose 实战入门适合把容器基础、Compose、Dockerfile 和 Nginx 站点部署放在一组里连续看。容器与站点部署专题 · Docker 与 Nginx 部署同专题其他序列 · 共享标签:Docker、部署交付Docker bridge、host、overlay 网络怎么选适合把镜像层、网络模式和数据卷选择放在一起看。容器与站点部署专题 · Docker 镜像与网络存储细节同专题其他序列 · 共享标签:部署交付多阶段构建之外还有哪些瘦身手段适合把镜像分层、镜像瘦身、时区日志、静态缓存和 WebSocket 代理放进一条容器交付主线上看。容器与站点部署专题 · 容器镜像与站点交付细节跨专题关联 · 共享标签:Docker、部署交付GitLab CI/CD 实战:Docker 构建镜像并通过 SSH 部署到服务器适合沿着流水线设计、自动部署、发布策略、数据库变更和多环境治理这条线往下读。CI/CD 与发布治理专题 · CI/CD 与发布治理跨专题关联 · 同场景:部署交付标签基数为什么会拖垮 Prometheus适合把 Prometheus、Grafana、标签治理和告警分级放在一起看。可观测性专题 · 指标与告警体系设计
继续阅读Docker 与 Nginx 部署当前序列第 3 篇 / 共 5 篇当前专题第 1 个序列 / 共 4 个序列
往前看
上一篇Docker Compose 实战入门回到当前序列上一章上一序列Linux 基础与服务器治理从第 1 篇开始:df 和 du 到底有什么区别
往后看
下一篇Nginx 两个最常见的用法继续当前序列下一章下一序列Docker 镜像与网络存储细节从第 1 篇开始:镜像层和构建缓存是怎么工作的

把零散经验整理成可查、可复用、可持续更新的企业级知识门户