如果你和我一样:
服务器是 Ubuntu
面板用的是 宝塔
项目想搞 CI / CD
但每次看到 Jenkins 就有点 PTSD 😵💫
那么 JPOM 基本就是“运维友好型 CI 工具”的最优解之一。
本文将完整记录一次 从零开始 的实战过程:
服务器命令行部署 JPOM Server → 解决 JDK17 兼容问题 → 宝塔接管进程 → 配置域名 + SSL 访问,全程可复现、可抄作业。
适合人群
Java 后端 / 运维工程师
正在使用宝塔,但 CI/CD 还没真正跑顺的人
系统默认 JDK17,却被 JPOM 默认 JDK8 坑过的人(你懂的)
JPOM 官方地址:
👉 https://jpom.top/
一、服务器与基础环境说明
服务器环境
系统:Ubuntu 20.04 / 22.04(实测均可)
面板:宝塔 Linux 面板(正式版)
运行服务:JPOM Server
JDK 环境规划(重点,敲黑板)
⚠️ 注意
JPOM 官方文档默认推荐 JDK8。
如果你服务器 系统默认是 JDK17,但 JPOM 还用 JDK8 的启动方式跑,那后果基本就是:
能启动,但跑不稳
能访问,但日志全是反射警告
能用,但你会开始怀疑人生
所以:
一定要显式指定 JPOM 使用 JDK17 启动。
二、宝塔中安装 JDK17
路径如下:
宝塔 → 网站 → Java 项目 → Java 环境管理
选择并安装:JDK 17
安装完成后,宝塔通常会放在类似路径:/www/server/java/jdk-17.0.8/bin/java
⚠️ 实际路径以你服务器为准,可以通过宝塔终端确认。
三、安装 JPOM Server(命令行)
本文采用 官方推荐的默认安装方式,其他安装方式可自行查阅JPOM官网。
1️⃣ 下载并安装 JPOM Server
在服务器中执行:curl -fsSL https://jpom.top/docs/install.sh | bash -s Server default
默认安装目录为:/usr/local/jpom-server/
2️⃣ 修改 JPOM 启动方式(JDK17 兼容核心)
进入目录:/usr/local/jpom-server/bin
创建并编辑 start.sh:
#!/bin/bash
#
# JPOM Server auto-start script (JDK 17 compatible)
#
# ========================
# 强制使用宝塔 JDK 17
# ========================
JAVA_HOME="/www/server/java/jdk-17.0.8"
JAVA="${JAVA_HOME}/bin/java"
if [ ! -x "$JAVA" ]; then
echo "ERROR: Java not found or not executable: $JAVA"
exit 1
fi
# ========================
# 基础函数
# ========================
function absPath() {
dir="$1"
case "$(uname)" in
Linux)
readlink -f "$dir"
;;
*)
cd "$dir" || exit
pwd
;;
esac
}
function errorExit() {
echo "$1" 2>&2
exit 1
}
# ========================
# 目录定义
# ========================
bin_abs_path=$(absPath "$(dirname "$0")")
base=$(absPath "$bin_abs_path/../")
conf_path="${base}/conf"
Lib="${base}/lib/"
LogPath="${base}/logs/"
tmpdir="${base}/tmp/"
Log="${LogPath}/stdout.log"
logback_configurationFile="${conf_path}/logback.xml"
application_conf="${conf_path}/application.yml"
pidfile="$base/bin/server.pid"
PID_TAG="JPOM_SERVER_APPLICATION"
server_log="${LogPath}/server.log"
# ========================
# JVM 参数
# ========================
JAVA_OPTS="-Xss1024k \
-XX:-OmitStackTraceInFastThrow \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=${LogPath}"
# 内存参数
USR_JVM_SIZE="-Xms512m -Xmx512m"
JAVA_OPTS="-server ${USR_JVM_SIZE} \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=250 \
-XX:+ExplicitGCInvokesConcurrent \
${JAVA_OPTS}"
JAVA_OPTS="${JAVA_OPTS} \
-Djava.awt.headless=true \
-Djava.net.preferIPv4Stack=true \
-Dfile.encoding=UTF-8 \
-Dlogging.config=${logback_configurationFile} \
-Dspring.config.location=${application_conf} \
-Djava.io.tmpdir=${tmpdir}"
# ========================
# JDK17 模块开放(关键)
# ========================
JAVA_OPTS="${JAVA_OPTS} \
--add-opens=java.base/java.net=ALL-UNNAMED \
--add-opens=java.base/java.nio=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED"
MAIN_ARGS="$*"
mode="$2"
RUN_JAR=""
# ========================
# 校验配置
# ========================
function checkConfig() {
mkdir -p "$LogPath" "$tmpdir"
if [[ ! -f "$logback_configurationFile" ]] || [[ ! -f "$application_conf" ]]; then
errorExit "Cannot find $application_conf or $logback_configurationFile"
fi
if [[ -z "${RUN_JAR}" ]]; then
if [ -f "$Lib/run.bin" ]; then
RUN_JAR=$(cat "$Lib/run.bin")
else
RUN_JAR=$(find "$Lib" -type f -name "*.jar" | sort | tail -1 | sed 's#.*/##')
fi
fi
if [ ! -f "${Lib}${RUN_JAR}" ]; then
errorExit "Jar not found: ${Lib}${RUN_JAR}"
fi
}
# ========================
# PID 获取
# ========================
function getPid() {
ps -ef | grep java | grep "$PID_TAG" | grep -v grep | awk '{print $2}'
}
# ========================
# 启动
# ========================
function start() {
pid=$(getPid)
if [ -n "$pid" ]; then
echo "JPOM Server already running, pid=$pid"
exit 0
fi
checkConfig
command="${JAVA} -Djpom.application.tag=${PID_TAG} ${JAVA_OPTS} -jar ${Lib}${RUN_JAR} ${MAIN_ARGS}"
echo "$command" > "$Log"
nohup $command >> "$Log" 2>&1 &
echo $! > "$pidfile"
echo "JPOM Server started, pid=$!"
}
# ========================
# 停止
# ========================
function stop() {
pid=$(getPid)
if [ -n "$pid" ]; then
echo "Stopping JPOM Server pid=$pid"
kill "$pid"
sleep 2
else
echo "JPOM Server not running"
fi
rm -f "$pidfile"
}
# ========================
# 状态
# ========================
function status() {
pid=$(getPid)
if [ -n "$pid" ]; then
echo "JPOM Server running, pid=$pid"
else
echo "JPOM Server stopped"
fi
}
# ========================
# 命令入口
# ========================
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
status)
status
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
;;
esac
3️⃣ 启动 JPOM
chmod +x start.sh
./start.sh start查看运行日志:tail -500f /usr/local/jpom-server/logs/stdout.log
如果启动报错,90% 都能从日志里直接看出原因,别盯着宝塔页面发呆。
四、宝塔接管 JPOM 进程(关键一步)
JPOM 启动后,我们要让 宝塔来接管这个 Java 进程,否则:
无法优雅重启
无法绑定域名
SSL 配置会非常难受
1️⃣ 接管 JPOM 线程
路径:
宝塔 → 网站 → Java 项目 → 添加项目 → 接管原项目
选择:
已启动的 JPOM Java 进程
2️⃣ 添加访问域名
在接管后的 Java 项目中:
设置 → 域名管理 → 添加域名
示例:jpom.template.com
五、Nginx 反向代理配置(JPOM 必须支持 WebSocket)
在该站点的 配置文件 中,加入以下核心配置:
server
{
// 省略其他配置
……
……
// 这里是核心配置
#PROXY-LOCAl-START 代理本地服务的相关配置
#PROXY-START/
location / {
proxy_pass http://127.0.0.1:2122;
proxy_http_version 1.1;
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;
# WebSocket(JPOM 必须)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
#PROXY-END/
……
……
}⚠️ JPOM 大量使用 WebSocket,如果少了这段,页面能打开,但功能会“间歇性失明”。
六、配置 SSL(Let’s Encrypt)
路径:
SSL → Let’s Encrypt
操作流程:
确认域名已正确解析到服务器 IP
申请证书
勾选 强制 HTTPS
宝塔会自动完成证书部署。
七、访问系统 & 初始化
现在,直接访问:https://jpom.template.com
根据页面提示创建 系统管理员账号,至此:
🎉 JPOM Server 已正式可用
总结一下(给未来的自己)
JPOM 很好用,但 JDK 版本一定要提前规划
宝塔 + JPOM = 对运维非常友好的组合
JDK17 跑 JPOM 不是问题
前提是:启动脚本你得自己接管
WebSocket + 反向代理 + SSL,一个都不能少
如果你现在:
想把构建流程从「手动登录服务器」升级到「可视化 + 自动化」
又不想被 Jenkins 的配置劝退
那 JPOM,真的值得一试。
评论区