【避坑实录】Vue3+SpringBoot项目Docker化部署:从403报错、连接拒绝到一键启动的完整复盘

前言:为什么要折腾Docker?

作为学生,欢迎各位访问我的初次正式项目WenJi并指出问题,感激不尽🙏

最近完成了项目的开发。在本地开发时,一切都很美好:IDEA启动后端,Vite启动前端,联调顺畅。

但当我想把项目部署到服务器,或者打包发给同学演示时,问题来了:

  • 对方电脑没装JDK 17怎么办?
  • 本地MySQL版本太低不支持怎么办?
  • Nginx反向代理配置太麻烦,不想在服务器上手动改配置文件怎么办?

为了解决这些“环境不一致”的噩梦,我决定尝试学习采用 Docker 容器化技术。本以为 docker-compose up 一下就能搞定,结果从镜像源配置到网络通信,一路踩雷。本文将结合“文及”项目的实际代码,分享从本地开发到 Docker 化的完整实战过程,重点记录那些**“如果不注意就会卡一两天”**的坑。

项目架构与目录规划

为了实现容器化编排,我们需要对项目结构进行标准化梳理。针对“文及”项目,我设计了如下的目录结构,核心在于通过 docker-compose.yml 统一管理所有服务:

WenJi/
├── docker-compose.yml            # 核心编排文件
├── .env                          # 环境变量(存放数据库密码等敏感信息)
├── backend/                      # 后端 SpringBoot 模块
│   ├── Dockerfile                # 后端镜像构建脚本
│   └── src/main/resources/
│       └── sql/                  # 数据库初始化脚本
└── frontend/                     # 前端 Vue3 模块
    ├── Dockerfile                # 前端镜像构建脚本
    └── nginx.conf                # Nginx 反向代理配置

踩坑实录与解决方案

坑点一:镜像下载慢与403报错(网络问题)
现象:执行 docker-compose up 时,控制台报错:

ERROR: failed to solve: pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed

或者下载速度只有几KB/s,甚至直接超时。

原因分析:

  • Docker Hub 官方源在国内访问受限。
  • 很多网上的教程推荐的加速器(如某些公共源)现在需要登录认证,导致匿名访问时被拒绝(403 Forbidden)。

** 解决方案**:
推荐使用中科大或网易的公共镜像源,这两个目前不需要认证且比较稳定。

操作步骤:

  1. 打开 Docker Desktop -> Settings -> Docker Engine。
  2. 修改 registry-mirrors 配置:
{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com"
  ]
}
  1. 点击 Apply & Restart。
  2. 关键一步:清理缓存,否则 Docker 还会尝试用旧的错误地址。
docker system prune -a -f

坑点二:OpenJDK镜像失效(基础镜像问题)
现象:在构建后端镜像时,提示 openjdk:17-jdk-slim 拉取失败,或者拉下来的镜像运行报错。

原因分析:
Oracle 修改了 OpenJDK 的授权协议,Docker Hub 上的官方 openjdk 镜像已经不再维护或存在兼容性问题。

** 解决方案**:
换成 Eclipse 基金会维护的 eclipse-temurin 镜像(OpenJDK 的开源发行版,完全兼容)。

修改后端 Dockerfile:

# --- 运行阶段 ---
# 推荐使用 Alpine 版本,体积仅为 200MB 左右
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 复制构建好的 jar 包
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

坑点三:后端连不上数据库(网络通信问题)
现象:后端容器启动后,日志疯狂报错:

java.sql.SQLException: Communications link failure
...
Caused by: java.net.ConnectException: Connection refused

原因分析:
这是新手最容易踩的坑!在 SpringBoot 的 application.yml 或环境变量中,我们习惯写 jdbc:mysql://localhost:3306...。
但在 Docker 容器内部,localhost 指的是容器自己,而不是宿主机,更不是旁边的 MySQL 容器。

** 解决方案**:
使用 Docker Compose 的服务名(Service Name)作为主机名。Docker Compose 会自动构建一个内部 DNS,让容器之间可以通过服务名互相访问。

修改 docker-compose.yml:

services:
  # 1. 数据库服务
  wenji-mysql:
    image: mysql:8.0
    container_name: wenji-mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: wenji_db
    volumes:
      - mysql_data:/var/lib/mysql
    # 注意:这里的服务名 wenji-mysql 就是后端要用的 host

  # 2. 后端服务
  wenji-backend:
    build: ./backend
    container_name: wenji-backend
    depends_on:
      - wenji-mysql
    environment:
      # 关键点:使用容器名 wenji-mysql 代替 localhost
      # 端口使用容器内部端口 3306,不要用宿主机的映射端口
      SPRING_DATASOURCE_URL: jdbc:mysql://wenji-mysql:3306/wenji_db
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    ports:
      - "8080:8080"

坑点四:容器重启数据丢失(数据持久化问题)
现象:在“文及”项目里新建了文档,重启容器(docker-compose restart)或者删除容器后,再次访问发现数据全没了,回到了初始状态。

原因分析:
容器是“用完即扔”的 ephemeral(临时)环境。MySQL 默认将数据存储在容器内的 /var/lib/mysql 目录。一旦容器被删除,这个目录也就随之消失了。

** 解决方案**:
使用 Volume(数据卷) 挂载,将容器内的数据目录映射到宿主机(或 Docker 管理的命名卷)。

修改 docker-compose.yml:

services:
  wenji-mysql:
    # ...其他配置
    volumes:
      - mysql_data:/var/lib/mysql  # 使用命名卷,Docker 会自动管理存储位置
      # 或者使用绑定挂载:- ./mysql_data:/var/lib/mysql

# 在文件末尾定义卷
volumes:
  mysql_data:

这样,即使你把容器删得干干净净,数据依然安全地保存在 mysql_data 卷中,下次启动新容器挂载该卷,数据依然存在。

坑点五:中文乱码(编码与字体问题)
现象:
项目启动后,前端页面显示的中文变成问号,终端查询数据库数据和查看后端日志时,中文日志显示为乱码。

原因分析:
这是一个“连环坑”,涉及三个层面:

  1. MySQL 层面:Docker 官方 MySQL 镜像默认字符集可能是 latin1,不支持中文。
  2. 操作系统层面:为了追求镜像体积最小化,后端使用的 Alpine 或精简版 Ubuntu 镜像通常默认不包含中文字体库,且系统语言环境(Locale)默认为 POSIX 或 C,不支持 UTF-8。
  3. JVM 层面:Java 应用如果没有显式指定编码,会继承操作系统的默认编码,导致读写文件时出现乱码。

解决方案:

1. 修改 MySQL 配置
在项目中创建一个 my.cnf 配置文件,并挂载到容器中,强制指定字符集为 utf8mb4。

  • backend/conf/my.cnf 内容:

    [mysqld]
    character-set-server=utf8mb4
    collation-server=utf8mb4_unicode_ci
    [client]
    default-character-set=utf8mb4
    
  • 修改 docker-compose.yml 挂载配置:

    services:
      wenji-mysql:
        volumes:
          - ./backend/conf/my.cnf:/etc/mysql/conf.d/my.cnf  # 挂载自定义配置
          - mysql_data:/var/lib/mysql
    

2. 修复后端镜像的 Locale 与字体
修改后端的 Dockerfile,安装中文字体支持并设置环境变量。

  • 修改 backend/Dockerfile:

    FROM eclipse-temurin:17-jre-alpine
    
    # 1. 安装字体库和中文字体包
    RUN apk add --no-cache ttf-dejavu fontconfig && \
        apk add --no-cache font-noto-cjk
    
    # 2. 设置系统语言环境变量
    ENV LANG=zh_CN.UTF-8
    ENV LC_ALL=zh_CN.UTF-8
    
    WORKDIR /app
    COPY target/*.jar app.jar
    
    # 3. 启动时强制指定 JVM 文件编码
    ENTRYPOINT ["java", "-Dfile.encoding=UTF-8", "-jar", "app.jar"]
    
最终成果:一键启动的快感

经过上述避坑,我们的 docker-compose.yml 终于能完美工作了。现在,无论是部署到服务器,还是发给同学演示,只需要做一件事:

在项目根目录执行:

docker-compose up -d

Docker Compose 会自动:

  1. 拉取 MySQL、Nginx 基础镜像。
  2. 根据 Dockerfile 构建后端和前端的自定义镜像。
  3. 启动容器,并按照 depends_on 的顺序确保数据库先于后端启动。
  4. 通过 Nginx 反向代理,用户访问 http://localhost 即可直接访问前端,前端请求 /api 时会自动转发到后端容器。
总结

通过这次“文及”项目的 Docker 化实践,我深刻体会到了容器化技术的优势,也明白了“环境一致性”的重要性。

  • 网络问题:善用国内镜像源,避开 403 陷阱。
  • 基础镜像:弃用官方 OpenJDK,拥抱 Eclipse Temurin。
  • 网络通信:容器间通信用服务名,别用 localhost。
  • 数据持久化:数据库必须挂载 Volume,否则数据必丢。

Docker 不仅仅是部署工具,更是解决“在我电脑上能跑,服务器上就挂了”这一难题,虽然一定程度上确实花了不少时间去熟悉操作。希望这篇实战记录能帮你省下一下午的调试时间!


如果你觉得这篇文章帮你节省了配置环境的时间,别忘了点赞、收藏、关注!

在部署过程中你遇到了哪些奇葩问题?欢迎在评论区留言,我们一起讨论解决!

更多推荐