资讯动态

SpringBoot+Docker+MySQL+Redis 从Windows到Linux的容器化部署与镜像迁移实战

发布时间:2026/10/20 19:13:07 来源:尧图企业网站定制
简介面向希望打通Windows开发与Linux部署链路的Java后端工程师这份docx格式操作笔记围绕SpringBoot项目容器化给出完整实践路径。内容从Dockerfile编写、jar包复制、镜像构建与容器运行入手逐步覆盖端口映射、容器日志查看、宿主机数据库访问时的网络调试再到MySQL容器化、挂载目录与端口映射最终说明如何将Windows中构建好的镜像打包迁移至Linux环境并延伸介绍Docker Compose管理多容器。资源包含1个文件压缩包大小2.48MB打开后即一份图文并茂的步骤文档配有命令示例和关键参数解释尤其适合刚接触Docker、希望跨平台部署SpringBoot应用的读者对照操作。目前已有134人学习下载可帮助减少环境配置踩坑快速形成从镜像构建到跨环境交付的完整认知。1. 需求背景Windows 开发到 Linux 部署的容器化闭环在 Windows 上本地开发一个 SpringBoot 项目最后要部署到一台 Linux 服务器上这是毕设、课设和不少小团队交付的常态场景。最常见的结果是本地连数据库一切正常jar 包传到服务器就各种幺蛾子JDK 版本不对、端口被占、数据库连不上每台服务器的环境还不一样排查半天不知道问题出在哪。这套方案把问题前置解决先在 Windows 上用 Docker 把 SpringBoot 应用、MySQL、Redis 全部容器化验证通过后用 docker save 把镜像打包搬到 Linuxdocker load 加载后直接运行。核心价值是把“环境差异”从交付物里剥离掉让“能跑”变成可复现的结果而不是靠运气。适合正在做毕设、课设需要把系统搬上 Linux 的学生也适合不想在服务器上手动配环境的开发同学。2. SpringBoot 容器化Dockerfile 编写与本地构建验证2.1 Dockerfile 的五要素基础镜像、复制与启动命令为什么要把 jar 包打进镜像而不是在 Linux 上手动装 JDK 再跑因为 JDK 版本、系统库、时区、字符集这些差异太多了镜像可以把这一整套固定下来。SpringBoot 容器化的第一步是在项目根目录创建一个没有扩展名的 Dockerfile 文件和 pom.xml 同级。这个文件告诉 Docker 怎么一步步把应用包变成可运行的镜像。# 使用 OpenJDK 8 的 Alpine 精简镜像体积小适合作为基础运行环境 FROM openjdk:8-jdk-alpine # 设置容器内的工作目录后续 COPY 和运行都基于 /app WORKDIR /app # 将构建好的 jar 包复制到镜像中并统一重命名为 app.jar # 注意这里的打好的应用包.jar 必须和 target 目录下的实际文件名完全一致 COPY target/打好的应用包.jar app.jar # 暴露应用端口这里以 8080 为例和 application.yml 中的 server.port 对应 EXPOSE 8080 # 启动命令用 exec 形式确保 java 进程能正确接收外部信号 ENTRYPOINT [java, -jar, /app/app.jar]用 openjdk:8-jdk-alpine 而不是 openjdk:8是因为 Alpine Linux 本身就是精简发行版同样的 JDK 8镜像体积能从三四百兆降到一百多兆。WORKDIR 决定了容器内所有相对路径的基准后续 COPY 和 java -jar 都依赖它。COPY 这一步最容易翻车源文件必须在构建上下文里存在而且名字要和 target 目录下实际打出来的包一模一样版本号变了没同步改 Dockerfile镜像照样能构建成功但容器跑起来会直接报找不到 app.jar。ENTRYPOINT 用 JSON 数组形式而不是 shell 形式是为了让 java 进程成为容器的主进程docker stop 时能收到 SIGTERM 正常退出。Dockerfile 指令的作用整理成一张表答辩时对照着讲很方便指令作用注意点FROM指定基础镜像选 alpine 版本能显著减小体积WORKDIR设置工作目录影响后续所有相对路径COPY拷贝文件进镜像源文件必须在构建上下文内EXPOSE声明容器监听端口只是声明真正生效靠 -p 映射ENTRYPOINT容器启动时执行的命令用 exec 形式别用 shell 形式写完 Dockerfile 之后先用 Maven 打包在 PowerShell 里执行 mvn clean package确认 target 目录下出现了 jar 包再继续。很多人 Dockerfile 里写的是旧版本的包名打包工具都正常直到容器起不来才回去检查这一行属于典型的低级踩坑。2.2 构建镜像docker build 的上下文与镜像标签打包完成后在 PowerShell 里导航到项目根目录执行构建命令。# 切换到项目根目录Dockerfile 就在这里 cd D:\workspace\my-springboot-app # 构建镜像-t 指定镜像名和标签最后的 . 是构建上下文不能省略 docker build -t myapp:1.0 . # 构建完成后查看本地镜像 docker images命令最后的这个点很多人第一次不理解。它表示构建上下文build context也就是 Docker daemon 在构建过程中能看到的文件范围。Docker 会把这个目录下的内容打包传给 daemon所以上下文目录里最好别有太多无关文件。如果项目里图片、文档比较多建议在项目根目录加一个 .dockerignore 文件把 target、.git、.idea、*.log 排除掉不然每次构建都要把几百兆无关文件传来传去。# .dockerignore 内容示例放在项目根目录和 Dockerfile 同级 target/ .git/ .idea/ *.log镜像名建议用 myapp:1.0 这种“名称:标签”格式标签带版本号而不是总用 latest。后面导出、回滚、区分测试环境和生产环境时版本号会省很多事。构建过程中每一行指令都会生成一个镜像层构建日志会一步一步显示 STEP。第一次构建要先拉取 openjdk:8-jdk-alpine 基础镜像如果卡住不动多半是镜像源问题在 Docker Desktop 设置里换成国内加速地址再重试。2.3 运行容器端口映射与日志验证镜像构建完成后用 docker run 把它跑起来。# -d 后台运行-p 8080:8080 把容器内 8080 映射到宿主机 8080 docker run -d -p 8080:8080 --name myapp-container myapp:1.0 # 查看容器日志判断启动是否正常 docker logs myapp-container # 查看运行中的容器列表确认状态是 Up docker ps-p 的参数格式是“宿主机端口:容器端口”左边宿主机端口可以随便改比如 -p 9090:8080 外部访问的就是 9090右边容器端口必须和 SpringBoot 实际监听的端口一致。--name 给容器起个固定的名字后面看日志、停容器、删容器都要用到它。启动后用 docker logs 看日志出现 Tomcat started on port 8080 就说明启动成功了浏览器访问 http://localhost:8080 验证接口。如果容器启动几秒后自己退出先 docker ps -a 看退出码Exited(0) 多半是 ENTRYPOINT 里的 jar 路径写错或者 jar 没进镜像Exited(1) 多半是应用启动时抛异常比如端口占用、数据库连不上。判断方法很简单docker logs 直接看报错比瞎猜快得多。这一步要在 Windows 本地把功能验证完再往下走否则问题混在一起后面很难定位。3. 依赖服务打通MySQL 与 Redis 的容器化部署3.1 第一版方案--nethost 的适用场景与局限应用容器跑起来了但 SpringBoot 项目基本都要连数据库。如果数据库装在 Windows 宿主机上最简单的办法是让容器直接共享宿主机网络也就是加一个 --nethost 参数。# 共享宿主机网络栈容器里的 localhost 就是宿主机的 localhost docker run -d -p 8080:8080 --name myapp-container --nethost myapp:1.0加了 --nethost 之后容器内的端口直接监听在宿主机网卡上-p 端口映射反而不生效了。这个方案能解决“容器访问宿主机数据库”这一个问题但实际意义不大生产环境数据库大概率在另一台服务器哪怕单机开发数据库也常被装进 Docker 里。共享网络栈还意味着容器和宿主机完全平级端口冲突风险也上来了。所以这个方案更适合临时排查不适合作为交付方案我一般只在定位问题时用它。真正可靠的解法是把数据库也容器化再用自定义网络把服务串起来。3.2 网络连通性排查ping、telnet 与数据库用户授权把数据库容器化之前先掌握一个排查套路应用连不上数据库时第一步不是改配置而是确认网络通不通。# 进入应用容器内部ping 数据库容器名验证网络层是否可达 docker exec -it myapp-container ping mysql-container # 用 telnet 测试数据库容器的 3306 端口是否对外提供服务 docker exec -it myapp-container telnet mysql-container 3306ping 通代表网络层没问题telnet 通代表端口和连接数正常这两步都过了问题就基本落在账号权限或者地址配置上。数据库装好之后还需要给远程访问创建用户默认的 root 通常只允许 localhost 登录。-- 创建用户允许任意 IP 的 root 账号密码为 root仅限开发环境 CREATE USER root% IDENTIFIED BY root; -- 授予全部权限 GRANT ALL PRIVILEGES ON *.* TO root% WITH GRANT OPTION; -- 刷新权限表让授权立即生效 FLUSH PRIVILEGES;用 Navicat 这一类数据库客户端连上 MySQL 图形化创建用户也可以效果一样。开发环境图省事用 % 没问题但生产环境一定要把 % 换成具体网段或 IP比如 192.168.1.%否则数据库等于裸奔。改完权限之后应用还是连不上就回头核对 yml 里的数据库地址、端口、用户名、密码这四要素逐项比对。提示docker exec 进入容器执行 ping 和 telnet前提是容器里有这两个命令。Alpine 精简镜像可能不带 telnetping 也可能要装 iputils。最省事的办法是先在宿主机 ping 容器 IP再进容器 ping 数据库容器名。3.3 MySQL 容器化四个挂载目录与 3307 端口映射数据库容器化的核心是数据不丢。容器本身是临时的删除就没了所以必须把数据目录、日志目录、配置目录都挂载到宿主机上。# 1. 创建挂载目录这里以 D:\development_sources\docker\mysql 为例 mkdir D:\development_sources\docker\mysql\log, D:\development_sources\docker\mysql\data, D:\development_sources\docker\mysql\conf, D:\development_sources\docker\mysql\mysql-files # 2. 部署 MySQL 8.0.24 容器 docker run -p 3307:3306 --restartalways --name mysql-container \ -v D:\development_sources\docker\mysql\log:/var/log/mysql \ -v D:\development_sources\docker\mysql\data:/var/lib/mysql \ -v D:\development_sources\docker\mysql\conf:/etc/mysql \ -v D:\development_sources\docker\mysql\mysql-files:/var/lib/mysql-files \ -e MYSQL_ROOT_PASSWORDroot \ -d mysql:8.0.24这条命令拆开看很清晰。-p 3307:3306 是因为宿主机 3306 已经被其他 MySQL 实例占用所以把容器内 3306 映射到宿主机 3307应用配置里的数据库端口也要跟着写 3307。--restartalways 设置重启策略Docker 守护进程启动或容器异常退出时容器会自动重启对交付场景很重要。四个 -v 分别对应日志目录、数据目录、配置目录和导入导出目录其中 data 目录最重要所有表结构和数据都存在这里备份时只需要备份这个文件夹。--restartalways 和 -v 的组合让 MySQL 容器可以随便删、随便重建只要 data 目录还在数据就不会丢。-e MYSQL_ROOT_PASSWORDroot 在容器首次启动时初始化 root 密码。参数值说明-p3307:3306宿主机 3307 映射容器 3306--restartalways异常退出后自动重启-v四个路径挂载日志、数据、配置、文件目录持久化-eMYSQL_ROOT_PASSWORDroot初始化 root 密码镜像mysql:8.0.24指定 MySQL 版本3.4 自定义网络 dev_net容器名即域名MySQL 容器起来了但应用容器要访问它默认的 bridge 网络不支持用容器名互相解析 IP。解法是创建一个自定义网络然后把所有容器都连上去。# 创建自定义 bridge 网络 docker network create dev_net # 把 MySQL 容器连接到 dev_net docker network connect dev_net mysql-container # 查看网络里的容器和 IP 分配情况 docker network inspect dev_net # 查看容器在 dev_net 中的具体 IP docker inspect -f {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} mysql-container # 把应用容器也连接到 dev_net容器名可以作为主机名互相访问 docker network connect dev_net myapp-container自定义网络和默认网络最大的区别是自定义网络内嵌了 DNS 解析容器名可以被其他容器直接当作主机名访问。也就是说应用 yml 里的数据库地址可以写成 jdbc:mysql://mysql-container:3306/项目数据库名而不是写死的 IP。这样做的好处是容器重建后 IP 会变但容器名不变只要连接在同一个网络里配置就永远有效。这是容器化部署里很关键的设计答辩时讲清楚这一条挺加分。Redis 容器部署好之后也用同样的方式 connect 到 dev_net。3.5 Redis 容器化AOF 持久化与访问密码Redis 的部署思路和 MySQL 类似只是挂载目录少一些。# 创建挂载目录Redis 的数据文件会写到这里 mkdir D:\development_sources\docker\redis\data # 部署 Redis 容器 docker run -p 6380:6379 --restartalways --name redis \ -v D:\development_sources\docker\redis\data:/data \ -d redis redis-server --appendonly yes --requirepass redis-p 6380:6379 是因为宿主机 6379 被其他服务占了外部访问走 6380。--appendonly yes 打开 AOF 持久化每个写命令都会追加到文件里容器重启后数据不丢代价是磁盘写入多一些。--requirepass redis 设置访问密码应用配置里的 Redis 密码要写成这个。最后把 Redis 容器也连到 dev_net方便应用容器直接用 redis 这个容器名访问# 加入 dev_net让应用容器可以通过 redis 主机名访问 docker network connect dev_net redis到这一步SpringBoot 应用、MySQL、Redis 三个容器都在 dev_net 里通过容器名互相访问Windows 本地这套环境就跑通了。4. 跨平台迁移镜像打包上传与 Linux 环境部署4.1 镜像导出docker save 与 export 的区别Windows 上这套环境跑通之后接下来要把它搬到 Linux。容器没法直接传但镜像可以。docker save 的作用就是把一个或多个镜像导出成 tar 包这个 tar 包就是跨平台交付的载体。# 导出应用镜像 docker save -o myapp.tar myapp:1.0 # 导出 MySQL 镜像 docker save -o mysqlimage.tar mysql:8.0.24 # 导出 Redis 镜像 docker save -o myredisimage.tar redis-o 指定输出文件名后面跟镜像名。三个镜像分开导好处是目标机器可以按需加载不用把不需要的镜像一起搬过去。注意 docker save 导的是镜像不是容器所以导出前先 docker images 确认要导的东西在不在。有人会把 save 和 export 搞混save 是打包镜像export 是导出容器文件系统。前者保留镜像的历史层和标签后者会丢掉这些信息迁移场景一律用 save。4.2 Linux 环境准备防火墙、yum 源与 Docker 安装Windows 上导出 tar 包之后用 SSH 终端工具比如 FinalShell连上 Linux 服务器输入 IP、端口默认 22、账号密码就能进去。先确认 Linux 发行版是 CentOS 7 这类 RedHat 系列用 uname -r 看内核版本然后做两件事关防火墙、装 Docker。# 开发环境关闭防火墙生产环境禁止这样操作 systemctl stop firewalld systemctl disable firewalld # 安装 docker 依赖工具yum-utils 提供 yum-config-manager 功能 yum install -y yum-utils device-mapper-persistent-data lvm2 # 配置阿里云 Docker 镜像源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 查看可安装版本便于选择稳定版本 yum list docker-ce --showduplicates | sort -r # 安装指定版本这里以 18.03.1.ce 为例 yum -y install docker-ce-18.03.1.ce # 启动 Docker 并设置开机自启 systemctl start docker systemctl enable docker # 验证 Docker 安装成功 docker version关防火墙这一条导师如果问“为什么关”标准回答是开发环境有大量端口要暴露为了省去逐端口放行的麻烦才关生产环境必须用安全组或防火墙精确放行不能照搬。yum 源换成阿里云镜像后国内下载速度快很多。安装时指定版本而不是直接装最新版是因为镜像版本和 Docker 版本有联动锁版本才能保证可复现。装完看 docker version能正常输出 Client 和 Server 两段信息代表 daemon 已经起来了。注意yum -y update 在部分教程里被放在安装前执行它会同时升级内核开发机无所谓但生产环境很可能导致服务不兼容我一般跳过 update 直接装 docker-ce。4.3 镜像加载docker load 与容器启动tar 包通过 SSH 工具上传到 Linux 的一个自定义目录比如 /opt/loadimages然后 cd 进去加载。# 创建目录存 tar 包 mkdir /opt/loadimages cd /opt/loadimages # 从 tar 包加载镜像-i 指定输入文件 docker load -i myapp.tar docker load -i mysqlimage.tar docker load -i myredisimage.tar # 确认三个镜像都已加载 docker imagesload 是 save 的逆操作把 tar 包里的镜像层恢复到本地镜像库。加载完成后直接用 docker run 在 Linux 上启动容器不需要重新写 Dockerfile不需要重新构建。这意味着构建环境Windows和运行环境Linux彻底解耦只要镜像在任何装了 Docker 的机器都能复现同一套运行结果。Linux 上启动前先把自定义网络建好把 MySQL 和 Redis 容器连进去再运行应用容器# 在 Linux 上重建 dev_net 并把容器加入同一网络 docker network create dev_net # 启动 MySQL 容器挂载目录换成 Linux 路径 docker run -p 3307:3306 --restartalways --name mysql-container \ -v /opt/docker/mysql/log:/var/log/mysql \ -v /opt/docker/mysql/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORDroot \ -d mysql:8.0.24 # 运行应用容器网络指定为 dev_net保证能解析到 mysql-container 这个主机名 docker run -d -p 8080:8080 --name myapp-container --netdev_net myapp:1.0如果应用要走 dev_net 才能通过容器名访问数据库那么应用容器必须和数据库容器在同一个网络里启动时加 --netdev_net 这一步不能漏。4.4 数据持久化迁移目录复制与环境变量分离镜像迁移解决了程序本身数据要单独处理。Windows 开发时 MySQL 的数据都写在 D:\development_sources\docker\mysql\data 这个挂载目录里迁移到 Linux 时把这个目录整个拷过去再用同样的 -v 挂载方式跑容器数据就跟着走了。这一步有个容易被忽略的坑应用 jar 包里的数据库连接地址如果写的是 localhost搬到 Linux 上就会连到错误的地方。解决方案不是改代码重新打包而是把数据库连接信息抽成环境变量docker run 时用 -e 传进去。这样代码和配置分离同一个镜像在不同环境用不同参数跑是容器化部署的基本玩法也是配置与镜像分离思想的体现。5. 避坑指南SpringBoot 容器化最容易翻车的五个点以下五条是我在这套流程里反复踩过的血泪经验每一条都按“现象 → 原因 → 解决”的顺序写排查时直接对照。5.1 容器启动后立刻退出docker ps 看不到现象执行 docker run 之后docker ps 里看不到容器docker ps -a 能看到一个 Exited 状态的容器。原因退出码是 0多半是 ENTRYPOINT 里 java -jar 的路径写错或者 COPY 的 jar 包根本没复制进镜像退出码是 1多半是应用启动时抛异常比如端口被占、数据库连不上。解决先用 docker logs 容器名 看日志再去容器里确认 jar 是否存在docker exec -it 容器名 ls /app。jar 不存在就回去盯 Dockerfile 的 COPY 那行包名和 target 目录下的实际文件名要一字不差。最容易犯的错是版本号升级了Dockerfile 还写旧包名构建不报错运行就翻车。5.2 端口映射后外部还是访问不了现象-p 8080:8080 写了浏览器访问 http://localhost:8080 就是打不开。原因三种常见情况——应用实际监听端口不是 8080yml 里改过 server.port容器启动失败Windows 防火墙拦了 Docker 端口。解决先 docker ps 确认状态是 Up再看 docker logs 确认应用监听在哪个端口最后检查 Docker Desktop 的端口映射配置。顺序排查别一上来怀疑命令写错。宿主机端口被占就换一个左边的映射比如 -p 9090:8080右边容器端口不要动因为那和 SpringBoot 实际监听端口绑定。5.3 MySQL 容器起不来bind address already in use现象docker run mysql 时直接报 Error response from daemon: driver failed programming external connectivity后面跟着 bind: address already in use。原因宿主机上已经有服务占了 3306 端口。很多开发机自己装了 MySQL 或 MariaDB端口早就被占用。解决换宿主机的映射端口用 3307:3306。注意要同步修改应用配置里的数据库端口很多人在这个位置漏改导致应用还是连 3306自然连不上。想确认谁占了端口在 Windows 上执行 netstat -ano | findstr 3306看到对应 PID 就知道是哪个进程。5.4 容器之间网络不通ping 不到对方现象应用容器里 ping mysql-container 无响应telnet 也连不上 3306 端口。原因容器都在默认 bridge 网络里默认网络不支持容器名 DNS 解析也没办法通过主机名互联。进容器 ping 的时候根本解析不到写的那个容器名。解决创建自定义网络 docker network create dev_net把相关容器都 connect 进去。自定义网络自带 DNS 解析容器名直接当主机名用。排查时用 docker network inspect dev_net 看容器是否真的在同一个网络里别光凭记忆判断。5.5 改了配置重启容器不生效现象修改了 application.yml 里的数据库地址docker restart 容器发现应用还是连老地址。原因jar 包在镜像构建时就已经 COPY 进去了restart 只是重启容器进程镜像里的内容一点没变。解决改 jar 包内容必须重新 docker build 重新 docker run。避免这个问题的正式做法是把易变的配置抽成环境变量在 docker run 时用 -e 传入# 用环境变量传入数据库和 Redis 配置镜像本身不用重新构建 docker run -d -p 8080:8080 --name myapp-container \ --netdev_net \ -e DB_URLjdbc:mysql://mysql-container:3306/项目数据库名 \ -e DB_USERNAMEroot \ -e DB_PASSWORDroot \ -e REDIS_HOSTredis \ -e REDIS_PASSWORDredis \ myapp:1.0代码里用 ${DB_URL:默认值} 的方式读取比如在 application.yml 里写spring: datasource: url: ${DB_URL:jdbc:mysql://localhost:3306/项目数据库名} username: ${DB_USERNAME:root} password: ${DB_PASSWORD:root}这样环境变了只改 docker run 参数不用重新打包。这套思路在运维里叫“配置与镜像分离”答辩时能讲出来会加分。6. 验收与答辩从能跑到说得清6.1 验收清单每一步都有证据容器化做完之后验收不能只靠“浏览器能打开”。我每次交付都会强制走一遍下面的清单检查项执行方式验收标准镜像齐全docker images应用、MySQL、Redis 三个镜像都在列表里容器运行正常docker ps容器状态都是 Up重启策略已生效应用可访问curl http://localhost:8080/某个接口返回 200 而不是 5xx数据库可连接docker exec -it mysql-container mysql -uroot -p能登录并 show databases 成功网络打通docker exec -it myapp-container ping mysql-container能 ping 通应用日志无连接异常数据持久化删除容器后重建数据还在不丢镜像可迁移docker save / docker load 到 LinuxLinux 上同样能跑起来这份清单的价值是任何一步出问题都能马上知道卡在哪个环节不会出现“本地好好的服务器跑不起来”这种玄学问题。6.2 答辩时怎么讲这套方案答辩最容易被问三个问题。第一个为什么用 Docker 部署答把 JDK、中间件、应用打包进镜像环境差异从交付物里剥离部署从配置环境变成加载镜像。第二个容器删了数据会不会丢答不会数据通过 -v 挂载在宿主机容器是临时的数据目录是持久的。第三个为什么用自定义网络答默认网络不支持容器名解析自定义网络里容器名就是域名IP 变了不影响配置。讲清楚“镜像、容器、数据卷”三个概念的区别是最容易拿分的点——镜像是一个只读模板容器是镜像的运行实例数据卷是独立于容器生命周期的持久化存储。从那以后每次做容器化交付我都强制走一遍 checklist先 docker ps 看状态再 docker logs 看日志再 curl 接口探活最后才敢把镜像导出去。这套流程本身也成了最有效的验收工具希望这篇能帮你少踩几个坑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑