目前我使用的是dockers desktop进行前后端和各种中间件的部署,都在同一个网络环境中,我们开始进行后端的部署.
1.编写Dockerfile文件,Java的版本是Java21
XML
# 构建阶段
FROM eclipse-temurin:21-jre AS builder
WORKDIR /app
COPY target/*.jar app.jar
# 分层提取
RUN java -Djarmode=layertools -jar app.jar extract
# 运行阶段
FROM eclipse-temurin:21-jre
# 时区与用户设置(Ubuntu 使用 apt-get)
RUN apt-get update \
&& apt-get install -y tzdata \
&& rm -rf /var/lib/apt/lists/* \
&& ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& dpkg-reconfigure -f noninteractive tzdata \
&& groupadd -r appgroup && useradd -r -g appgroup appuser
WORKDIR /app
# 从构建阶段复制分层文件
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
# 创建日志目录,并赋予 appuser 完全的读写权限
RUN mkdir -p /app/logs && chown -R appuser:appgroup /app
# 切换到普通用户
USER appuser
EXPOSE 8090
# 启动命令(如果项目未启用虚拟线程,可去掉 -XX:+UseVirtualThreads)
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
这里稍微介绍以下用法:
关键指令拆解
| 指令 | 作用 | 使用技巧 |
|---|---|---|
FROM |
指定基础镜像 | 优先选择 -alpine(轻量)或 -slim,明确版本号(如 17-jre)避免"滚动更新"带来的兼容风险 |
WORKDIR |
设置工作目录 | 如果目录不存在会自动创建,建议使用绝对路径 |
COPY |
从构建上下文复制文件 | 利用 Docker 层缓存:将变化最少的指令(如依赖文件)放在前面,变化最频繁的(如业务代码)放在最后,以加速重复构建 |
RUN |
执行构建命令 | 每条 RUN 都会产生一个新层,尽量合并多条命令(如 && 连接),减少镜像层数 |
USER |
切换为普通用户 | 生产环境务必使用非 root 用户运行应用,提高安全性 |
EXPOSE |
声明容器监听端口 | 仅用于文档说明,不会自动映射端口,运行时仍需 -p 映射 |
ENTRYPOINT / CMD |
设置容器启动命令 | ENTRYPOINT 更适合作为固定启动命令,CMD 作为可覆盖的默认参数 |
其中Eclipse Temurin 是 Eclipse 基金会下的一个开源项目,它提供的是 OpenJDK(开源 Java 标准实现)的高质量、经过 TCK(技术兼容性工具包)测试的构建版本。
-
它就是 Java 本身 。你可以把它看作是官方的
Oracle JDK的免费、开源且可靠的替代品。 -
在 Dockerfile 中的作用 :它为你的 Spring Boot 应用提供了运行所需的 Java 虚拟机(JVM) 和基础类库。
21-jre-alpine 拆解
这个标签名由三个部分组成,对应了你问的"版本号":
| 字段 | 含义 | 说明 |
|---|---|---|
| 21 | Java 版本号 | 表示这个镜像内置了 Java 21 的运行时。这是目前 Spring Boot 3.x 版本的基线版本(必须 Java 17 及以上)。 |
-jre |
运行环境类型 | 代表 Java Runtime Environment (Java 运行时环境) 。它只包含运行 Java 程序所需的虚拟机(JVM)和核心类库,不包含 编译工具(javac)、调试工具等。 |
-alpine |
操作系统基础镜像 | Alpine 是一个极其轻量级的 Linux 发行版 ,体积非常小(约 5 MB)。使用它作为基础,能让你的最终镜像体积骤减。例如,eclipse-temurin:21-jre-alpine 镜像大小通常在 80 MB 左右,而 eclipse-temurin:21-jre(基于标准 Ubuntu)可能在 200 MB 以上。 |
Spring Boot 分层构建原理(优化重点)
在 Spring Boot 2.3+ 中,可以通过 layertools 将 fat jar 拆分为四个层:
• dependencies:大部分外部依赖(稳定,很少变动)
• spring-boot-loader:Spring Boot 启动器
• snapshot-dependencies:快照依赖(适中变动)
• application:你的业务代码(频繁变动)
为什么要分层?
• 当业务代码改动时,只需要重建 application 层,前几层可以直接复用 Docker 缓存,大幅缩短构建时间(从几分钟缩短到几秒)。
• 推送/拉取镜像时也更省流量。
2.接下来我们就可以编译Dockerfile文件了,通过wind+x组合键选择"终端(管理员)"的powershell,进入到项目所在目录的Dockerfile文件夹下面,比如我的是E:\dev\client-agent,我们可以通过以下命令进行Dockerfile文件的执行:
XML
# 编译项目,注意这里的有个点符号必须要有,不然就报错,它代表"当前目录",告诉 Docker 到哪里去找 Dockerfile 和相关文件
docker build --no-cache -t client-agent-backend:latest .
# 创建dockers 容器,并启动容器,容器名称是client-agent-backend,第一个8090是映射到外部的端口号,第二个8090是我们自己服务启动的端口号
docker run -d -p 8090:8090 --name client-agent-backend client-agent-backend:latest
# 如果发现容器执行不成功,可以删除容器
docker rm -f client-agent-backend
# 关闭容器
docker stop client-agent-backend
# 启动容器
docker start client-agent-backend
3.注意事项:
在我们的application.yml配置文件中,需要把所有的链接到其他服务的路径修改为host.docker.internal,这些服务也必须在相同的dockers服务路径下面,如果是其他的dockers服务路径,需要修改为相应的地址。
完成以上这些后,我们就可以正常启动dockers服务了。
接下来是前端服务的部署:
1.同样的我们在前端服务中配置Dockerfile文件,内容如下:
XML
# 第一阶段:构建阶段 (build stage)
FROM node:20-alpine AS builder
# 设置工作目录
WORKDIR /app
# 复制依赖清单文件 (利用 Docker 层缓存)
COPY package*.json ./
# 安装依赖 (如果网络慢,可配置国内镜像源)
RUN npm config set registry https://registry.npmmirror.com \
&& npm ci
# 复制项目源代码
COPY . .
# 执行项目构建 (生成 dist 或 build 目录)
RUN npm run build
# 第二阶段:运行阶段 (production stage)
FROM nginx:1.27-alpine
# 安装 tzdata 并设置时区 (可选)
RUN apk add --no-cache tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
# 复制自定义 Nginx 配置文件 (如果有)
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 从构建阶段复制构建产物到 Nginx 的静态目录
COPY --from=builder /app/dist /usr/share/nginx/html
# 暴露 80 端口
EXPOSE 80
# 启动 Nginx (前台运行,保持容器存活)
CMD ["nginx", "-g", "daemon off;"]
2.我们还需要提供nginx.conf配置文件,内容如下:
XML
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# ========== 核心修改:处理 API 请求 ==========
# 将所有 /api/ 开头的请求转发给后端服务
location /api/ {
# 后端服务地址(取决于你的部署环境)
# 方案 A:后端也跑在 Docker 里,用容器名访问(如 http://backend-server:8090)
# 方案 B:后端跑在宿主机上,用 host.docker.internal(Windows/Mac)或宿主机 IP
proxy_pass http://host.docker.internal:8090;
# 注意:如果后端地址带 /api/,则直接拼接;如果不带,比如 http://host:8090,可能会多一层路径,需留意斜杠
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# ========== 处理前端路由(保留你原有的) ==========
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存(可选)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
这里需要注意的是这里后端地址的配置,因为我们也是在同一个dockers下面,因此也配置成host.docker.internal,最后我们就可以启动前端服务了:
XML
# 这个同后端启动过程一样,Dockerfile文件的执行
docker build --no-cache -t client-agent-front:latest
# 给容器起名并启动容器,其中5173端口号是web页面的访问地址端口,80端口号是nginx.conf里面配置的
docker run -d -p 5173:80 --name client-agent-front client-agent-front:latest
# 容器出错删除容器
docker rm -f client-agent-front
# 关闭容器
docker stop client-agent-front
# 启动容器
docker start client-agent-front
到这里我们就完成了前后端的dockers容器化服务,如图
