返回文章列表 →

Note

web项目部署

把web全栈项目部署到Ubuntu系统

发布于 更新于

Ubuntu 部署步骤

1. 部署默认值

本方案使用 nvm 管理 Node.js,因为不同项目可能需要不同 Node 版本。注意:nvm 是“按用户安装”的,所以必须在对应用户下安装,如这里使用的是 deployer ,不要在 root 下安装。并且项目以my_cs_logs为例。

占位符:

<你的服务器IP>      例如 1.2.3.4
<你的子域名>        例如 cs.example.com
<你的Git仓库地址>   例如 git@github.com:xxx/my_cs_logs.git
<数据库强密码>      你自己设置的 MySQL 密码
<JWT随机密钥>       后面用 openssl 命令生成
<项目目录>          以下为my_cs_logs
<数据库>          以下为my_cs_logs

部署结构:

Nginx:负责域名、HTTPS、前端静态文件、API 反向代理
NestJS:后端服务,只监听服务器本机 3000 端口
MySQL:安装在同一台 Ubuntu 服务器
nvm:给 deployer 用户管理 Node.js 版本

2. 环境配置

2.1域名解析和安全组

域名绑定IP

在域名控制台添加解析:

记录类型:A
主机记录:<子域名前缀>
记录值:<你的服务器IP>

例如 `cs.example.com`:
记录类型:A
主机记录:cs
记录值:1.2.3.4

端口处理

放行:
22    SSH 登录服务器
80    HTTP 访问、申请 HTTPS 证书
443   HTTPS 正式访问

不要放行:
3000  本地后端端口,只允许 Nginx 在服务器内部访问
3306  本地MySQL 端口,只允许后端在服务器内部访问

验证

# 在你本地电脑执行:测试域名是否已经解析到服务器 IP
ping <你的子域名>

2.2登录服务器并创建部署用户

目的:角色化,不同角色负责不同的职责,专门创建一个负责部署项目的角色

# 用 root 登录服务器;如果云厂商默认用户是 ubuntu,就把 root 换成 ubuntu
ssh root@<你的服务器IP>
# 创建 deployer 用户,后续项目都用这个用户部署
adduser deployer
# 给 deployer 用户添加 sudo 权限,让它能执行管理员命令
usermod -aG sudo deployer
# 切换到 deployer 用户;nvm 必须在这个用户下安装
su - deployer

2.3 安装基础软件

更新软件

# 更新 apt 软件源索引
sudo apt update

# 升级服务器已有软件包;-y 表示自动确认
sudo apt upgrade -y

安装必须软件

# 安装 Git、curl、证书工具、编译工具、Nginx、MySQL
sudo apt install -y git curl ca-certificates build-essential nginx mysql-server

安装Node.js(使用nvm)

# 确认当前用户是 deployer;如果不是 deployer,先不要继续
whoami

# 安装 nvm;这是官方 nvm 仓库当前推荐的安装方式
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash

# 让当前终端立即加载 nvm;否则可能要重新登录后 nvm 才能使用
source ~/.bashrc


# ======================================================================================
❗如果 `source ~/.bashrc` 后仍提示 `nvm: command not found`,执行以下命令,没有问题则跳过
# 手动加载 nvm 脚本;用于解决当前 shell 没有自动加载 nvm 的情况
export NVM_DIR="$HOME/.nvm"
# 如果 nvm.sh 存在,就加载它
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# ======================================================================================


# 安装 Node.js 22 最新小版本(根据项目需求安装对应版本即可)
nvm install 22

# 切换当前终端使用 Node.js 22
nvm use 22

# 把 Node.js 22 设为 deployer 用户默认版本,重新登录后也会默认使用它
nvm alias default 22

验证

# 查看 MySQL 状态;active (running) 表示正常运行
sudo systemctl status mysql

# 查看 Nginx 状态;active (running) 表示正常运行
sudo systemctl status nginx


# 查看 Node.js 版本,应当是 v22.x.x,且不低于 v22.12.0
node -v

# 查看 npm 版本,确认 npm 可用
npm -v

# 查看 node 命令路径;应当在 /home/deployer/.nvm/ 下面
which node

3. 项目配置

3.1 获取项目

创建目录

# 创建 /var/www 目录,通常用来存放网站项目
sudo mkdir -p /var/www

# 把 /var/www 目录所有者改成 deployer,后续 deployer 才能写入项目文件
sudo chown -R deployer:deployer /var/www

克隆项目

# 从 Git 仓库克隆项目到服务器
git clone <你的Git仓库地址> /var/www/my_cs_logs

# 如果是私有仓库,需要获取秘钥,以下为获取方法(以Gitee为例):

# 生成服务器 SSH key,用来访问 GitHub/Gitee 私有仓库
# -t key 类型
# -C 注释
ssh-keygen -t ed25519 -C "deployer@my_cs_logs"
# 会出现需要确定的内容,中间通过三次回车键确定即可
# 查看生成的 SSH 公钥和私钥
ls ~/.ssh/ # 输出:私密文件id_ed25519   公钥文件id_ed25519.pub
# 读取公钥文件 ~/.ssh/id_ed25519.pub
cat ~/.ssh/id_ed25519.pub 
# 输出如ssh-ed25519 AAAA***5B Gitee SSH Key,把它添加到个人的SSH 公钥中即可
# 在通过SSH克隆仓库即可,如git clone git@gitee.com:July1st/my_cs_logs.git

# 进入项目根目录
cd /var/www/my_cs_logs

3.2 初始化 MySQL

进入MYSQL命令行

# 进入项目根目录,确保能找到 my_cs_logs.sql
cd /var/www/my_cs_logs
# 导入项目 SQL;会创建 my_cs_logs 数据库和表
sudo mysql < my_cs_logs.sql
# 进入 MySQL 管理命令行
sudo mysql

创建MYSQL账户

-- 创建项目专用数据库用户,只允许服务器本机连接
CREATE USER '<数据库用户名>'@'localhost' IDENTIFIED BY '<数据库强密码>';

-- 授权项目用户读写 my_cs_logs 数据库
GRANT SELECT, INSERT, UPDATE, DELETE ON my_cs_logs.* TO '<数据库用户名>'@'localhost';

-- 刷新权限,让授权立即生效
FLUSH PRIVILEGES;

-- 退出 MySQL 命令行
EXIT;

验证账号

# 使用项目数据库用户登录,会要求输入 <数据库强密码>
mysql -u <数据库用户名> -p my_cs_logs
-- 查看是否已经成功导入表
SHOW TABLES;

-- 退出 MySQL
EXIT;

3.3 配置后端环境变量

# 进入 NestJS 后端目录
cd /var/www/my_cs_logs/service-nest
# 复制环境变量模板为正式 .env 文件
cp .env.example .env
# 编辑 .env 文件;nano操作:Ctrl + O 保存;   Enter 确认文件名;    Ctrl + X 退出
nano .env

.env 内容修改

DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=<数据库用户>
DB_PASSWORD=<数据库强密码>
DB_DATABASE=my_cs_logs

JWT_SECRET=<JWT随机密钥>
JWT_EXPIRES_IN=7d

NODE_ENV=production

// 配置邮箱 SMTP
MAIL_HOST=
MAIL_PORT=587
MAIL_SECURE=false
MAIL_USER=
MAIL_PASS=
MAIL_FROM=
MAIL_CODE_EXPIRES_MINUTES=5
MAIL_CODE_RESEND_SECONDS=60
# 生成 JWT_SECRET,用输出结果替换 .env 里的 <JWT随机密钥>
openssl rand -base64 48

3.4 构建并启动后端

构建并启动

# 进入后端目录
cd /var/www/my_cs_logs/service-nest

# 根据 package-lock.json 安装后端依赖,服务器部署推荐 npm ci
npm ci

# 编译 NestJS,把 TypeScript 编译到 dist 目录
npm run build

# 创建日志目录,后端会写入 logs/error.log 和 logs/combined.log
mkdir -p logs

# 安装 PM2;因为使用 nvm,所以不要加 sudo
npm install -g pm2

# 查看 pm2 路径,应当在 /home/deployer/.nvm/ 下面
which pm2

# 用 PM2 启动后端;--cwd 用来指定工作目录,保证 .env 能被读取
pm2 start dist/main.js --name my-cs-logs-api --cwd /var/www/my_cs_logs/service-nest

# 查看 PM2 进程状态,online 表示后端正在运行
pm2 status

# 查看后端日志;如果卡住不退出,按 Ctrl + C 返回
pm2 logs my-cs-logs-api

# 保存当前 PM2 进程列表,用于服务器重启后恢复
pm2 save

# 生成 PM2 systemd 开机自启配置;适配 deployer 用户和 nvm 环境
pm2 startup systemd -u deployer --hp /home/deployer
# 上一条命令会输出一条 `sudo ...` 开头的命令,把那条 sudo 命令复制出来,再执行一次。

验证

# 直接请求服务器本机后端接口,确认 NestJS 正常
curl http://127.0.0.1:3000/api/csLogs/getDictionary

3.5 构建前端

# 进入前端目录
cd /var/www/my_cs_logs/web

# 根据 package-lock.json 安装前端依赖
npm ci

# 构建前端,生成 dist 静态文件。构建产物:/var/www/my_cs_logs/web/dist
npm run build

4. 代理配置(Nginx)

创建配置文件

# 创建 Nginx 站点配置文件
sudo nano /etc/nginx/sites-available/my_cs_logs

写入

server {
    listen 80;
    server_name <你的子域名>;

    root /var/www/my_cs_logs/web/dist;
    index index.html;

    client_max_body_size 20m;

    location /api/ {
        proxy_pass http://127.0.0.1:3000;
        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;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}

配置含义

listen 80                      监听 HTTP
server_name                    绑定你的子域名
root                           指向前端 dist 目录
location /api/                 API 请求交给后端
proxy_pass 127.0.0.1:3000      转发到 NestJS
try_files ... /index.html      支持 Vue 路由刷新不 404

启用站点

# 把 sites-available 里的配置链接到 sites-enabled,表示启用这个站点
sudo ln -s /etc/nginx/sites-available/my_cs_logs /etc/nginx/sites-enabled/my_cs_logs

# 删除 Nginx 默认站点,避免默认欢迎页占用域名
sudo rm -f /etc/nginx/sites-enabled/default

# 检查 Nginx 配置语法
sudo nginx -t

# 重新加载 Nginx 配置
sudo systemctl reload nginx

验证

http://<你的子域名>

5. 配置 HTTPS

# 安装 snap 基础组件
sudo snap install core

# 更新 snap core
sudo snap refresh core

# 移除 apt 版本 certbot,避免和 snap 版本冲突
sudo apt remove -y certbot

# 安装官方推荐的 snap 版 certbot
sudo snap install --classic certbot

# 创建 certbot 命令软链接,让系统能直接执行 certbot
sudo ln -sf /snap/bin/certbot /usr/bin/certbot

# 为子域名申请 HTTPS 证书,并让 certbot 自动修改 Nginx 配置
sudo certbot --nginx -d <你的子域名>

# 测试证书自动续期是否正常
sudo certbot renew --dry-run

验证

https://<你的子域名>

6. 问题排查

# 查看后端 PM2 进程是否在线
pm2 status

# 查看 Nginx 是否正常运行
sudo systemctl status nginx

# 查看 MySQL 是否正常运行
sudo systemctl status mysql

# 查看 HTTPS 首页响应头
curl -I https://<你的子域名>

# 测试 API 是否能通过域名访问
curl https://<你的子域名>/api/csLogs/getDictionary

浏览器检查:

1. https://<你的子域名> 可以打开
2. 刷新页面不 404
3. 登录/注册页面可以打开
4. API 请求不报 502
5. 配好 SMTP 后验证码能收到

14. 后续更新项目

# 进入项目根目录
cd /var/www/my_cs_logs

# 拉取最新代码
git pull

数据库更新:

# 进入项目根目录
cd /var/www/my_cs_logs

# 创建数据库备份目录
mkdir -p ~/db-backups

# 备份当前数据库,防止迁移 SQL 执行失败后无法恢复
sudo mysqldump --single-transaction --no-tablespaces my_cs_logs > ~/db-backups/my_cs_logs_$(date +%F_%H%M%S).sql

# 执行数据库脚本
sudo mysql my_cs_logs < service-nest/sql/XXXXX.sql

后端更新:

# 进入后端目录
cd /var/www/my_cs_logs/service-nest

# 确认node版本
nvm use 22

# 重新安装后端依赖(非必须)
npm ci

# 重新构建后端
npm run build

# 重启后端服务
pm2 restart my-cs-logs-api

前端更新:

# 进入前端目录
cd /var/www/my_cs_logs/web

# 确认node版本
nvm use 22

# 重新安装前端依赖(非必须)
npm ci

# 重新构建前端
npm run build

# 重新加载 Nginx
sudo systemctl reload nginx

15. 常见问题

  • nvm: command not found:确认你是在 deployer 用户下;执行 source ~/.bashrc,或重新 SSH 登录。
  • node 路径不在 /home/deployer/.nvm/:说明当前可能没用 nvm 的 Node,执行 nvm use 22。
  • sudo npm 找不到或版本不对:使用 nvm 后不要用 sudo npm install -g ...。
  • 502 Bad Gateway:后端没启动,检查 pm2 status 和 pm2 logs my-cs-logs-api。
  • 页面打不开:检查 DNS、安全组、sudo systemctl status nginx。
  • 接口失败:检查 Nginx /api/ 代理和后端 3000 端口。
  • 数据库失败:检查 .env 的数据库账号、密码、库名。
  • 验证码失败:检查 SMTP 配置。

参考文档