公开摘要
这篇文章将一份 Laravel 部署笔记改写为公开版:所有真实服务器地址、仓库、目录、用户名和业务路径都已抽象,只保留适合复用的部署原则。它关注的不是某一台机器的命令清单,而是小规格服务器上自动部署最容易断裂的几条链路:资源、SSH、依赖、容器路径和生产收尾。
部署拓扑:把登录链、拉代码链和容器链拆开
Laradock + GitHub Actions 的典型拓扑是:代码托管在 GitHub,生产服务器上有项目目录和 Laradock 目录,Actions 在 push 后通过 SSH 进入服务器,再由服务器拉取目标分支,最后在 Laradock 的 workspace 或 php-fpm 容器里执行 Composer 与 Laravel 命令。
生产服务器 → SSH 拉取 Git 仓库 → Laradock workspace 执行 composer install
Laradock php-fpm / workspace → 重建缓存、修复 storage 权限、完成上线检查
这条链路里有三个边界必须明确:Actions 不直接持有代码仓库写权限之外的业务秘密;服务器不依赖人工进入容器执行发布;Composer 安装必须读取锁文件,避免线上临时解析依赖树。
| 组件 | 职责 | 需要准备 | 失败信号 |
|---|---|---|---|
| GitHub Actions | 触发部署、登录服务器、输出日志 | Secrets、workflow、分支触发规则 | SSH 握手失败或 secret 缺失 |
| 生产服务器 | 保存项目目录、执行 Git 拉取 | 部署用户、SSH key、Swap、目录权限 | publickey、not a git repository |
| Laradock | 提供 PHP CLI 与运行环境 | workspace、php-fpm、volume 映射 | 容器路径不存在或 TTY 报错 |
| Laravel 项目 | 依赖安装、缓存、日志、上传目录 | composer.lock、storage 可写 | 依赖漂移或运行时 Permission denied |
前置检查:小服务器先补资源安全网
中小规格服务器运行 Composer 时最常见的问题不是 YAML 写错,而是内存被耗尽。Swap 不是性能优化,它只是让内核在高峰时有一个缓冲区,避免 Composer 解析依赖或生成 autoloader 时直接被 OOM killer 结束。机器越小,越应该减少线上计算量:能复用锁文件就不重新求解,能跳过无变化依赖就不重复安装。
- 确认服务器内存、磁盘、Swap 均可用,Swap 大小按业务负载设置为兜底值。
- 确认部署目录、Laradock 目录和容器内挂载路径一一对应。
- 确认生产分支策略清晰,例如只允许主干或发布分支触发自动部署。
- 确认
composer.json与composer.lock同步提交,线上不临时 update。 - 确认 Laravel 的
storage、缓存目录、日志目录最终归运行用户可写。
预检阶段还要明确“谁会改服务器上的文件”。如果部署脚本使用强制同步远端分支,那么服务器上的手工修改会被覆盖;这恰恰是自动化部署需要的确定性,但必须提前约定好,避免把生产热修当成常态。
SSH 信任链:Actions 登录服务器,服务器拉取代码
一条完整的部署链路通常包含两个方向。第一条是 GitHub Actions Runner 能登录生产服务器:私钥保存在 Actions Secrets,服务器的 authorized_keys 接受对应公钥。第二条是生产服务器能拉取 GitHub 仓库:服务器公钥登记为仓库 Deploy Key,服务器使用对应私钥执行 git fetch。
链路 B:服务器上的部署公钥 → GitHub Deploy Keys → 允许服务器拉取仓库
| 位置 | 保存内容 | 用途 | 安全建议 |
|---|---|---|---|
| Actions Secrets | REMOTE_HOST、REMOTE_USER、SSH_PRIVATE_KEY | 让 Runner 登录服务器 | 只用泛化变量名,不在 workflow 写真实值 |
服务器 authorized_keys | 部署公钥 | 允许对应私钥登录 | 权限收紧到 700/600 级别 |
| GitHub Deploy Keys | 服务器公钥 | 允许服务器拉取仓库 | 默认只读;确需写入再评估写权限 |
| workflow | 引用 secret 的占位符 | 组织部署步骤 | 不打印私钥、不输出真实主机和仓库名 |
部署密钥应专用,不复用个人常用登录密钥。若服务器只负责拉取代码,Deploy Key 从只读权限开始即可;只有脚本确实需要从服务器向仓库写入内容时,才评估写权限。公开文章中也不要出现真实主机、真实仓库、真实用户或完整私钥变量值。
Workflow 设计原则:确定性、幂等、可回滚
workflow 的核心目标是把“手工 SSH 发布”拆成可重复步骤:拉取远端状态、判断依赖是否变化、同步代码、在容器里执行必要命令、做生产收尾。小服务器上尤其要避免每次 push 都全量计算依赖,因此可以检测 composer.json 或 composer.lock 是否变化,再决定是否进入 Composer 步骤。
- 触发条件只绑定明确分支,避免任意实验分支误发布。
- 登录服务器后进入抽象项目根目录
<app-root>,不存在 Git 元数据时初始化或提前人工 clone。 - 先
git fetch获取远端状态,再比较依赖文件变化,最后同步到目标提交。 - 依赖变化时进入 Laradock 目录,在
workspace容器中执行 Composer。 - 根据变更类型选择是否重建 Laravel 配置、路由、视图缓存。
- 收尾检查权限、日志、健康页面和关键接口。
LARADOCK_DIR="<laradock-root>"
PROJECTS=("<service-a>" "<service-b>")
git fetch --all
COMPOSER_CHANGED=$(git diff --name-only HEAD origin/<branch> | grep -E 'composer\.(json|lock)' || true)
git reset --hard origin/<branch>
docker-compose exec -T -e COMPOSER_MEMORY_LIMIT=-1 workspace composer install \
--working-dir=/var/www/<app>/<service> \
--no-dev --optimize-autoloader --no-interaction
docker-compose exec -T 的 -T 很重要,因为 GitHub Actions 这类非交互环境没有可用 TTY。Composer 命令应加 --no-interaction,避免流程等待输入。多子项目场景下,用数组或配置列出需要安装依赖的目录,但对外文档只保留抽象路径。
Composer install,而不是线上 update
生产部署应使用 composer install,读取已经提交并测试过的 composer.lock。composer update 会根据版本范围重新计算依赖树,既耗内存又可能引入未测试版本;这类动作应该在本地或 CI 的测试阶段完成,而不是在小规格生产机上临时执行。
| 命令 | 做什么 | 适合位置 | 风险 |
|---|---|---|---|
composer install | 按 lock 文件安装确定版本 | 生产部署、CI 构建 | lock 缺失或未同步会失败 |
composer update | 重新解析并更新依赖版本 | 本地开发、受控升级分支 | 依赖漂移、耗时、耗内存 |
--no-dev | 不安装开发依赖 | 生产环境 | 代码若误依赖 dev 包会暴露问题 |
--optimize-autoloader | 优化类加载映射 | 生产环境 | 需要在代码同步后执行 |
推荐闭环是:本地或独立构建环境执行依赖升级,测试通过后提交 composer.json 和 composer.lock;服务器只做安装与优化。这样即使部署失败,也能根据 Git 提交和 lock 文件定位具体差异。
生产收尾与常见失败表
Laravel 发布的最后一步不是“代码拉完”,而是让运行时状态可用。配置、路由、视图缓存是否需要重建,要根据本次变更判断;storage、缓存和日志目录必须确保 PHP-FPM 运行用户可写。权限修复应收敛到项目目录,不要为了省事对过大的父目录递归授权。
docker-compose exec -T php-fpm php /var/www/<app>/<service>/artisan route:cache
docker-compose exec -T workspace chown -R <runtime-user>:<runtime-group> /var/www/<app>/<service>/storage
| 错误特征 | 可能原因 | 处理方式 |
|---|---|---|
ssh: handshake failed | Actions 私钥与服务器公钥不匹配,或 .ssh 权限错误 | 核对 secret、authorized_keys 与目录权限 |
Permission denied (publickey) | 服务器拉仓库时没有 Deploy Key 权限 | 把服务器公钥加入对应仓库 Deploy Keys |
fatal: not a git repository | 项目目录没有 Git 元数据或路径错了 | 确认 BASE_DIR,必要时重新 clone 或初始化 remote |
Process Killed | Composer 被 OOM killer 结束 | 检查 Swap、减少 update、使用 install 与锁文件 |
The directory does not exist | 宿主机路径与容器内路径不一致 | 核对 Laradock volumes 和 --working-dir |
Laravel Permission denied | 日志、缓存或上传目录不可写 | 修复 storage 和缓存目录归属 |
- 服务器 Swap、磁盘、部署目录和 Laradock 容器均已预检。
- Actions Secrets 只保存抽象变量,workflow 不硬编码敏感值。
- Deploy Key 默认只读,密钥专用于部署,不复用个人密钥。
- 生产使用
composer install与composer.lock,不在线上执行composer update。 - 所有容器命令带
-T和非交互参数,路径使用容器内路径。 - 发布后完成 Laravel 缓存、权限、日志和健康检查。