尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Docker实战:Spring Boot + Vue 前后端分离项目容器化部署全攻略

发布时间:2026/9/9 19:54:02

资讯中心
01
ARTICLE

Docker实战:Spring Boot + Vue 前后端分离项目容器化部署全攻略

Docker实战:Spring Boot + Vue 前后端分离项目容器化部署全攻略
做前后端分离项目部署的时候我最常用也最推荐的方式就是 Docker。Spring Boot Vue 这套组合很多人在本机跑得特别顺一放到服务器就各种莫名其妙的问题JDK 版本不对、Nginx 配置不生效、MySQL 连不上、前端刷新就 404。这些问题本质上都是“环境不一致”造成的。把整个项目用 Docker 容器化之后镜像就是环境构建一次到处运行整个交付链路会变得非常可控。这篇文章我就把一次完整的 Docker 部署 Spring Boot Vue 实战记录拆开讲从最开始的目录规划、后端和前端镜像构建、Docker Compose 编排到迁移上线后最容易踩的几个坑全部摊开来说。适合刚接触容器化部署的 Java / 前端开发也适合正在准备把自己项目搬到服务器上的同学。1. 为什么前后端分离项目一定要走容器化——从痛点说起很多团队最开始部署前后端项目都是照着文档手动操作服务器上装个 JDK装个 Maven拉代码编译再装 Node前端打包再装 Nginx写配置最后装 MySQL、Redis。每一步都好像不难但组合起来就是灾难。因为每一台服务器的系统版本、已有环境、依赖版本都不一样文档写得再详细换台机器就未必能复现。我自己就经历过生产环境明明按文档一步步来的后端就是连不上数据库最后发现是 MySQL 8 的认证插件和客户端不兼容。容器化解决的就是这个核心问题把“环境”和“应用”一起打包。你的 Spring Boot 应用本地用 JDK 8 能跑放到容器里还是 JDK 8本地 MySQL 是 8.0服务器上的容器镜像也固定是 8.0。这样“在我电脑上是好的”这句话就变成了“在任何装了 Docker 的机器上都是好的”。1.1 没容器化之前的部署地狱可以想象一下这样的部署流程登录服务器检查是否已经装了 JDK版本是多少如果跟项目需要的不一致还得先处理旧的 JDK然后装 Maven拉代码mvn clean package编出 jar后端启动之后还要检查端口是不是被占用了。接着前端要在服务器上装 Node版本还有讲究——Vue 3 Vite 要求 Node 16 以上装完再npm install国内网络环境下这个步骤经常能卡住十几分钟最后再npm run build打出 dist 目录。然后配置 Nginx把 dist 指过去还要反代后端接口。光这一套下来一个熟练工也得折腾半天。这还只是第一次部署。等下次换一台服务器或者让别的同事接手一切从头再来所有踩过的坑重新踩一遍。1.2 容器化之后的目标架构我用 Docker 部署 Spring Boot Vue 项目最终跑起来的不是一个大容器而是四个互相协作的容器容器镜像职责frontendnginx托管 Vue 打包后的静态文件反向代理/api到后端backendeclipse-temurin (JDK)运行 Spring Boot 打出来的 jar 包mysqlmysql:8.0业务数据存储redisredis:6.2缓存、会话、验证码等逻辑依赖有人会问为什么前端不直接跟 Nginx 合在一起其实本来就是合在一起的——前端构建后会生成一堆静态文件这些文件放到 Nginx 镜像里由 Nginx 对外提供服务。严格来说“前端容器”就是一个装了静态资源的 Nginx。而后端、MySQL、Redis 各自独立容器互不干扰需要扩容时也可以单独对某个服务增加副本。这就是容器化带来的另一个好处服务隔离独立伸缩。2. 环境准备Docker 安装、镜像加速与目录规划动手前先说环境。这个项目我是在一台 Linux 服务器上部署的同时本地 Windows 开发机上也会用 Docker Desktop 做一些镜像构建验证。两边的安装方法不一样坑也不一样。2.1 Docker Desktop 与 Linux Docker 的选择如果你用 Windows 做开发机首选 Docker Desktop。它自带图形界面可以管理容器、看日志、一键清理对不熟悉命令行的同学很友好。但 Docker Desktop 在 Windows 上依赖 WSL2 或 Hyper-V安装之前必须确认两件事BIOS 里开启了 CPU 虚拟化Windows 功能里开启了“适用于 Linux 的 Windows 子系统”和“虚拟机平台”。如果你遇到启动时报错提示Docker Desktop failed to start because virtualisation support wasnt detected那几乎可以断定是虚拟化没开或者相关系统功能没启。解决办法是重启进 BIOS 打开虚拟化开关然后在“启用或关闭 Windows 功能”里把“Hyper-V”“Windows 虚拟机监控程序平台”“适用于 Linux 的 Windows 子系统”全部勾上重启后再启动 Docker Desktop。这一步在整个 Docker 学习过程中是最容易劝退新人的但其实都是基础设施问题。Linux 服务器上安装就简单多了以 CentOS 7 为例只需要执行sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io sudo systemctl start docker sudo systemctl enable docker装完验证一下docker version docker compose version2.2 镜像加速配置镜像加速这一节很关键。你去拉 nginx、mysql 这些官方镜像时如果直连官方 Docker Hub经常卡成几 KB/s一个几百兆的镜像能下半小时。解决办法是配置 registry mirror也就是“镜像加速器”。编辑/etc/docker/daemon.jsonWindows 上通过 Docker Desktop - Settings - Docker Engine 打开同样的文件{ registry-mirrors: [ https://docker.m.daocloud.io, https://docker.mirrors.ustc.edu.cn, https://hub-mirror.c.163.com ] }改完重启 Dockersudo systemctl daemon-reload sudo systemctl restart docker需要注意的是镜像加速地址的可用性经常变化如果某个源失效换一个即可。这也是我一直建议把镜像源配置提前写进团队文档的原因否则新同事部署时第一步就卡住。2.3 项目目录规划部署前先规划好目录不要随便把文件丢在 /root 下。我这次用一个统一的项目根目录/opt/myapp/ ├── docker-compose.yml ├── mysql/ │ ├── data/ │ └── init/ │ └── init.sql ├── redis/ │ └── data/ ├── backend/ │ ├── Dockerfile │ └── target/ │ └── app.jar └── frontend/ ├── Dockerfile ├── nginx.conf └── dist/这种规划的好处是数据目录、配置目录、构建目录各司其职后续备份/opt/myapp/mysql/data就是备份数据库。项目不要和 Dockerfile 放在同一个目录里也可以只要构建时指定好上下文路径就行但新手建议先按这个标准结构来。3. 后端镜像构建Spring Boot、MySQL 8.0 与 Redis 的容器化很多人一开始只写了一个 Dockerfile 把 Spring Boot 打进去结果数据库还在本机容器一重启地址就变了非常被动。我习惯先把数据层容器跑起来再让后端依赖它们。3.1 数据层先行MySQL 与 Redis 的 Compose 配置我单独写了一个docker-compose.data.yml用来启动 MySQL 和 Redis方便调试时不需要每次都把整个后端一起拉起来。version: 3.8 services: mysql: image: mysql:8.0 container_name: myapp-mysql restart: always environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: myapp TZ: Asia/Shanghai ports: - 3306:3306 command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci volumes: - ./mysql/data:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d redis: image: redis:6.2 container_name: myapp-redis restart: always command: redis-server --requirepass redis123 --appendonly yes ports: - 6379:6379 volumes: - ./redis/data:/data关于 MySQL 8.0 有两个值得注意的细节。第一是字符集虽然 MySQL 8 默认字符集已经是 utf8mb4但我仍然习惯显式指定避免某些历史版本或客户端连接时的隐式转换问题。第二是初始化脚本MySQL 官方镜像首次启动时会自动执行/docker-entrypoint-initdb.d目录下的.sql文件所以建表语句、初始化数据都可以放在./mysql/init/init.sql中第一次启动自动完成初始化这是非常方便的开发协作方式。Redis 这边我只设置了密码和 AOF 持久化。生产环境如果做缓存集群可以考虑主从结构镜像依然是 redis只需要改启动命令挂不同配置核心逻辑不变。3.2 后端 Dockerfile多阶段构建从 Maven 到 JRE后端 Dockerfile 我推荐用多阶段构建而不是“直接用 Maven 镜像跑 jar”。多阶段构建的好处是最终镜像里不包含 Maven、不包含源码、不包含编译期依赖体积会小很多生产环境也更安全。后端项目是 Spring Boot 2.x所以我用一个 JDK 8 / 11 环境的 Maven 镜像做构建阶段最后用 JRE 镜像做运行阶段FROM maven:3.8.6-eclipse-temurin-8 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests FROM eclipse-temurin:8-jre WORKDIR /app COPY --frombuilder /build/target/*.jar app.jar EXPOSE 8080 ENV TZAsia/Shanghai ENTRYPOINT [java, -jar, /app/app.jar]这里有两个细节值得展开说。第一个是mvn dependency:go-offline这一步会在构建阶段提前把项目所有依赖下载到本地 Maven 仓库后续mvn package就会快很多尤其是在 Docker 多阶段构建中每一层都会被缓存依赖没有变化时不会重复下载。第二个是-DskipTests构建 Docker 镜像时一般跳过测试因为单元测试应当在 CI 流水线阶段执行在这里跑反而浪费时间、放大不确定性。如果你用的是 Spring Boot 3.x那么基础镜像换成eclipse-temurin:17-jre或21-jre即可其他部分完全一样。3.3 application.yml 的容器化改造Spring Boot 项目里最常见的部署问题是数据库地址写死。本地开发时写localhost:3306没问题但部署到 Docker 里这个地址指向的是容器自身而不是 MySQL 容器。正确做法是把这些可变配置全部抽成环境变量。修改application.ymlspring: datasource: url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:myapp}?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/Shanghai username: ${DB_USERNAME:root} password: ${DB_PASSWORD:root123} redis: host: ${REDIS_HOST:localhost} port: ${REDIS_PORT:6379} password: ${REDIS_PASSWORD:redis123}然后在 docker-compose 里通过 environment 注入environment: DB_HOST: mysql DB_USERNAME: root DB_PASSWORD: root123 REDIS_HOST: redis REDIS_PASSWORD: redis123这里DB_HOST的值为mysql对应的是 compose 中 MySQL 服务名。在 Docker 网络里服务名可以直接当作主机名解析这就是容器编排里最常见的服务发现方式。4. 前端镜像构建Vue 项目打包与 Nginx 反向代理前端部分相比后端要简单一些但它有自己的坑Vue 项目是纯静态资源需要一个 Web 服务器来托管前后端分离意味着存在跨域问题还有一个很容易被忽略的——前端路由刷新 404 的问题。4.1 多阶段构建Node 打包再进 Nginx前端 Dockerfile 同样采用多阶段构建FROM node:16-alpine AS builder WORKDIR /build COPY package*.json ./ RUN npm config set registry https://registry.npmmirror.com npm install COPY . . RUN npm run build FROM nginx:1.24-alpine COPY --frombuilder /build/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80第一段用 Node 环境执行打包第二段把打包产物拷贝到 Nginx 镜像。这里需要注意npm install和npm ci的区别package-lock.json存在时建议用npm ci它严格按照锁定文件安装保证构建可复现速度也更快。上面写的是npm install因为用npm ci需要确保你项目里确实提交了 lock 文件这个根据自己的项目情况选。另外国内网络环境下npm 官方源下载慢是常态我在 Dockerfile 里用npm config set registry切换到了 npmmirror 镜像源这样构建时能稳定不少。4.2 nginx.conf 里的关键点history 路由与 /api 转发Nginx 配置是整个前端部署最容易出问题的地方。Vue Router 如果是 history 模式浏览器访问一个前端路由比如/user/123服务器上并不存在这个物理路径后端 Nginx 会返回 404。解决办法是用try_files把所有路径都兜底到 index.htmlserver { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://backend:8080; 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 /api/这一段是反向代理前端请求/api/loginNginx 会把请求转发给http://backend:8080/login。注意这里 proxy_pass 的写法http://backend:8080末尾没有带路径所以转发时会把原始 URI 原样带上若写成http://backend:8080/转发时会把/api/前缀去掉。这是最容易混淆的点。为什么是backend而不是localhost因为 Nginx 容器和后端容器都在同一 Docker 网络中backend 就是 compose 服务名是它们之间互相访问的主机名。如果 Nginx 容器里写localhost:8080指向的是 Nginx 容器自己一定连不通。4.3 播放 m3u8 视频流的 Nginx MIME 配置补充很多 Vue 项目会有视频播放功能比如播放 m3u8 格式的视频流。前端用 hls.js 或者 video.js 播放 m3u8但 Nginx 默认不认识.m3u8和.ts文件会返回application/octet-stream浏览器解析不了视频就黑屏。解决方法是显式加 MIME 类型location ~* \.m3u8$ { add_header Content-Type application/vnd.apple.mpegurl; } location ~* \.ts$ { add_header Content-Type video/mp2t; }这种场景在视频点播、直播回放类的系统里非常常见。如果你用 Nginx 托管这些静态视频文件记得把这段配置加上否则排查起来很难想到是 MIME 类型的问题。5. Docker Compose 编排服务启动顺序、网络通信与数据持久化前端和后端的镜像都准备好之后最后一口气整合成一个编排文件。我习惯所有的服务都放在一个docker-compose.yml里方便一条命令启动整个项目。5.1 一份完整的 docker-compose.yml下面是这份文件的核心内容version: 3.8 services: mysql: image: mysql:8.0 container_name: myapp-mysql restart: always environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: myapp TZ: Asia/Shanghai command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci volumes: - ./mysql/data:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -proot123] interval: 10s timeout: 5s retries: 5 redis: image: redis:6.2 container_name: myapp-redis restart: always command: redis-server --requirepass redis123 --appendonly yes volumes: - ./redis/data:/data healthcheck: test: [CMD, redis-cli, -a, redis123, ping] interval: 10s timeout: 5s retries: 5 backend: build: context: ./backend container_name: myapp-backend restart: always depends_on: mysql: condition: service_healthy redis: condition: service_healthy environment: DB_HOST: mysql DB_PORT: 3306 DB_NAME: myapp DB_USERNAME: root DB_PASSWORD: root123 REDIS_HOST: redis REDIS_PASSWORD: redis123 TZ: Asia/Shanghai ports: - 8080:8080 frontend: build: context: ./frontend container_name: myapp-frontend restart: always depends_on: - backend ports: - 3000:80使用命令启动docker compose up -d --build查看所有容器状态docker compose ps5.2 服务间通信容器名代替 localhost这是初学者最容易困惑的地方。在 compose 项目里Compose 会自动创建一个默认网络所有服务都会加入这个网络。Docker 内置 DNS 会把服务名解析成对应容器的 IP 地址所以后端容器里访问数据库只需用服务名mysql就可以访问 Redis 用redis前端 Nginx 里代理后端用backend。这和“一台服务器上部署用 localhost 访问”的思路完全不同因为每个容器都是独立的小环境都有自己的网络命名空间。你可以在后端容器里执行docker exec -it myapp-backend ping mysql如果网络通了你会发现它能解析出mysql这个主机名对应的内部 IP。这个内部 IP 会随容器重建而改变但服务名永远不变所以代码里千万不要写死 IP而是用服务名。5.3 数据持久化与初始化 SQL数据持久化是部署 MySQL、Redis 不可忽略的一步。Docker 容器的文件系统是临时的容器删了数据就没了。所以必须把容器内的数据目录挂载到宿主机MySQL 的数据目录/var/lib/mysql挂载到主机的./mysql/dataRedis 的 AOF 持久化文件目录/data挂载到主机的./redis/data这样做之后即使容器删掉重建数据依然保存在宿主机目录里下次启动自动加载。MySQL 初始化脚本的挂载也有讲究/docker-entrypoint-initdb.d目录下的.sql文件只会在容器首次创建且数据目录为空时执行。如果 MySQL 数据目录已经初始化过了后面再改 init.sql 是不会自动执行的。这是一个很有迷惑性的坑很多人以为改了 init.sql 再重启容器就会更新数据库实际并不会。要么进容器手动执行 SQL要么删掉data目录重新初始化注意会丢数据。5.4 健康检查与启动顺序优化depends_on只保证依赖服务的容器先启动不保证依赖服务“可用”。比如后端启动时 MySQL 容器刚启动MySQL 还没有完成初始化这时候后端去连数据库就会报连接拒绝然后整个应用启动失败而 Docker 的 restart 策略会反复重启后端容器运气好一会儿能起来运气不好就会一直循环。解决办法是给 MySQL 和 Redis 加上 healthcheck后端depends_on里指定等待健康状态通过再启动depends_on: mysql: condition: service_healthy redis: condition: service_healthy这样 Compose 会等待 MySQL 的mysqladmin ping成功、Redis 的redis-cli ping返回 PONG 之后才启动后端容器。这个优化在第一次启动时效果最明显能省掉很多日志刷屏和等待时间。6. 上线后最容易踩的坑从端口冲突到跨域调试部署完成并不代表万事大吉这里整理几个我在实际使用中反复踩过的坑每一个都有真实的故障案例。6.1 Docker Desktop 虚拟化检测失败这个问题在 Windows 开发机上非常典型报错信息就是热词里提到的Virtualization support not detected。系统把虚拟化关了Docker Desktop 自然起不来。排查思路是进入任务管理器 - 性能 - CPU看右下角“虚拟化”是否已启用。如果显示“已禁用”需要重启进 BIOS找到 Intel VT-x 或 AMD-V 的选项并打开。如果 CPU 虚拟化已经启用但 Docker Desktop 还是报这个错那就去“启用或关闭 Windows 功能”里把“基于虚拟化的安全性”相关功能关掉或者检查“内核隔离”的内存完整性设置。如果系统本身不支持 WSL2也可以把 Docker Desktop 的引擎切换成 Hyper-V 模式但 Hyper-V 会和其他虚拟机软件冲突需要根据实际情况取舍。6.2 Springfox 3.0.0 与 Spring Boot 2.6 的兼容问题如果你的 Spring Boot 项目集成了 Swagger 文档并且用的还是 springfox 3.0.0项目启动时很可能直接抛 NullPointerException导致容器一直重启处于 CrashLoopBackOff 状态。根因是 Spring Boot 2.6 之后默认的路径匹配策略从AntPathMatcher改成了PathPatternParserspringfox 3.0.0 没有适配新策略。解决办法是在application.yml中添加spring: mvc: pathmatch: matching-strategy: ant_path_matcher这个配置加上之后Swagger 页面就能正常访问了。类似这种兼容性问题在容器环境下更容易暴露因为容器一旦启动失败就不会像本地 IDE 那样给你一个红字报错以后继续等它会不断重启刷日志所以看日志的能力在容器化部署中格外重要。6.3 容器内时区与端口冲突容器默认时区是 UTC如果你没做任何设置Spring Boot 打印的日志时间、MySQL 存储的时间都会比北京时间慢 8 小时。解决方式有两个一是给每个容器设置环境变量TZ: Asia/Shanghai二是在 Dockerfile 里执行ENV TZAsia/Shanghai。两个一起用更稳妥。端口冲突是在同一台服务器上部署多个项目时很容易遇到的问题。比如项目 A 占用了 8080项目 B 也想用 8080启动就会报端口被占用。排查命令netstat -tlnp | grep 8080然后把 compose 里的端口映射改掉。但我更推荐暴露到不同端口比如项目 A 是 8080项目 B 是 8081内部容器端口不变只改宿主机映射端口。6.4 docker exec -it 排查容器的几个高频姿势部署完成之后真正有用的调试手段是进到容器内部看实际环境。我最常用的命令有这么几个# 查看容器日志 docker logs -f myapp-backend # 进入容器内部 docker exec -it myapp-backend /bin/bash # 查看容器环境变量 docker exec myapp-backend env # 查看容器网络连通性 docker exec myapp-backend ping mysql看到日志之后大部分问题都能定位。比如后端起不来先看是不是数据库连接失败数据库连不上先看是不是健康检查没通过健康检查失败再进 MySQL 容器看启动状态。这一套排查链路用熟了之后你会发现自己越来越依赖容器日志而不是“猜”。另外一个经验是docker compose up -d --build其实干了“构建镜像 创建容器 启动容器”三件事如果只想重启某个服务而不重新构建用docker compose restart backend如果改了 Dockerfile 或者代码需要重新构建再启动docker compose up -d --build backend最后说一个我自己的小习惯每次部署完我都会用一个特定的 URL 做一次完整的“冒烟测试”比如前端主页能打开登录接口能通数据库里的数据能查出来。这条链路通了整个容器化部署才算真正完成。平时排查问题时我的习惯也是先看容器状态再看日志最后才怀疑代码。大多数部署层的问题不需要改一行 Java 或者 Vue 代码只需要把 Docker 的配置调对。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。