文章前言
永远把别人对你的批评记在心里,别人的表扬,就把它忘了。Hello 大家好~!我是淮桑榆
本文主要是记录如何在阿里云 Ubuntu 上使用 Jenkins + PM2 部署 Express 接口,特地记录下与诸位分享,如有阐述不对的地方欢迎在评论区指正。
观看到文章最后的话,如果觉得不错,可以点个关注或者点个赞哦!感谢~❤️
前系文章
node全栈(一):在阿里云 Ubuntu 上部署 Jenkins(Docker 方式)实战指南
文章主体
感谢各位观者的耐心观看,阿里云 Ubuntu 上使用 Jenkins + PM2 部署 Express 接口的正片即将开始,且听淮桑榆娓娓道来

本地开发Express接口
在本地开发一个基础的Express接口,暂时返回固定的测试数据
初始化项目
mkdir node-api && cd node-api //创建文件夹
npm init -y //初始化 npm 项目
npm install express //安装
编写服务代码
在文件中创建index.js文件
const express = require('express')
const app = express()
const port = 3000
/* ------------------------------- 允许跨域,方面前端调用 ------------------------------ */
app.use((req, res, next)=>{
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
next()
})
/* ------------------------------- 用户接口,返回用户数据 ------------------------------ */
app.get('/api/user', (req, res)=>{
const result = {
status: 200,
message: '成功返回用户数据',
data: {
age: 18,
name: '淮桑榆',
bobbies: ["编程", "服务器部署"]
}
}
res.json(result)
})
app.listen(port, () => {
console.log(`服务已启动,访问:http://localhost:${port}/api/user`);
})
本地测试
运行服务,在浏览器中访问 http://localhost:3000/api/user, 查看JSON数据是否正常返回
node app.js
代码托管到GitHub上
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin git@github.com:huaisangyu/node-api.git
git push -u origin main
配置 SSH
在服务器终端生成SSH Key
在Docker容器中进入Jenkins运行环境
docker exec -it -u jenkins jenkins bash
创建 .ssh 目录并设置权限
mkdir -p /var/jenkins_home/.ssh
chmod 700 /var/jenkins_home/.ssh
在 Jenkins 容器中生成 SSH Key
ssh-keygen -t ed25519 -C "jenkins@huaisangyu" -f /var/jenkins_home/.ssh/id_ed25519 -N ""
设置私钥权限
chmod 600 /var/jenkins_home/.ssh/id_ed25519
chmod 644 /var/jenkins_home/.ssh/id_ed25519.pub
查看公钥(Jenkins中生成的)
cat /var/jenkins_home/.ssh/id_ed25519.pub
将公钥(Jenkins中生成的)添加到GitHub
-
打开项目仓库 → 点击 Settings → 点击左侧 Deploy keys → 点击 Add deploy ke
-
填写:
- Title:Jenkins
- Key:粘贴刚才的公钥
-
保存
将公钥(Jenkins中生成的)复制到宿主机上
- 在宿主机执行
sudo mkdir -p /root/.ssh
sudo chmod 700 /root/.ssh
- 将公钥(Jenkins中生成的)追加到宿主机的
echo "公钥" | sudo tee -a /root/.ssh/authorized_keys
- 设置权限
sudo chmod 600 /root/.ssh/authorized_keys
sudo chown root:root /root/.ssh/authorized_keys
- 添加 GitHub SSH 主机到 known_hosts
# 在Jenkins容器里执行
ssh-keyscan -t ed25519 github.com >> /var/jenkins_home/.ssh/known_hosts
chmod 644 /var/jenkins_home/.ssh/known_hosts
测试免密登录
因为到时Jenkins执行Pipeline Script时要免密进入服务器
# 在Jenkins容器里执行
ssh -i /var/jenkins_home/.ssh/id_ed25519 -v root@宿主机IP
查看私钥(Jenkins中生成的)
cat /var/jenkins_home/.ssh/id_ed25519
在Jenkins直凭证
将上面的私钥填入下面的密闻里面
在宿主机中生成 SSH Key
chmod 700 .ssh
cd .ssh
ssh-keygen -t ed25519 -C "root@huaisangyu" -f /root/.ssh/id_ed25519 -N ""
ssh-keyscan github.com >> /root/.ssh/known_hosts
chmod 644 /root/.ssh/known_hosts
查看公钥(宿主机中生成的)
cat /root/.ssh/id_ed25519.pub
将公钥(宿主机中生成的)添加到GitHub
-
打开项目仓库 → 点击 Settings → 点击左侧 Deploy keys → 点击 Add deploy ke
-
填写:
- Title:root
- Key:粘贴刚才的公钥
-
保存
配置 PM2
安装n(Node.js版本管理工具)
n 依赖 npm,所以我们需要先用 apt 装一个基础版 Node.js,仅用于跑 n,后续真正的 Node 版本由 n 接管
- 用 apt 安装基础 Node(版本无所谓)
curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
apt install -y nodejs
- 使用npm安装n(全局)
npm install -g n
用n安装和管理Node.js
- 安装你需要的Node版本
n lts
安装完成后,n会把Node装到:/usr/local/bin/node
- 切换并确认Node版本
node -v
which node
我们发现node -v输出的还是老版本,这个时候我们要修复path
执行
export PATH="/usr/local/bin:$PATH"
并写入 shell 配置
bash 用户
echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
zsh 用户
echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
移除apt安装的Node(让n完全接管)
这一步非常重要,否则以后容易出现版本混乱
apt remove -y nodejs npm
node -v
npm -v
which node
如果能正常输出版本,并且路径是:/usr/local/bin/node,说明n接管成功了
全局安装 PM2
npm install -g pm2
Jenkins配置
新建 Jenkins 任务
-
Jenkins Dashboard → 新建Item(New Item)
-
名称:
node-api -
类型:流水线(Pipeline)
Pipeline Script
pipeline {
agent any
environment {
DEPLOY_HOST = "公网ip"
DEPLOY_USER = "root"
DEPLOY_PATH = "/var/project/node-api"
SSH_KEY = "/var/jenkins_home/.ssh/id_ed25519"
}
parameters {
gitParameter(
name: 'Branch',
type: 'PT_BRANCH',
defaultValue: 'main',
description: '选择要部署的分支',
branchFilter: 'origin/(.*)',
selectedValue: 'DEFAULT',
sortMode: 'NONE',
quickFilterEnabled: true
)
string(name: 'Repository', defaultValue: 'git@github.com:huaisangyu/node-api.git', description: 'Git仓库地址')
credentials(name: 'Creadential', defaultValue: 'github-ssh', description: '用于访问Git仓库的SSH私钥')
}
stages {
stage('准备参数') {
steps {
script {
repo = params.Repository.trim()
branch = params.Branch
cred = params.Creadential
echo "使用分支:${branch}"
echo "使用仓库:${repo}"
echo "使用凭据ID:${cred}"
}
}
}
stage('刷新Git分支信息') {
steps {
echo "刷新Git Parameter分支列表"
git changelog: true,
branch: "${branch}",
url: "${repo}",
credentialsId: "${cred}"
}
}
stage('拉取代码') {
steps {
echo "在宿主机拉取最新代码,分支:${branch}"
sh """
ssh -i ${SSH_KEY} -o StrictHostKeyChecking=yes ${DEPLOY_USER}@${DEPLOY_HOST} '
if [ ! -d ${DEPLOY_PATH} ]; then
git clone -b ${branch} ${repo} ${DEPLOY_PATH}
else
cd ${DEPLOY_PATH} && git fetch --all && git checkout ${branch} && git reset --hard origin/${branch}
fi
'
"""
}
}
stage('安装依赖') {
steps {
echo "在宿主机安装依赖"
sh """
ssh -i ${SSH_KEY} -o StrictHostKeyChecking=yes ${DEPLOY_USER}@${DEPLOY_HOST} 'cd ${DEPLOY_PATH} && npm install'
"""
}
}
stage('启动/重启服务') {
steps {
echo "使用 PM2 启动或重启 Node 服务"
sh """
ssh -i ${SSH_KEY} -o StrictHostKeyChecking=yes ${DEPLOY_USER}@${DEPLOY_HOST} '
cd ${DEPLOY_PATH} && pm2 start index.js --name node-api --update-env || pm2 restart node-api
pm2 save
'
"""
}
}
stage('查看服务状态') {
steps {
echo "检查服务状态"
sh """
ssh -i ${SSH_KEY} -o StrictHostKeyChecking=yes ${DEPLOY_USER}@${DEPLOY_HOST} 'pm2 status node-api'
"""
}
}
}
post {
success {
echo "部署成功 ✅ 分支:${branch}"
}
failure {
echo "部署失败 ❌"
}
}
}
Jenkins控制台输出
网页访问
成功运行起项目并返回数据
部署其他分支
切换到test分支,并将数据返回调整下,然后提交到远程仓库
/*
* @Description :
* @Author : Sherlock
* @Date : 2026-01-20 14:23:29
* @LastEditors : Sherlock
* @LastEditTime : 2026-01-20 14:23:31
* @FilePath : /index.js
*/
const express = require('express')
const app = express()
const port = 3000
/* ------------------------------- 允许跨域,方面前端调用 ------------------------------ */
app.use((req, res, next)=>{
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
next()
})
/* ------------------------------- 用户接口,返回用户数据 ------------------------------ */
app.get('/api/user', (req, res)=>{
const result = {
status: 200,
message: '成功返回用户数据',
data: {
age: 18,
name: '测试家的淮桑榆',
bobbies: ["编程", "服务器部署"]
}
}
res.json(result)
})
app.listen(port, () => {
console.log(`服务已启动,访问:http://localhost:${port}/api/user`);
})
现在回到Jenkins界面,然后选择test分支后点击Build
然后我们再次进行网页访问,发现数据已经产生变化了
配置HTTPS(加密访问)
在已经实现HTTP反向代理的基础上,通过 Let's Encrypt 申请免费 SSL 证书,将访问地址从 http:// 升级为 https://(如 https://huaisangyu.top/api/user),并自动将 HTTP 请求重定向到 HTTPS
安装Cerbot
sudo apt update
sudo apt install certbot python3-certbot-nginx -y
申请 SSL 证书并自动配置 Nginx
替换为你的域名(与 Nginx 配置中的 server_name 一致)
sudo certbot --nginx -d 你的域名
执行过程中的交互选项:
- 输入邮箱(用于证书过期提醒)→ 回车
- 同意服务条款(输入
Y)→ 回车 - 是否共享邮箱(输入
N拒绝)→ 回车
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
(Enter 'c' to cancel): jianpoli0214@gmail.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.6-August-18-2025.pdf. You must agree
in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N
Account registered.
Requesting a certificate for huaisangyu.top
成功标志:
输出包含以下内容,说明证书申请并配置成功:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/huaisangyu.top/fullchain.pem
Key is saved at: /etc/letsencrypt/live/huaisangyu.top/privkey.pem
This certificate expires on 2026-04-29.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
Deploying certificate
Successfully deployed certificate for huaisangyu.top to /etc/nginx/sites-enabled/jenkins
Congratulations! You have successfully enabled HTTPS on https://huaisangyu.top
验证HTTPS配置
-
访问测试
在浏览器中打开 huaisangyu.top/api/user ,地址栏会显示小锁图标(表示加密连接),并返回正常数据
-
重定向测试
访问 huaisangyu.top/api/user (HTTP),会自动跳转到 https:// 版本
-
证书信息查看
点击浏览器地址栏的小锁 →「证书」,可查看证书有效期(默认 90 天)
配置证书自动续期
Let's Encrypt 证书有效期为 90 天,需设置自动续期:
# 测试自动续期功能(无错误输出即为正常)
sudo certbot renew --dry-run
# 添加定时任务(每天自动检查续期)
sudo crontab -e
在打开的文件中添加一行(每天凌晨 2 点执行续期检查):
0 2 * * * /usr/bin/certbot renew --quiet
保存退出:Ctrl+O → 回车 → Ctrl+X
结尾营业
看官都看到这了,如果觉得不错,可不可以不吝啬你的小手手帮忙点个关注或者点个赞
