这篇专门讲清楚一个疑问:GitHub Actions 明明跑在 GitHub 平台上,怎么把项目部署到我们自己的服务器?密钥传上去安全吗? 用一篇实战(Spring Boot 部署到阿里云 ECS)把整个链路讲透。
一、核心机制:云端 runner → SSH → 你的服务器
GitHub Actions 的执行环境(runner)在 GitHub 的云主机上,但它可以通过网络连到任何机器——包括你自己的服务器。部署的本质就是:
git push → Actions 云上跑 workflow → 用 SSH 连你的服务器 → 执行部署命令你的服务器不需要装任何 GitHub 组件,只要:
- 开 SSH 端口(默认 22)
- 有一个能被 SSH 登录的用户(建议专用 deploy 用户,见下文密钥安全)
- runner 能连到(公网 IP 可达)
二、密钥安全吗?Secrets 机制
疑问:"SSH 私钥不是要上传到 GitHub 吗?那不是泄密吗?"
答案:传,但用的是 GitHub Secrets(加密变量),不是明文:
- 密钥存在仓库 Settings → Secrets and variables → Actions(或组织级 Secrets)
- 加密存储:GitHub 加密保存,页面无法回读明文,有审计日志
- 运行时注入:workflow 里通过
${{ secrets.XXX }}引用,只在执行时注入环境,不会打印到日志 - 仓库设 private:别人看不到你的仓库更看不到 Secrets
最佳实践(关键):
| 做法 | 说明 |
|---|---|
| 部署专用密钥 | 不用服务器主账号(root)!单独建 deploy 用户 + 单独 SSH key,权限只限部署目录 |
| 仓库 private | 私有仓库,Secrets 不对外 |
| 最小权限 | deploy 用户只允许 scp 到指定路径、执行指定脚本 |
| 定期轮换 | 密钥泄露风险随使用时间增加,定期换 |
| 自建 runner(替代方案) | 把 Actions runner 装到你自己服务器上跑——代码不出 GitHub,不用传密钥(适合保密要求高的场景) |
三、实战:Spring Boot 自动部署到服务器
1. 服务器端准备(一次性的)
bash
# 1. 建部署专用用户(不直接用 root)
sudo useradd -m deploy
# 2. 生成部署专用密钥对
ssh-keygen -t ed25519 -f ~/.ssh/deploy_key -N ""
# 3. 公钥给 deploy 用户
sudo mkdir -p /home/deploy/.ssh
sudo cp ~/.ssh/deploy_key.pub /home/deploy/.ssh/authorized_keys
# 4. 建应用目录 + 部署脚本
sudo mkdir -p /opt/myapp
sudo chown -R deploy /opt/myapp2. GitHub 侧配置 Secrets
仓库 Settings → Secrets and variables → Actions → New repository secret:
| Secret 名 | 值 |
|---|---|
SERVER_HOST | 服务器公网 IP(如 47.96.158.10) |
SERVER_USER | deploy |
SERVER_SSH_KEY | 私钥内容(cat ~/.ssh/deploy_key 的输出,全选包括头尾注释) |
3. 写 workflow(.github/workflows/deploy.yml)
yaml
name: Deploy
on:
push:
branches: [ main ] # main 分支推送时触发
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# ① 拉代码
- uses: actions/checkout@v4
# ② 装 JDK
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
# ③ 打包(跳过测试省时间)
- run: mvn package -DskipTests
# ④ 配好 SSH 私钥(从 Secrets 注入)
- uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SERVER_SSH_KEY }}
# ⑤ 用 scp 把 jar 传到服务器
- name: Upload jar
run: |
scp -o StrictHostKeyChecking=no \
target/myapp.jar \
${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:/opt/myapp/
# ⑥ ssh 远程执行部署脚本(停旧起新)
- name: Restart service
run: |
ssh -o StrictHostKeyChecking=no \
${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} \
'cd /opt/myapp && kill -9 $(cat app.pid) 2>/dev/null; nohup java -jar myapp.jar > app.log 2>&1 & echo $! > app.pid'4. 触发
bash
git push origin main
# → Actions 自动跑 → 检查 Actions 页面日志 → 部署完成关键点:
scp:把构建产物(jar)传到服务器ssh '命令':远程执行部署命令(停旧进程、起新进程)StrictHostKeyChecking=no:首次连接不交互确认(测试环境用;生产建议把服务器指纹加进 known_hosts)- Secrets 全流程加密,日志里不会出现私钥内容
四、进阶:更规范的部署方式
上面是"裸部署"(scp + 手动起进程),更规范的做法:
| 方式 | 说明 |
|---|---|
| scp + 脚本(本文) | 简单直接,适合小项目 |
| Docker 部署 | jar 打成 Docker 镜像推到仓库(如阿里云 ACR),服务器 docker pull + compose up(见《Docker Compose 多容器编排》) |
| 云原生 | K8s + 镜像仓库,GitHub Actions 只负责"构建推镜像",部署交给 K8s(滚动更新) |
生产环境推荐 Docker 方式:GitHub Actions build 镜像 → push 到镜像仓库 → SSH 到服务器 docker compose pull && up -d——回滚就是 docker compose 切版本,简单安全。
五、常见问题
- SSH 连接超时:服务器安全组没开 22 端口 / 防火墙拦截——去云控制台检查
- Permission denied:deploy 用户没权限访问 /opt/myapp——
chown -R deploy - 私钥报错:Secrets 粘贴时丢了头尾注释(
-----BEGIN OPENSSH PRIVATE KEY-----)或换行被吃——重贴完整内容 - workflow 语法错:用 GitHub 仓库里的 Action 校验 / 本地 vscode 装 YAML 插件
- 不想传密钥:自建 runner——在服务器装 GitHub Actions runner(官方提供注册脚本),runner 在你服务器上跑,代码、密钥都不用上传 GitHub
小结
- GitHub Actions 部署 = 云端 runner 用 SSH 连你自己的服务器执行部署
- 密钥用 Secrets 加密存储 + deploy 专用密钥 + 仓库 private = 安全
- 实战链路:checkout → JDK → build → scp jar → ssh 重启
- 生产推荐 Docker 方式(build 镜像 → push → compose up)
- 不想传密钥 → 自建 runner 到你自己服务器
基础概念见《CI/CD 快速入门》,容器化部署见《Docker Compose 多容器编排》。
