如果你和我一样,手上有个跑在阿里云ECS上的Node服务,大概率经历过这套流程:改完代码 push 上 GitHub,然后打开终端 ssh 登录服务器,进项目目录,git pull,装依赖,pm2 restart,再看一眼日志确认没崩。单次操作五分钟,听起来不长,但架不住每次发布都要走一遍,而且人的操作是不可靠的——忘装依赖、拉错分支、漏了重启,任何一个环节出问题,线上就得背锅。
后来我花了大半天,用 GitHub Actions 把这套流程整个自动化了:只需要 push 代码,服务器自己完成拉取、安装依赖、重启进程。这台 ECS 上跑的是一个 Node 接口服务,托管在 GitHub 私有仓库里,整套方案不花一分钱,配置一次之后以后每回发版都只做一件事:push。这篇文章就是把这套配置从头到尾记录下来,包括服务器端的准备、工作流文件的写法,以及我实际踩过的一些坑。代码都是可以直接复制改改就用的,适合个人项目或者小团队内部服务,想搭一套省心部署链路的朋友直接照着操作就行。
1. 自动化部署的整体思路与方案选型
1.1 手动部署的痛点和自动化方案的核心价值
先把手动部署的流程摆出来,你感受一下。
ssh root@服务器IP cd /var/www/myapp git pull origin main npm ci --omit=dev pm2 restart myapp pm2 logs --lines 50五条命令,熟练工做下来两分钟。但它的问题是,这两分钟必须由“人”来完成,而人在重复劳动里一定会犯错。我有一次改了数据库配置忘了提交,结果 pull 之后服务起不来;还有一次同事在服务器上手动改过代码,我这边一 pull 直接冲突,被迫现场处理合并。这类事情只要经历过一次,你就明白自动化部署不是折腾,是刚需。
自动化部署的核心价值不只是“省时间”。它把“部署”这件事从一个靠记忆的体力活,变成了一条可回放、可追溯、结果可预期的流水线。谁在什么时间 push 了哪个分支,流水线执行到哪一步失败,失败原因是什么,全都有记录。出了问题不再是“你刚才在服务器上敲了什么”,而是直接看 workflow 日志。对于单人维护的项目,它帮你省掉琐碎操作;对于多人协作,它把发版权限收拢成“合到主分支”这一个动作,规则清晰多了。
具体到 Node 项目,还有一个额外的好处:依赖安装的一致性。npm ci 会根据 package-lock.json 安装完全一致的依赖版本,避免“你本地没问题,服务器一装就缺包”的经典尴尬。这个下面会细说。
1.2 为什么选择 GitHub Actions 而不是 Jenkins
有人可能觉得自动化部署应该上 Jenkins 这种专业工具。我之前也搭过 Jenkins,但对个人项目和轻量场景来说,它太重了:要单独一台机器跑服务,要配构建节点,插件体系虽然强大但也有学习成本,而且 Jenkins 本身也是要维护的。GitHub Actions 就不一样。
第一,它和 GitHub 仓库天然是一体的,不需要额外的平台账号,不需要代码库做镜像同步,仓库里建一个 .github/workflows 目录,放一个 yml 文件,推送上去就生效。第二,它的免费额度对个人开发者非常够用,跑一个简单的部署任务不会占太多资源。第三,配置文件本身就在仓库里,随代码走,改部署流程和改业务代码一样经过 review,这个版本管理习惯是 Jenkins 那种网页配置很难做到的。
那什么情况不适合 GitHub Actions?如果你需要部署的平台不在 GitHub 上托管,比如代码在别的代码托管平台,那 Actions 就不合适了;如果你的生产环境对安全和流程有很强的审计要求,必须内网部署,那也另说。但题目既然是“GitHub 项目部署阿里云服务器”,Actions 就是顺理成章的选择。
1.3 完整链路拆解:从 push 到服务重启
整个自动化的执行链路我是这么设计的:
- 你本地
git push origin main,代码提交到 GitHub 仓库。 - GitHub 检测到 push 事件,自动触发仓库里名为 deploy 的 workflow。
- workflow 里配置的 job 在 GitHub 的临时虚拟机上启动(ubuntu-latest),它负责执行部署脚本。
- 虚拟机通过 SSH 连接阿里云 ECS,使用预先配置好的密钥完成免密登录。
- 登录后在服务器上执行部署脚本:进入项目目录、拉取代码、安装依赖、重启 PM2 进程。
- 部署完成,workflow 显示绿色对号,服务已经跑上新代码。
这个设计里有两个关键选择。第一个是构建在哪个环节做:Node 后端项目通常不需要编译,所以可以直接在服务器上 npm ci 装依赖;如果项目是前端或需要构建的,我建议在虚拟机上构建好产物,再通过 rsync 把产物同步到服务器,而不是在服务器上装构建工具链。第二个是连接方式:选择 SSH 密钥认证而不是密码认证,因为密码会以明文形式存放在 workflow 变量里,一旦泄露就等于服务器拱手让人;密钥虽然也不是百分百绝对安全,但配合权限控制,泄露面小很多,而且吊销一个 key 比改密码更精准。
2. 服务器端准备:把阿里云ECS调校到位
2.1 安装 Node.js 环境与 Git
动手配 Actions 之前,服务器基础环境必须先就绪。我假设你买的是阿里云 ECS,系统是 Ubuntu 22.04(Debian 系操作一样,如果是 CentOS 把 apt 换成 yum/dnf 即可)。装好系统后的第一步,先把系统软件源更新一下,然后安装 Git:
apt update && apt upgrade -y apt install -y git curlNode.js 的安装我这里强烈建议用 nvm,而不是直接 apt install nodejs。原因是 apt 源里的 Node 版本往往滞后,而项目一旦用了新语法或者新版依赖,对 Node 版本有要求,nvm 可以让你随时切换版本,不用重装系统。给自己服务器装 nvm 的方式很成熟:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc nvm install 20 nvm alias default 20 node -v为什么要锁一个默认版本?因为服务器重启后,如果没有设置 alias,shell 默认 path 里可能没有 node,PM2 开机自启的时候就会找不到 node 而启动失败。这个细节我吃过亏:配好 PM2 的 startup 后没设默认版本,机房一断电重启,所有服务全部起不来,排查了半天才发现是 nvm 的 node 没进 PATH。设好nvm alias default之后再执行pm2 save,可以避免这个问题。
还有一个容易被忽略的操作:确认 Git 的身份配置。服务器上拉代码时通常用 HTTPS 方式,如果仓库是私有的,需要在服务器上配一个访问令牌(GitHub 的 Personal Access Token),否则git pull无法认证。两个方案:一是在项目 remote 地址里带 token(不推荐,容易泄露在 shell history);二是使用 GitHub CLI 做认证,或者配置 credential helper。个人实践里最省心的方式是:用 deploy key。给仓库单独加一把部署专用的 SSH 公钥,服务器只对这个仓库有拉取权限,权限边界清晰,也不会影响其他仓库。
2.2 用 PM2 管理 Node 进程的三个理由
很多新人上来就是node app.js直接跑,进程挂了就挂了,没有人帮他拉起来。PM2 解决的就是进程守护这件事,我的项目里用 PM2 管理 Node 服务有三个核心理由:
第一,崩溃自动重启。Node 进程偶发未捕获异常会直接退出,有了 PM2,它检测到进程退出会自动拉起,配合--max-restarts和min_uptime参数还能避免“刚起来又崩溃”的无限重启循环,保护服务器 CPU。
第二,统一日志管理。直接node app.js的时候,console.log 打到哪里完全看 shell 心情,一旦关闭终端就丢了。PM2 会自动把 stdout 和 stderr 写入日志文件,排查问题时pm2 logs myapp --lines 200一把梭,比翻 nohup.out 舒服太多。
第三,开机自启。pm2 startup会生成一个 systemd 服务,pm2 save会把当前进程列表保存下来,服务器重启后 PM2 自动拉起所有业务进程,不用登录服务器手动再敲一遍 pm2 start。
我的项目在服务器上的目录结构是这样的:
/var/www/myapp/ ├── app.js ├── ecosystem.config.js ├── package.json ├── package-lock.json └── .env其中 ecosystem.config.js 是 PM2 的声明式配置,这个文件建议放在项目仓库里,这样部署脚本可以直接引用:
module.exports = { apps: [ { name: 'myapp', script: './app.js', instances: 1, exec_mode: 'fork', env: { NODE_ENV: 'production' } } ] };为什么用 fork 模式而不是 cluster?我这个服务是单线程足够撑住量级的,cluster 模式下多实例会带来端口共享和内存开销问题,对一台 2C4G 的机器来说没必要的复杂度不需要引入。等到吞吐量真的上去了,再把 exec_mode 改成 cluster 也只是一行配置的事,但集群模式下要特别注意程序状态(session、内存缓存等)的共享问题。
2.3 SSH 免密登录与安全组放行
服务器和仓库之间的认证叫 deploy key,GitHub Actions 和服务器之间的认证则是另一对密钥。我们动手生成一套专门给 Actions 用的 SSH 密钥对:
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/deploy_key -N ""生成后得到两个文件:deploy_key是私钥,deploy_key.pub是公钥。把公钥追加到服务器的 authorized_keys 里:
mkdir -p ~/.ssh chmod 700 ~/.ssh cat deploy_key.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys私钥内容会在下一步放进 GitHub Secrets 里,交给 Actions 使用。这里有个安全细节:如果你用 root 用户跑服务,公钥放在/root/.ssh/authorized_keys;如果你创建了专门用户,那要放到对应用户的家目录,并且注意,不同用户的.ssh权限必须匹配,owner 不能错,不然 SSH 默认拒绝这种不够私密的 key 文件使用。
阿里云 ECS 的安全组是另一道门禁,很多人部署失败就是因为代码没问题、流程没问题,但外网访问端口被拦。需要确认两件事:一是入方向放行了 22 端口给 SSH 用(Actions 需要通过它连服务器),二是你业务端口(比如 Node 服务监听 3000,或者通过 Nginx 反代的 80/443)已经放行。安全组配置原则是“最小放行”,别搞什么 0.0.0.0/0 全端口放行,只开出业务需要的端口即可。
3. 仓库侧配置:写一个可用的 GitHub Actions 工作流
3.1 在仓库中配置 Secrets
服务器的准备工作做完了,接下来是仓库侧的重头戏。先到仓库的 Settings → Secrets and variables → Actions 页面,点击 New repository secret,逐个添加如下内容:
| Secrets 名称 | 对应的值 |
|---|---|
| SERVER_HOST | 阿里云 ECS 的公网 IP 地址 |
| SERVER_USER | 部署用户的用户名(如 root 或 deploy) |
| SERVER_SSH_KEY | 刚才生成的私钥内容,也就是 deploy_key 文件里的全文 |
| SERVER_PATH | 服务器上的项目目录,如 /var/www/myapp |
Secrets 的作用是把敏感信息和 workflow 文件分离。你写 workflow 时可以直接引用${{ secrets.SERVER_HOST }},但仓库代码里永远看不到真实的 IP 和密钥。这一点必须形成习惯:任何不会写进 commits 的敏感信息都不要硬编码到 yml 里,否则仓库一公开,服务器就等于裸奔。
另外有个小细节,粘贴私钥的时候,结尾的换行符要保留。因为 SSH 私钥就是靠换行来分段识别的,如果复制的时候丢了最后的换行,Actions 里的 ssh-action 会报错,表现是“key too short”或者 parse 失败,很难排查。所以拷私钥时建议用cat命令完整输出,再完整粘贴,别用鼠标框选。
3.2 workflow 文件核心写法逐行拆解
在仓库根目录创建.github/workflows/deploy.yml,这是整套自动化的大脑。下面是我项目里用的一个完整版本,你可以直接把占位符替换成自己的值:
name: deploy on: push: branches: [ main ] workflow_dispatch: jobs: deploy: runs-on: ubuntu-latest steps: - name: 拉取代码 uses: actions/checkout@v4 - name: 安装 Node 20 uses: actions/setup-node@v4 with: node-version: '20' - name: 安装依赖并构建(如果项目需要) run: | npm ci npm run build:prod - name: 通过 SSH 部署到阿里云 ECS uses: appleboy/ssh-action@v1.0.3 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USER }} key: ${{ secrets.SERVER_SSH_KEY }} port: 22 script: | cd ${{ secrets.SERVER_PATH }} git pull origin main npm ci --omit=dev pm2 reload ecosystem.config.js逐行说一下关键点。
on.push.branches: [ main ]表示 main 分支收到 push 事件时触发部署。如果你的默认分支叫 master,一定改成 master,否则会出现“明明 push 了但 workflow 不跑”的灵异事件。
actions/checkout@v4是把代码拉到 GitHub 的临时虚拟机里。为什么要拉?因为后面的构建步骤需要代码;如果你的项目不需要构建,这一步也可以省。但我的经验是保留它,原因后面在触发策略里讲。
actions/setup-node@v4配合npm ci这一步,是为了在虚拟机上装依赖做构建(如果有构建步骤)。注意这里用的命令是npm ci而不是npm install。两者的差别是:npm ci 会严格按照 package-lock.json 安装,速度快而且不会悄悄改依赖版本;npm install 则可能因为 semver 范围和缓存原因装出和你本地不一样的依赖树。所有依赖一致性有要求的流程,都应该用 npm ci。
最后的核心步骤appleboy/ssh-action,它做的事就是:用你配置的 host、username、key 登录服务器,然后执行script里写的那几行命令。我在服务器上执行的操作是:进目录 → 拉代码 → 装生产依赖 → 用 PM2 的 reload 而不是 restart 平滑重载进程。reload 在 fork 模式下会等待旧的进程 Gracefully 退出再拉起新的,能做到短暂的请求不中断;如果 process 没有监听 graceful 信号,效果和 restart 差不多,但作为流程习惯,reload 更稳妥。
如果你想离底层更近一点,也可以不用第三方 action,自己写 ssh 命令,但需要额外配置 ssh-agent 和 known_hosts,脚本长一倍,好处是少依赖一个第三方。个人意见是:appleboy 这个 action 社区维护量大、用法稳定,直接用就好;但要注意,第三方 action 的版本要固定 tag(比如 v1.0.3),不要用 @main 这种浮动版本,防止上游更新后行为变化。
3.3 触发策略与构建时机的取舍
部署的触发方式,我只用了三种组合。
第一种是 push 到 main 分支直接部署。这是主线流程,发版动作就是 merge 到 main,简单直接。
第二种是workflow_dispatch,也就是在 GitHub Actions 页面手动点“Run workflow”。这个用途是,某次部署失败想重跑、或者临时想部署一次旧版代码,不用改代码就能触发。我认为这个能力必须有,纯 push 触发的问题在于:部署失败后你改了 yml 再 push,会触发一次新部署,但那次部署用的是新代码,如果业务问题和部署问题纠缠在一起,排障难度就上去了。有手动触发按钮,可以反复试一次部署脚本而不用动业务代码。
第三种是打 tag 触发,比如on.push.tags: [ 'v*' ],适合做版本发布,但个人项目目前用不上,就不赘述了。
构建时机的取舍值得单独说。Node 后端项目有两种做法:一种是在 GitHub 虚拟机上构建好产物再传到服务器;另一种是服务器上直接 npm ci 然后启动。我选的是后者,因为我的项目不需要编译,直接在服务器装依赖即可,省去产物传输环节。但如果你的项目是前端项目(比如 Vue/React),我会建议走前者:在 Actions 里跑npm run build,产物是 dist 目录,然后用 rsync 同步到服务器上 Nginx 的站点目录。前端构建工具链(webpack/vite)在服务器上跑很占资源,一台 1C1G 的 ECS 光构建就能卡死,这个教训一定要记住。
4. 第一次完整部署与问题排查实录
4.1 从零跑通一次自动化部署
配置完整套之后,第一次部署别急着 push,先在服务器上手动验证一遍,确定拉代码、装依赖、重启这三件事都能独立完成。我在服务器上手动跑通的命令:
cd /var/www/myapp git pull origin main npm ci --omit=dev pm2 reload ecosystem.config.js pm2 list确认pm2 list里进程状态是 online,然后 curl 一下接口地址,看返回是否正常。这一步是隔离变量:先证明部署脚本本身没问题,再去看 Actions 能不能把脚本远程执行成功。
确认没问题后,随便改一行代码(比如加个console.log),push 到 main,然后打开仓库的 Actions 标签页,应该能看到一个叫 deploy 的 workflow 在跑。点进去看每一步的日志,重点关注最后的 SSH 执行段,里面有 connect 信息和命令输出。第一次跑的时候,我建议你盯完整场,等到状态变成绿色对号,再去服务器上pm2 list确认进程重启时间已经更新,再 curl 一下接口,整个链路就算通了。
如果 Actions 执行到最后一步失败了,那日志里基本会留下线索。下面把高频出现的几类故障整理成速查表,按图索骥比较好定位。
4.2 高频故障问题速查表
| 故障现象 | 可能原因 | 解决方式 |
|---|---|---|
| workflow 没触发 | 分支名写错,trigger 的分支和实际 push 分支不一致 | 检查 deploy.yml 里 on.push.branches 是否和默认分支一致 |
| SSH 连接报 Permission denied (publickey) | 私钥内容没复制完整、公钥没加到 authorized_keys、username 配错 | 重新检查 Secrets 里私钥是否完整,确认公钥在哪个用户下就配哪个用户 |
| 报 Host key verification failed | 服务器不在已知主机列表里 | 在 ssh-action 里增加 fingerprint 参数;或者本地先 ssh 连接一次让 known_hosts 记住 |
| npm ci 报错 Could not resolve dependency | package-lock.json 和 package.json 不同步 | 本地执行 npm install 更新 lock 文件后提交 |
| pm2 reload 后服务立刻 crash | 服务器上的 .env 缺失、Node 版本不对、依赖没装全 | 看 pm2 logs,先手动跑 node app.js 检查,确认无误后再部署 |
| 代码更新了但服务没生效 | PM2 吃的还是旧进程;或者 PM2 指向的 script 路径不对 | pm2 reload 后再 pm2 list;检查 ecosystem.config.js 里的 script 路径 |
| 远程连接超时,15秒后失败 | 安全组没放行 22 端口、服务器防火墙拦截、IP 写错 | 去阿里云控制台检查安全组入方向规则;本地 telnet 服务器IP 22 测试连通性 |
| git pull 时报权限错误 | 服务器没有该仓库的拉取权限,或 deploy key 配置无效 | 用 deploy key 或 Personal Access Token 配置 Git 凭据,先手动 pull 通过一次 |
有一个我自己踩过比较久的坑就是 fingerprint。使用 appleboy/ssh-action 时,如果没设置 known_hosts,它默认不会校验服务器指纹,虽然功能上能连上,但忽略指纹校验其实是安全风险。建议按它的文档加上fingerprint参数,把服务器的 SSH 指纹填进去。获取指纹的命令是:
ssh-keyscan 服务器IP输出的第一列就是指纹信息。配好之后,还能避免由于 known_hosts 不一致导致的随机连接失败,一举两得。
4.3 环境变量与配置文件这类隐藏坑
代码层面的问题大多好排查,真正容易坑人的是那些“不在 git 里”的文件。
Node 项目基本都有环境配置文件,比如.env。它里面放了数据库密码、API Key、端口这些敏感内容,标准做法是把它写进.gitignore,不提交到仓库。但这样一来,服务器上的.env就只能在服务器上手动维护,不会跟着部署流程更新。问题就来了:部署脚本里有git pull,幸而 .env 不入库所以不会被覆盖,但如果哪天你图省事把 .env 加进了仓库,或者部署脚本里执行了. /var/www/myapp/.env之类的操作,稍不注意就可能把生产配置洗掉。
我的建议是:服务器上的.env人工维护,且部署前备份一份。最粗暴但有效的方式:
cp /var/www/myapp/.env /var/www/myapp/.env.bak放在部署脚本的第一行,真出事还能秒还原。别嫌土,这招救过我两次。
另一个隐藏坑是 Node 版本漂移。你本地用的 Node 20,服务器上跑的是 Node 18,然后某个依赖升级后用了新 API,本地构建通过、线上运行崩溃。解决办法是在项目根目录放一个.nvmrc文件:
20然后在部署脚本里,加一行nvm use:
cd ${{ secrets.SERVER_PATH }} source ~/.bashrc nvm use git pull origin main npm ci --omit=dev pm2 reload ecosystem.config.js有了 .nvmrc,nvm 会自动切到对应版本,保证服务器和本地环境一致。这也是我认为“服务器端环境准备”那节里 nvm 比直接安装 Node 更香的核心原因——版本可以跟项目走。
5. 安全加固、回滚与低频场景
5.1 权限最小化与密钥管理
自动化链路打通之后,安全意识要跟上。我最想强调的一件事是:不要让 Actions 直接拿 root 登录。虽然教程里用 root 最省事,但一旦 Secrets 泄露,攻击者拿到的就是服务器的最高权限,等于整个机器被接管。
正确的做法是创建一个权限受限的部署用户:
useradd -m -s /bin/bash deploy mkdir -p /var/www/myapp chown -R deploy:deploy /var/www/myapp然后把这个用户的公钥放进它的 authorized_keys。用 deploy 用户跑部署脚本,它最多只能动项目目录,破坏半径小得多。如果项目需要监听 80/443 这种特权端口,用 deploy 用户加 Nginx 反向代理解决,而不是图省事直接上 root。
密钥管理上,我给自己定了几条规矩:
- GitHub Secrets 里存的私钥永远是独立生成的,绝对不用本地开发机的私钥。
- 私钥不进仓库,不进 Docker 镜像,不贴到聊天工具。
- 定期轮换:每隔几个月删掉旧密钥对重新生成,更新 Secrets。轮换一次的成本只有两分钟,但能让泄露风险控制在有限窗口内。
- GitHub 仓库的 Actions 权限,在 Settings → Actions → General 里,把 Workflow permissions 设为 “Read repository contents”,不要给写权限,这样即便 workflow 被恶意修改,也没法通过 Actions 往仓库里提交代码。
5.2 版本回滚方案
自动化部署爽是爽,但回滚也要想好。最常用的回滚方式是:回到上一个可用版本,重跑部署。
Git 层面可以这样操作:
git log --oneline -5找到上一个可用的 commit hash,然后在服务器上:
cd /var/www/myapp git checkout <上一个commit> npm ci --omit=dev pm2 reload ecosystem.config.js但我不推荐直接 checkout 到游离状态,这样下一次git pull会报错或冲突。更干净的做法是git revert,生成一个新提交把代码回退,然后 push 到 main,自动触发部署。这样历史记录是连续的,不会出现 HEAD detached 的问题。
还有一种是 PM2 层面的“快速回滚”:如果只是配置或者代码导致进程持续崩溃,你可以临时切回旧版本,同时保留当前版本代码继续排查。两条腿走路比一条腿方便。
我的建议是,把回滚命令直接写成一个 shell 脚本放到服务器上,比如/var/www/rollback.sh,参数传 commit hash 或 tag,执行之后自动拉指定版本并重启。这样出了问题时,人只需要执行一行脚本,不用现场拼命令,省去紧张操作时的出错率。
5.3 低成本监控与异常通知
部署自动化之后,服务跑不跑、新版本有没有生效,不能靠人肉盯。低成本的做法是先让 PM2 的日志和状态可视化:
pm2 list pm2 monitpm2 monit 可以实时看 CPU 和内存占用,适合 ssh 上去快速确认。但人不会 24 小时挂着,所以异常通知是必要的。
最即时的通知是让 Actions 在部署失败时给你发一条消息。可以在 workflow 后面加一个失败时的通知步骤,方式很灵活:飞书或钉钉的机器人 webhook、邮件通知都可以实现。一个简单的做法是在 yml 尾部加:
- name: 部署失败通知 if: failure() uses: appleboy/telegram-action@master with: to: ${{ secrets.TELEGRAM_TO }} token: ${{ secrets.TELEGRAM_TOKEN }} message: 部署失败,请检查 Actions 日志Telegram 在国内网络环境下的可达性是个问题,所以我实际上用的是飞书机器人,直接在 Actions 里调用 curl 发送 webhook 请求。思路一样,选一个你自己能用、愿意看的通知渠道就行。这一步的价值是:部署失败不再靠用户报障才知道,而是第一时间主动收到通知。
服务运行期的监控,如果暂时不想上重型监控系统,就在服务器上用一个 crontab 任务定期检查进程,失败了自动拉起并通知:
*/5 * * * * pm2 describe myapp > /dev/null 2>&1 || (cd /var/www/myapp && pm2 start ecosystem.config.js && curl -s 通知webhook)这种方案零成本但有效,能覆盖 90% 的“进程挂了没人知道”场景。真到了业务规模需要精细化的阶段,再上商业化监控或开源监控组件也不迟。
我在整个落地过程中最深的体会是:自动化部署不是一次性工程,而是把你反复给服务器“打下手”的动作固化下来。它最值钱的地方不是省了那几分钟,而是让“部署”变成了一件可回放、可追溯、按一下就执行完的事。代码在哪儿、依赖怎么装、服务怎么起、出问题怎么查,全都有据可循,这正是线上服务可控性的开始。
最后分享一个经验:不要把所有命令全塞进 yml 的 script 字段里。yml 里只保留一个bash /var/www/deploy.sh这样的调用,把具体部署逻辑写到服务器上的脚本里。这样做的好处是,本地调试、手动重跑、Actions 执行走的是同一套逻辑,排查问题只在脚本里改,不需要每次都跑一遍完整 workflow,迭代速度会快很多。这套思路适合所有想让部署省心一点的朋友,从一台服务器、一个 Node 项目开始,慢慢就能长出适合你自己的发布流程。