☰
CentOS7+Docker+IDEA:Spring Boot容器化部署实战
2026/10/14 7:54:17 网站建设 项目流程

本地开发环境跑得再欢,到了部署环境就“水土不服”——这是Java后端入门时最容易碰见的无奈。Spring Boot项目在自己电脑上双击就能启动,换一台机器却可能因为JDK版本、系统依赖、端口占用各种原因直接挂掉。解决这个问题的标准答案就是容器化,而这篇文章要讲的,正是用VirtualBox搭一台CentOS 7虚拟机,在上面跑Docker,最后用IntelliJ IDEA构建并部署 Spring Boot 的 Docker 镜像。这套链路覆盖了虚拟机网络配置、Linux基础操作、Docker镜像构建和远程连接四个知识块,适合正在学Spring Boot的Java开发者,也适合想搞懂“我在IDEA里写的代码到底是怎么变成线上服务”的同学。标题里的CenterOS7其实就是CentOS 7,下面我会把每一步拆开,讲清楚原理,也把容易踩的坑提前指出来。

1. 为什么是这套组合:每个组件在部署链路里的角色

好多人第一反应是:我直接在Windows上装个Docker Desktop不就行了?为什么要先套一层VirtualBox虚拟机,再装CentOS 7,然后再上Docker,绕这么大一圈?这个疑问特别正常,我当初也是这么想的,但折腾完一轮之后才明白,这套组合恰恰是练习“真实部署”成本最低的路子。

1.1 四个组件各管哪一段

先说VirtualBox。它的作用是模拟出一台“干净”的Linux服务器。你在宿主机上折腾JDK、Docker、防火墙,很容易把日常开发环境弄得乱七八糟。虚拟机相当于一个可随时还原的沙盒,装坏了删掉重建就行,宿主机完全不受影响。这一点在学习和实验阶段格外重要,你可以放心大胆地把配置改来改去,最多损失一台虚拟机的启动时间。

CentOS 7承担的是“生产环境”角色。虽然CentOS 7已经进入维护周期末期,但市面上仍有大量存量服务器跑着它,很多公司的老业务系统也都是CentOS 7 + Docker的组合。它内核版本是3.10,跑Docker 20.x这种版本没有问题。用这套组合学习,至少不用担心“在自己机器上能跑,公司服务器跑不了”这种尴尬。

Docker是整个方案的交付层。它把Spring Boot打成的jar包、运行所需的JRE、系统依赖、时区设置全部打包成一个镜像,然后以容器方式运行。容器和宿主机共用内核,但文件系统、网络、进程空间是隔离的,所以不管在哪台装了Docker的机器上,跑出来的效果都一致。

最后是IntelliJ IDEA。它在这里不只是写代码的编辑器,更是构建和部署的入口。IDEA自带Docker支持,可以通过远程连接让虚拟机里的Docker daemon直接读取本机的Dockerfile和jar包,在线构建镜像并启动容器。整个过程不需要把jar包手动拷到服务器上,也不需要打开命令行敲一堆scp命令。

1.2 为什么不用“Java -jar”直接部署

如果只是想把项目跑起来,最粗暴的办法是把jar包传到服务器,装个JDK,然后nohup java -jar xxx.jar。这样做最大的问题是环境脆弱:服务器上JDK版本和本地不一致时,可能出现ClassNotFound;系统缺字体库或字符集,可能导出乱码;遇到多台服务器时,每台都要手动重复相同操作,稍有不慎就配置漂移。

Docker把这堆问题打包解决。镜像里声明了“我需要什么环境”,构建一次,处处运行。这个思路在后面接触Kubernetes时尤其重要,因为K8s调度的最小单元就是容器,它不认识“哪台机器上装了JDK”。所以现在多花点时间把Docker基础打牢,对后续进阶是实打实的价值。

1.3 这套方案能做到什么程度

搭好之后,你可以实现这样的完整闭环:在IDEA里修改代码 → Maven打包 → IDEA通过Dockerfile构建镜像 → 镜像出现在虚拟机的Docker上 → 容器启动 → 浏览器访问虚拟机端口看到接口返回。整个流程跑通之后,你对“部署”这个概念就不再有恐惧,因为全流程每一步你都能自己控制,出了问题也知道去哪里看日志。

2. VirtualBox里装CentOS 7:先把网络这张网织明白

虚拟机的安装本身不复杂,真正让人栽跟头的是网络配置。很多人虚拟机是装好了,但宿主机连不上它,它在容器里拉不了镜像,或者容器起来之后浏览器访问不到,这些问题八成都是网络模式选错了。

2.1 创建虚拟机的推荐参数

内存建议给到4096MB以上,至少也要2048MB。Spring Boot应用本身不重,但加上Docker daemon、操作系统和可能的构建过程,内存太小的虚拟机会非常卡顿,甚至容器启动直接被OOM杀掉。CPU给2核,磁盘建议20GB起步,因为CentOS 7安装完占3GB左右,Docker镜像和容器日志还会持续占用空间。

安装方式我推荐Minimal最小化安装,不要装带图形界面的版本。既然要学服务器部署,就应该直接面对命令行。CentOS 7的安装盘可以从官网或国内镜像站下载Minimal ISO,体积在800MB左右,比起DVD版本动辄4GB,下载和写入U盘都更快。

2.2 三种网络模式怎么选

VirtualBox为虚拟网卡提供几种模式,多数人只用默认的NAT(网络地址转换)。NAT模式下,虚拟机可以通过宿主机访问外网,但外部设备无法主动访问虚拟机。如果虚拟机里装了Docker并映射了8080端口,宿主机浏览器直接访问虚拟机的IP是不通的,必须配置端口转发规则。

桥接模式让虚拟机像一台独立的机器一样接入宿主机所在的局域网,会拿到局域网里的一个IP。好处是宿主机可以直接访问它,坏处是如果路由器DHCP分配的IP变了,之前配置的连接地址就全部失效。仅主机模式则只有宿主机和虚拟机能够互通,虚拟机不能访问外网,这会影响Docker拉镜像。

我比较推荐的做法是:NAT模式负责上网和拉镜像,同时配置端口转发,把宿主机的某个端口转发到虚拟机的对应端口。比如宿主机8080 → 虚拟机8080,这样访问本机localhost:8080就等于访问虚拟机里的容器端口。这种方式对IP不敏感,就算虚拟机IP变了,转发规则依然指向那个虚拟机的内部IP。

表:三种网络模式一句话总结

模式外网访问宿主机访问虚拟机适合场景
NAT可以需端口转发单机实验,最常用
桥接可以直接访问模拟局域网服务器
仅主机不行直接访问纯本地联调

2.3 固定IP与SSH准备

CentOS 7安装完成后,默认不一定启用了SSH服务。先执行systemctl status sshd检查,如果没启动就systemctl enable --now sshd,然后在宿主机上用SSH客户端(Windows下可以用终端工具)连接虚拟机,后续大部分操作就都不用在虚拟机窗口里敲了。

如果你选了NAT模式且不想每次记端口转发,也可以给虚拟机添加第二块仅主机网卡,网段通常会落在192.168.56.x。然后编辑网卡配置文件,比如/etc/sysconfig/network-scripts/ifcfg-enp0s3,把BOOTPROTO改成static,按实际情况设置IPADDR=192.168.56.101、NETMASK=255.255.255.0,并确保ONBOOT=yes,再systemctl restart network。这样虚拟机既有固定的仅主机IP用于SSH连接,又有NAT网卡用于拉镜像,是我在多台实验环境里验证过比较舒服的配置。

3. 在CentOS 7上把Docker装利索:换源、加速、守护服务

CentOS 7默认的yum源里并不直接提供docker-ce,直接yum install docker装出来的可能是非常古老的版本,功能不全会影响后续操作。所以我们需要手动添加Docker的软件源,同时考虑到国内网络环境,还应该把镜像加速器和日志限制一并配置好,避免装完才发现拉镜像慢、日志撑爆磁盘。

3.1 安装Docker的完整命令序列

先安装yum-utils工具包,它提供yum-config-manager命令,用来管理系统源配置更省事:

yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

官方源在国内可能连接不稳定,如果遇到超时,可以把上面的仓库地址替换为同样目录结构的国内镜像源,仓库配置结构完全一样,替换后继续执行安装即可。然后安装社区版Docker并启动:

yum install -y docker-ce docker-ce-cli containerd.io systemctl enable --now docker

验证是否装好直接看版本:docker version,只要客户端和服务端都显示版本号,说明daemon已经正常工作了。

3.2 配置镜像加速和日志上限

Docker拉镜像默认去公共仓库下载,国内直连有时很慢。我们需要编辑/etc/docker/daemon.json,加入镜像加速地址和日志限制配置:

{ "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn", "https://hub-mirror.c.163.com" ], "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" } }

这里registry-mirrors填的是公共镜像加速地址,不同地区效果有差异,可以多试几个。log-opts里的配置非常值得养成习惯,Spring Boot应用开启debug时日志量很大,不限制的话容器长期运行后日志文件会膨胀到几个GB,清理起来很痛苦。改完配置后systemctl daemon-reload && systemctl restart docker。

3.3 远程连接的开与不开

IDEA要操控虚拟机里的Docker,最直接的方式是打开Docker的TCP监听端口2375。但需要明确一件事:裸奔的2375端口在公开网络环境下等于把Docker控制权送给别人,任何人都可以往你的daemon上丢容器。它只能在可信家庭网络或实验环境里用,公司生产环境绝对不要这么干。

更稳妥的方式是用IDEA的SSH连接Docker。IDEA的Docker配置里选择SSH类型,填入虚拟机地址、SSH端口、用户名和密码,IDEA会通过SSH隧道访问本机Unix socket,既安全又不需要额外改daemon配置。这也是我在实践中比较推荐的方式,下文的示例都会基于SSH连接展开。

提示:不要为了图方便把2375端口直接暴露在大网段上。学习环境也要有安全意识,否则虚拟机沦为矿机是分分钟的事。

4. 在IDEA里把Spring Boot项目变成镜像:Dockerfile是重头戏

前面的一切准备工作,最终都是为了在IDEA里点一下就能生成镜像。但生成镜像之前,Dockerfile必须先写对,而Dockerfile的写法直接决定了镜像体积、构建速度和运行时稳定性。这一块我展开讲一讲。

4.1 准备一个最小可运行的Spring Boot项目

先建一个最普通的Spring Boot Web项目,不需要太多代码,一个Controller加一个返回字符串的接口就够了。为了保持生产环境一致性,项目JDK和基础镜像JDK尽量对齐,我这里用Spring Boot 2.7 + JDK 8,对应基础镜像openjdk:8-jre-alpine。

Controller代码:

@RestController public class HelloController { @GetMapping("/hello") public String hello() { return "hello docker, time: " + LocalDateTime.now(); } }

然后执行mvn clean package,确认target目录下生成jar包。这个jar包是我们构建镜像的唯一应用产物,后续COPY指令会把它放进镜像里。

4.2 一个干净的Dockerfile长什么样

在项目根目录新建Dockerfile,内容如下:

# 第一阶段:指定运行时基础镜像,JRE版本尽量与项目JDK一致 FROM openjdk:8-jre-alpine # 元数据信息 LABEL author="demo" version="1.0.0" # 设置时区,解决容器内时间与北京时间相差8小时的问题 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # 工作目录 WORKDIR /app # 将本地打好的jar包复制进镜像 COPY target/demo-0.0.1-SNAPSHOT.jar /app/demo.jar # 对外暴露端口 EXPOSE 8080 # 容器启动时执行的命令 ENTRYPOINT ["java", "-Xms256m", "-Xmx256m", "-jar", "demo.jar"]

每一行都有讲究。用jre-alpine而不是完整jdk镜像,是为了大幅缩小体积,完整JDK镜像动辄600MB往上,JRE的Alpine版本通常只有150MB左右。设置TZ=Asia/Shanghai是因为很多应用会在日志里输出时间,容器默认时区是UTC,不设置就会看到日志时间比本地少8小时。WORKDIR /app决定了后续命令和容器的默认目录,统一了路径预期。EXPOSE 8080只是声明容器内端口,真正让外部访问靠的是运行容器时的-p映射,这个很多人容易混淆。

ENTRYPOINT里的JVM参数也建议写上,不给参数时JVM会根据宿主机物理内存自动计算最大堆,在内存较小的虚拟机上容易把内存吃满。手动限制堆大小,容器反而更稳定。

4.3 在IDEA里配置Docker连接并构建

IDEA的Docker支持在Settings → Build, Execution, Deployment → Docker里配置。选择SSH类型连接方式,填入虚拟机的SSH连接信息,连接成功后IDEA会列出宿主机上已有的镜像、容器和网络。

然后点击菜单Run → Edit Configurations,新建一个Dockerfile类型的运行配置:

  • Dockerfile路径:选择项目根目录的Dockerfile
  • 镜像标签:例如demo:1.0
  • 容器名称:demo-container
  • 端口绑定:8080:8080

保存后点击运行,IDEA会先读取Dockerfile,把构建上下文打包发送到虚拟机里的Docker daemon,在虚拟机上完成镜像构建,然后自动创建并启动容器。下方输出窗口会实时显示构建日志和容器日志,这个体验比手动敲命令行直观很多。

4.4 镜像瘦身与多阶段构建

如果项目里加了机器学习和图像处理类依赖,基础JRE镜像也会膨胀,这时可以考虑多阶段构建。用一个带Maven的镜像在构建阶段编译打包,再把jar拷贝到轻量运行镜像里,不需要在宿主机或本地手动打包:

FROM maven:3.6-jdk-8 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests FROM openjdk:8-jre-alpine WORKDIR /app COPY --from=builder /build/target/demo-0.0.1-SNAPSHOT.jar /app/demo.jar ENTRYPOINT ["java", "-Xms256m", "-Xmx256m", "-jar", "demo.jar"]

多阶段构建的优点是最终镜像只包含运行所需内容,构建工具和中间产物不会残留在镜像里。不过这种方式的缺点是需要网络下载Maven依赖,首次构建会比较慢。作为实验,直接用第一种单阶段方式操作起来更简单直观。

5. 镜像部署到虚拟机:端口映射、日志和IDEA可视化

镜像构建成功之后,部署其实已经几乎完成了,因为镜像就在目标虚拟机的Docker里。这一步的重点是验证访问链路是否真的通了,以及习惯用一些常用命令检查容器运行状态。

5.1 容器启动后的必查命令

IDEA运行配置帮我们创建了容器,但实际维护时还是要会命令行。SSH到虚拟机,执行:

docker ps

能看到CONTAINER ID、镜像、端口映射和状态。如果容器没起来,用docker ps -a查看所有容器,再用docker logs 容器名查看启动日志。Spring Boot启动一般需要十几秒,刚启动时访问接口可能报连接拒绝,等一会儿再试。

在本机服务器内部先自测一下:

curl http://localhost:8080/hello

如果返回了字符串,说明应用本身没问题,问题只可能出现在外部网络链路。

5.2 宿主机访问虚拟机的几种路径

如果VirtualBox用的NAT模式并配置了端口转发,宿主机的http://localhost:8080/hello应该直接能通。在端口转发设置里,把宿主机端口设为8080,子系统端口也设为8080,目标虚拟机IP填虚拟机的NAT网卡地址(通常为10.0.2.15)。这样虚拟机里不管IP怎么变,宿主机的访问入口始终是localhost。

如果是桥接模式,那就直接访问虚拟机的局域网IP,比如http://192.168.31.xx:8080/hello。注意防火墙放行8080端口:

firewall-cmd --permanent --add-port=8080/tcp firewall-cmd --reload

有些教程会直接让你关闭firewalld,方便是方便,但我不推荐养成这个习惯。生产环境对防火墙操作应该尽量精细,实验阶段也应该顺手学正确的做法。

5.3 IDEA Docker面板能省下大量命令

IDEA里配置好Docker连接后,工具窗口会显示远程Docker上的容器列表。点开容器,可以直接看到STDOUT日志、环境变量、端口映射、文件系统变化,还能一键停止、启动、删除容器。修改代码后需要重新部署时,在Dockerfile运行配置里点一下Rerun,就会重建镜像并替换容器。

这个流程用熟了,你会形成一种肌肉记忆:开发环境改代码 → 打包 → 跑Docker配置 → 浏览器验证。后续就算切换到生产环境,只要把Dockerfile迁移过去,行为几乎是一模一样的。

6. 按这条链路排查:容器起不来的典型故障与复盘

任何一套环境搭建教程,只讲顺利路径是不完整的。我自己在从零搭这套环境时,几乎每一步都出过问题,下面这几条是最高频的,按这个顺序排查能省下大量时间。

6.1 现象一:IDEA报Cannot connect to the Docker daemon

这个报错信息覆盖面很广。如果是用SSH方式连接,先确认虚拟机sshd在运行、密码或密钥没填错。然后SSH进虚拟机执行:

systemctl status docker

如果显示inactive(dead),直接systemctl start docker并systemctl enable docker让它开机自启。有些朋友用的发行版精简镜像没有配置systemd对Docker的自动启动,装完Docker不手动enable,重启虚拟机就再也连不上了。

还有一种可能性是当前用户不在docker组里。默认情况下Docker socket只有root和docker组的用户能访问,执行:

usermod -aG docker username

然后退出SSH重新登录,组权限才会生效。

6.2 现象二:虚拟机能curl通,宿主机访问不了

这是最典型的网络模式没想清楚。先确认虚拟机里curl localhost:8080是否返回正常,如果正常,问题一定在外面。

NAT模式下,宿主机访问虚拟机必须走端口转发。有时候端口转发规则里“子系统IP”填错了,比如填成公网IP或容器内部IP,转发就失效。正确填法是虚拟机内部网卡的IP地址,NAT通常固定为10.0.2.15。

如果是桥接模式,先ip addr确认虚拟机当前IP,然后用这个IP访问。之前有同学改了网卡配置后忘了systemctl restart network,配置没生效自然访问不通。另外记得确认firewalld是否放行端口,这一条经常被忽略。

6.3 现象三:构建镜像时拉基础镜像超时

构建日志卡在Pulling openjdk:8-jre-alpine不动,或者反复重试后报错,基本都是网络问题。检查/etc/docker/daemon.json里的加速地址是否生效,可以执行docker info查看Registry Mirrors列表是否列出。加速地址填了之后如果还是慢,换个加速地址再试。基础镜像只需要拉一次,之后构建就很快了,所以第一次即便慢也不要太焦虑。

6.4 现象四:端口被占用或容器名冲突

第二次运行Dockerfile配置时,IDEA可能报容器名字冲突,因为上一个同名容器还挂在Exited状态。通常处理方式是:

docker rm -f demo-container

或者直接在IDEA的容器列表里把旧容器删除再运行。如果端口被其他进程占用,docker ps看看哪个容器占了8080,先停掉它,再启动新的。这种问题多出现几次你就明白了,Docker的端口映射一旦被占用,新容器根本起不来,错误日志通常还会误导你去查应用配置。

6.5 现象五:容器起来了但内存不足被OOM杀

docker logs里能看到Native memory allocation (mmap) failed这类报错,或者docker ps -a显示容器Exited。原因多半是虚拟机内存分配太抠,Spring Boot和JVM一起把内存吃光了。解决方式:给虚拟机加内存,同时把JVM的-Xmx调小到适合容器的值。我习惯在容器里给256MB堆,虚拟机内存给4GB,这样即便同时跑两三个容器也不会崩。

提示:容器里看内存别再看宿主机free,要用docker stats查看每个容器的真实资源占用,这个命令在定位容器内存问题时比什么都直观。

最后再说几句

这套环境跑通之后,我的体会是:容器化真正解决的不仅是“换机器跑不了”的问题,更是把你从繁琐的环境安装中解放出来,让你开始用“镜像和容器”的思维去理解软件交付。我到现在还是建议初学者用VirtualBox而不是云服务器练习,因为虚拟机可以随意推倒重来,而且网络和系统的问题能亲手排查一遍,这些经验在云服务器上是很难完整经历的。一个小技巧收尾:给容器加上--restart=always,虚拟机重启后容器会自动恢复,省得每次开机都手动启动。把这套流程吃透,再往后接触集群、容器编排这些概念时,你会发现它们很多思路都源于Docker最基础的那几个命令。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询