
手动部署 Laravel 应用这事儿,一旦项目频繁更新,就会变成纯粹的体力活。每次都要 SSH 连上服务器跑一堆命令,久了真的烦。最近折腾了一下 GitHub Actions 的 CI/CD(持续集成/持续部署),把整个流程自动化了,这篇把踩过的坑和具体做法说清楚。
本文要实现的目标很简单:当你往 main 分支推送代码时,GitHub Actions 自动完成整个部署流程。
整个流程走下来是这样的:
推送到 main 分支 → GitHub Actions 启动 → SSH 连接到服务器 → 拉取最新代码 → 安装依赖 → 构建前端资源 → 执行数据库迁移 → Laravel 优化 → 重启队列处理器
开始之前,确保你已经有这些:
一个托管在 GitHub 上的 Laravel 应用
一台运行 Ubuntu 的 DigitalOcean 云服务器
Laravel 应用已经在服务器上跑起来了
Nginx 或 Apache 已经配置好
服务器上安装了 PHP、Composer、Node.js 和 npm
.env 文件里数据库连接已经配好
会用 GitHub 仓库的基本设置
为了安全起见,建议创建一个专门的部署用户,而不是直接用 root。不过这篇文章为了简化演示,我会用 root 来说明。如果是生产环境的项目,还是建议单独建一个有限权限的用户来做部署。
先在 package.json 里写一个 deploy 命令。这样 GitHub Actions 只需要在服务器上执行一条命令就行,不用把一堆部署命令都写在工作流文件里。
打开 package.json,加上这个脚本:
"scripts": { "deploy": "git pull && COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader && npm ci && npm run build && php artisan migrate --force && php artisan config:cache && php artisan route:cache && php artisan view:cache && php artisan optimize"
}
如果你的项目没有 package-lock.json 文件,把 npm ci 换成 npm install:
"scripts": { "deploy": "git pull && COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader && npm install && npm run build && php artisan migrate --force && php artisan config:cache && php artisan route:cache && php artisan view:cache && php artisan optimize"
}
| 命令 | 作用 |
|---|---|
git pull |
从 GitHub 拉取最新代码 |
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader |
安装生产环境 PHP 依赖,并优化 Composer 自动加载 |
npm ci |
根据 lock 文件安装 JavaScript 依赖,保证构建一致性 |
npm run build |
构建生产环境的前端资源 |
php artisan migrate --force |
强制执行生产环境的数据库迁移 |
php artisan config:cache |
缓存 Laravel 配置 |
php artisan route:cache |
缓存路由 |
php artisan view:cache |
编译并缓存 Blade 模板 |
php artisan optimize |
运行 Laravel 优化命令 |
注意:执行
php artisan migrate --force会修改数据库结构。部署之前一定要确保迁移脚本在测试环境跑过,而且要有数据库备份策略。
接下来在 Laravel 项目里创建 GitHub Actions 工作流文件。
新建这个文件:
.github/workflows/deploy.yml
然后填入以下内容:
name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Deploy via SSH uses: appleboy/ssh-action@v1.2.0 with: host: ${{ secrets.DROPLET_HOST }} username: ${{ secrets.DROPLET_USER }} key: ${{ secrets.DROPLET_SSH_KEY }} script: | cd /var/www/your-app npm run deploy php artisan queue:restart
把 /var/www/your-app 换成你服务器上 Laravel 项目的实际路径:
/var/www/your-app
举个例子:
script: | cd /var/www/my-laravel-app npm run deploy php artisan queue:restart
php artisan queue:restart 这个命令会让 Laravel 队列处理器优雅重启,这样它们才能加载新部署的代码。如果你用 Supervisor 管理队列处理器,Supervisor 会自动重新启动它们。
GitHub Actions 需要 SSH 连接到你的服务器。不要用你自己的个人 SSH 密钥,而是单独建一个专门用于部署的密钥。
在本地机器上运行:
ssh-keygen -t ed25519 -C "your-app-github-deploy" -f ~/.ssh/your-app-github -N ""
这会生成两个文件:
| 文件 | 用途 |
|---|---|
~/.ssh/your-app-github |
私钥,需要添加到 GitHub Secrets |
~/.ssh/your-app-github.pub |
公钥,需要添加到服务器上 |
私钥不要泄露出去,只能存放在 GitHub Secrets 里。
现在把公钥添加到服务器上,这样 GitHub Actions 才能通过 SSH 连接上来。
ssh-copy-id -i ~/.ssh/your-app-github.pub root@YOUR_DROPLET_IP
type $env:USERPROFILE\.ssh\your-app-github.pub | ssh root@YOUR_DROPLET_IP "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"
把 YOUR_DROPLET_IP 换成你实际的服务器 IP 地址。
可以测试一下 SSH 连接是否正常:
ssh -i ~/.ssh/your-app-github root@YOUR_DROPLET_IP
如果连接成功,说明配置没问题。私钥添加到仓库 Secrets 之后,GitHub Actions 也能正常连接。
GitHub Actions 现在可以连接服务器了,但服务器也需要有权限从 GitHub 仓库拉代码。
先 SSH 登录到服务器:
ssh root@YOUR_DROPLET_IP
然后在服务器上生成一个新的 SSH 密钥:
ssh-keygen -t ed25519 -C "your-app-droplet-deploy" -f ~/.ssh/your-app-github -N ""
查看公钥内容:
cat ~/.ssh/your-app-github.pub
复制输出的内容,然后进入你的 GitHub 仓库:
Settings → Deploy keys → Add deploy key
填写部署密钥信息:
| 字段 | 值 |
|---|---|
| Title | DigitalOcean Droplet |
| Key | 粘贴服务器上的公钥 |
| Allow write access | 不要勾选 |
只读权限就够了,因为服务器只需要从 GitHub 拉取代码,不需要写入。
添加完部署密钥后,配置服务器的 SSH,让它连接 GitHub 时使用这个密钥:
cat >> ~/.ssh/config <<'EOF'
Host github.com IdentityFile ~/.ssh/your-app-github StrictHostKeyChecking no
EOF chmod 600 ~/.ssh/config
测试服务器能否连接到 GitHub:
ssh -T git@github.com
如果配置正确,GitHub 会识别这个连接。
现在把 SSH 连接信息添加到 GitHub Secrets。
进入你的 GitHub 仓库:
Settings → Secrets and variables → Actions → New repository secret
添加以下 Secrets:
| Secret 名称 | 值 |
|---|---|
DROPLET_HOST |
服务器 IP 地址 |
DROPLET_USER |
SSH 用户名,比如 root |
DROPLET_SSH_KEY |
本地私钥的完整内容 |
获取本地私钥内容的方法:
cat ~/.ssh/your-app-github
cat $env:USERPROFILE\.ssh\your-app-github
要复制全部内容,包括这几行:
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
把完整私钥粘贴到 DROPLET_SSH_KEY 这个 Secret 里。
部署脚本和工作流文件都配置好后,把改动提交推送到 main 分支:
git add .github/workflows/deploy.yml package.json
git commit -m "Add auto-deploy pipeline"
git push origin main
然后去 GitHub 仓库点开 Actions 标签页。
如果一切配置正确,应该能看到部署工作流正在运行。绿色勾勾表示部署成功。
完整流程是这样的:
你推送代码到 main 分支 ↓
GitHub Actions 启动部署工作流 ↓
GitHub Actions 通过 SSH 连接到服务器 ↓
工作流进入 Laravel 项目目录 ↓
服务器执行 npm run deploy ↓
Laravel 从 GitHub 拉取最新代码 ↓
Composer 安装生产环境依赖 ↓
Node 构建前端资源 ↓
Laravel 执行迁移和优化命令 ↓
队列处理器优雅重启 ↓
部署完成
这样一来,每次想部署更新就不用手动 SSH 连服务器了。
git@github.com: Permission denied (publickey)这通常说明服务器没有权限从 GitHub 仓库拉代码。
排查方法:
确认服务器上生成了 SSH 密钥
确认服务器公钥已添加到 GitHub Deploy Keys
确认仓库 remote 用的是 SSH 而不是 HTTPS
检查 remote URL:
git remote -v
应该显示这样的格式:
git@github.com:username/repository.git
如果是 HTTPS 格式,改成 SSH:
git remote set-url origin git@github.com:username/repository.git
composer: Continue as root/super user [yes]?如果用 root 部署,Composer 会弹出警告。部署脚本里已经加了这个环境变量来跳过提示:
COMPOSER_ALLOW_SUPERUSER=1
长期来看,还是建议创建一个专门的部署用户,而不是用 root。
npm ci 报错npm ci 需要有 package-lock.json 文件。如果项目里没有,先在本地生成:
npm install
然后把生成的 package-lock.json 提交到仓库。
.ssh/authorized_keys: No such file or directory先手动创建目录和文件:
mkdir -p ~/.ssh
touch ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
然后再添加公钥。
部署后重启 Laravel 队列处理器:
php artisan queue:restart
如果用的是 Supervisor,检查 worker 状态:
sudo supervisorctl status
需要的话手动重启:
sudo supervisorctl restart all
查看 GitHub Actions 里的工作流日志。常见原因包括:
工作流文件里项目路径写错了
服务器上没有 Composer
服务器上没有 Node.js 或 npm
文件权限不对
数据库迁移失败
前端构建失败
.env 文件里环境变量缺失
也可以直接在服务器上手动跑一下部署命令:
cd /var/www/your-app
npm run deploy
如果手动跑都失败了,先把服务器上的问题解决掉,再让 GitHub Actions 重试。
如果是简单的个人项目,用 root 部署问题不大。但如果是生产环境,建议加强安全:
创建专门的部署用户,不要用 root
部署用户只给它应用目录的访问权限
私钥只存放在 GitHub Secrets 里
不要把私钥提交到仓库
GitHub Deploy Keys 用只读权限
确保 .env 文件不会被提交
执行生产环境迁移前先备份数据库
现在你有一套基于 GitHub Actions 和 DigitalOcean 云服务器的 Laravel 自动部署流水线了。
每次往 main 分支推送代码,GitHub Actions 就会自动连接服务器、拉取最新代码、安装依赖、构建前端资源、执行迁移、优化 Laravel、重启队列处理器。
这套方案对于不用 Kubernetes 或复杂部署平台的项目来说,是个务实的起点。随着应用规模增长,可以再加入部署通知、数据库备份、测试跑 CI、零停机部署、回滚机制这些功能。