跳过正文
  1. 系列/
  2. 技术番外/

番外:1G 小内存 VPS 部署 Java JSP 项目实战:Docker 本地构建 + 远程运行完美方案

其雁过无痕
作者
其雁过无痕
目录

在拥有了一台属于自己的 VPS(如 1核 1G内存,配置了 2G Swap)后,很多新手在尝试部署 Java 项目时,往往会选择直接在服务器上安装 Maven 或运行 docker build。但现实很残酷:Java 编译极其消耗内存,1G 的内存在构建瞬间就会被挤爆,导致系统卡死或触发 OOM (Out Of Memory) 杀掉进程。

破局思路:本地构建,远程运行 利用性能强大的本地 Windows 电脑(作为宿主机)进行代码编译、打包和 Docker 镜像构建。然后将纯净的“镜像成品”传输到 VPS 上,VPS 只需要负责简单的“运行”即可。

以下是完整的实战记录,带你一步步避坑,完成低配服务器的完美部署。

阶段一:VPS 端数据库准备与数据导入
#

由于资源有限,我们通过 Docker 启动一个轻量级、限制内存的 MySQL 容器。

1. 启动 MySQL 容器并限制内存
#

在 VPS 上执行以下命令启动 MySQL 8.0。关键点在于 -m 400m,这能防止 MySQL 随着运行时间增长吞噬掉所有内存。

sudo docker run -d \
  --name mysql-db \
  --restart unless-stopped \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=你的数据库密码 \
  -e MYSQL_DATABASE=你的数据库名 \
  -m 400m \
  mysql:8.0 \
  --default-authentication-plugin=mysql_native_password

2. 导入本地数据到 VPS 数据库
#

不要尝试在服务器上安装笨重的可视化工具,直接通过命令行搞定:

  • 第一步(Windows): 将本地导出的 xxx.sql 文件传到 VPS。如果你的 VPS 修改过默认 SSH 端口,记得加上 -P 参数。
scp -P 你的SSH端口号 xxx.sql root@你的VPS_IP:/root/
  • 第二步(VPS):sql 文件从系统拷贝到 MySQL 容器内部。
sudo docker cp /root/xxx.sql mysql-db:/xxx.sql

注意:这里的“mysql-db”是启动的MySQL容器的名字,上面的第一步的“–name”

  • 第三步(VPS): 让 MySQL 容器执行该文件导入数据。
sudo docker exec -it mysql-db mysql -uroot -p你的数据库密码 你的数据库名 -e "source /xxx.sql"

(验证:此时可通过本地电脑的 Navicat 或 IDEA 直连 VPS 的 3306 端口,确认数据导入成功。)


阶段二:本地 Java 项目调整与打包
#

为了适应 1G 内存的限制,我们需要对 Java 环境做向下兼容的优化。

1. 修改代码中的数据库连接
#

极易踩坑点: 之前代码里写的是 localhost:3306,现在必须修改为 你的VPS公网IP:3306,并且核对用户名、密码以及对应的数据库名称。

2. 将 Java 版本降级为 1.8 (Java 8)
#

Java 8 内存占用更小,且与传统的 JSP 技术(依赖 javax.servlet)兼容性最好。

  • 在 IDEA 的 Project Structure 中,将 SDK 和语言级别修改为 1.88

  • 打开 Maven 面板,依次双击 cleanpackage,在 target 目录下生成全新的 .war 包。

3. 编写 Dockerfile
#

新建一个名为 vps-deploy 的文件夹,将 .war 文件拷贝进去,并在同级目录新建 Dockerfile(无后缀):

# 选用 Tomcat 9 搭配轻量级 Java 8 环境 (slim版本体积更小)
FROM tomcat:9.0-jre8-slim

# 清理 Tomcat 默认页面,保持环境纯净
RUN rm -rf /usr/local/tomcat/webapps/*

# 将项目 war 包复制并重命名为 ROOT.war,实现根目录直接访问(无需在 IP 后加项目名)
COPY 你的项目名.war /usr/local/tomcat/webapps/ROOT.war

EXPOSE 8080
CMD ["catalina.sh", "run"]

阶段三:构建镜像与解决网络代理问题
#

1. 本地构建 Docker 镜像
#

vps-deploy 目录下打开终端,执行:

docker build -t my-jsp-app:v1 .

避坑指南(网络拉取失败): 如果在构建时遇到 failed to fetch oauth token 报错,说明国内直连 Docker Hub 被墙。

  • 解决方案: 启动你的代理客户端(如 v2rayN / Clash 等),开启 TUN 模式(虚拟网卡模式),让终端和 Docker Desktop 强制走代理网络,重新执行 build 即可顺利拉取基础镜像。

2. 导出镜像包
#

网络畅通完成构建后,将镜像打包为一个离线的 .tar 文件:

docker save -o my-jsp-app.tar my-jsp-app:v1

阶段四:VPS 最终部署与内存压榨
#

1. 传输镜像到 VPS
#

再次使用 scp 命令,把“集装箱”运上服务器:

scp -P 你的SSH端口号 my-jsp-app.tar root@你的VPS_IP:/root/

2. 加载并运行容器(核心优化点)
#

在 VPS 终端执行 sudo docker load -i my-jsp-app.tar 加载镜像。随后,使用以下命令运行你的 JSP 项目:

sudo docker run -d \
  --name my-web-app \
  --restart unless-stopped \
  -p 80:8080 \
  -e JAVA_OPTS="-Xms128m -Xmx256m" \
  my-jsp-app:v1

关键参数解析:

  • -p 80:8080:将服务器的 80 端口映射到容器的 8080,实现在浏览器直接输入 IP 访问网站。

  • -e JAVA_OPTS="-Xms128m -Xmx256m"绝对不能省的一句! 限制 Tomcat 的 JVM 初始内存为 128M,最大 256M。结合前面限制了 400M 的 MySQL,系统整体占用被严格控制在 700M 左右,完美适配 1G 内存机器!

3. 常见报错排查:端口冲突
#

如果运行上述命令报错 Bind for 0.0.0.0:80 failed: port is already allocated,说明 80 端口被占用了(可能是之前测试用的 Nginx,或者自带的 Web 服务)。

  • 排查命令: sudo ss -tulnp | grep :80sudo docker ps 查找占用 80 端口的进程或容器。
  • 解决: 使用 kill -9 PID 杀掉进程,或 docker rm -f 容器名 删掉占位容器。然后重新运行启动命令即可。

部署总结: 通过“本地编译打包 -> 生成纯净镜像 -> 导出导入 -> 云端限制内存运行”这一套标准的工业级工作流,不仅保护了小内存 VPS 脆弱的性能,还极大提高了部署成功率。即使未来更换服务器,只需要把 tar 包拷走,你的项目也能在 1 分钟内在新服务器上满血复活!

相关文章

番外:Dockerfile 常用指令详解

基础环境设置 # FROM:指定基础镜像 功能:这是每个 Dockerfile 的第一条指令(除注释外)。它决定了你的应用运行在什么环境之上。 示例:FROM ubuntu:24.04 (基于 Ubuntu 24.04 系统)或 FROM nginx:alpine (基于轻量级的 Nginx 镜像)。 WORKDIR:设置工作目录 功能:相当于 Linux 里的 cd 命令。后续的 RUN、CMD、COPY 等指令都会在这个目录下执行。如果目录不存在,Docker 会自动帮你创建。 示例:WORKDIR /app (将后续操作的默认路径设为容器内的 /app 目录)。 2. 文件复制 # COPY:复制文件/目录到容器中 功能:将宿主机(你的电脑或服务器)上的文件或目录,原封不动地拷贝到容器的指定路径下。 示例:COPY . /app (将当前宿主机目录下的所有文件,复制到容器的 /app 目录下)。 ADD:高级复制 功能:和 COPY 类似,但带有额外功能。如果复制的是一个本地的 .tar.gz 压缩包,ADD 会自动解压到目标路径;它也支持填入一个网络 URL 来下载文件。(新手推荐优先使用 COPY,语义更清晰)。 3. 执行命令与配置 # RUN:在构建镜像时执行命令 功能:这是构建阶段的主力军。通常用来安装软件包、创建文件夹、配置环境等。注意:每次 RUN 都会生成一个新的镜像层,所以通常会把多条命令用 && 连起来写。 示例:RUN apt-get update && apt-get install -y curl。 ENV:设置环境变量 功能:定义环境变量,后续的 RUN 指令可以使用,并且这些变量也会一直保留到容器运行阶段供你的程序读取。 示例:ENV MYSQL_ROOT_PASSWORD=my-secret-pw 或 ENV PORT=8080。 EXPOSE:声明监听端口 功能:这只是一个声明,告诉使用这个镜像的人,该容器内部的程序会使用哪个端口。它不会自动将端口映射到宿主机,映射依然需要在运行 docker run 时加上 -p 参数或在 docker-compose.yml 中配置。 示例:EXPOSE 80 (声明容器内的 Web 服务使用 80 端口)。 4. 容器启动指令 # CMD:容器启动时的默认命令 功能:指定容器跑起来之后要做的第一件事(例如启动 Nginx、运行 Java 后端应用等)。如果用户在 docker run 时手动指定了其他命令,CMD 的内容会被覆盖。 示例:CMD ["nginx", "-g", "daemon off;"]。 ENTRYPOINT:容器启动的主入口 功能:和 CMD 类似,但它不会被轻易覆盖。通常用于让容器表现得像一个独立的可执行程序。如果同时存在 ENTRYPOINT 和 CMD,CMD 的内容会作为参数传递给 ENTRYPOINT。 综合示例 # 为了方便理解,这里提供一个部署简单前端/静态网页的通用 Dockerfile 模板:

任务 3:熟练使用终端复用工具

核心目标 # 操作:安装并学习使用 tmux。 收获:掌握在服务器上跑长耗时任务的能力。即使本地 SSH 突然断开,任务依然会在后台运行,不会前功尽弃。 核心工具:tmux # tmux (Terminal Multiplexer) 是现代服务端开发的必备工具,解决了远程连接中途掉线导致任务中断的痛点。

任务 2:分配虚拟内存 (Swap)

Linux 服务器运维笔记:分配虚拟内存 (Swap) 避坑与实操 # 0. 背景与目标 # 在小内存(如 1GB RAM)的云服务器上运行 Java、MySQL 或进行前端构建时,物理内存极易耗尽导致进程被系统杀掉(OOM)。Swap(交换空间) 充当了“虚拟内存”的角色,是服务器在高负载下的“救命支撑”。