mehrancodes/laravel-harbor
Laravel Harbor helps you deploy Laravel applications with Docker, offering Artisan commands and configuration for building, provisioning, and releasing your app to a server in a repeatable container-based workflow.
What this package is. Laravel Harbor is a developer-tooling package (CLI + laravel-harbor config) that provisions ephemeral preview environments per pull request on Laravel Forge, then tears them down. It is not a runtime dependency of the application; it lives in the CI workflow and holds Forge and Git API credentials. That framing matters more than any individual feature, because the blast radius of adopting or upgrading it is CI and infrastructure-adjacent, not production-adjacent.
Assessment of v2.1.0. This is a capability release with one genuine bug fix and two compatibility decisions, not a refactor:
public_deploy_key / private_deploy_key validation errors from Forge), meaning any team that needed private repo cloning could not use Harbor v2 at all. The fix is the correct shape: generate a keypair before site creation, register the public half with the Git host, hand both halves to Forge, and delete the key on teardown. Upgrade alone resolves it.FORGE_GIT_API_PROVIDER. This correctly separates "how Forge clones" from "which Git API Harbor calls for deploy keys and PR/MR comments." Defaulting the API provider to the clone provider, plus URL-based inference for github.com / gitlab.com, keeps the common case zero-config. Self-hosted GitLab is now covered, which is the main capability unlock for enterprise buyers.FORGE_DEPLOY_KEY supersedes FORGE_GITHUB_DEPLOY_KEY with an alias and a CI log warning. Managed, low-friction deprecation, but it is debt with a scheduled breaking change in the next major.FORGE_DEPLOY_KEY_PUBLIC / FORGE_DEPLOY_KEY_PRIVATE for environments Harbor cannot reach, and warning-level messages that print the public key when auto-registration is impossible. Good incident ergonomics.github, gitlab, gitlab-custom, bitbucket, custom, with custom requiring FORGE_GIT_REPOSITORY_URL. Failing before the Forge call is the right fail-fast boundary.Technical risks.
repo scope. The default GITHUB_TOKEN in Actions cannot do this. A TPM should treat this as a security review item, not a CI config detail — the token Harbor holds is materially more powerful after v2.1.0 than before it, and it must be custodied accordingly (dedicated app, per-repo installation, rotation owner).FORGE_DEPLOY_KEY_PRIVATE) introduces a secret we now own.concurrency groups. Without them, rapid pushes or PR reopen produce a hard failure (site must be installed before it can be deployed), plus orphaned or duplicate Forge sites.bitbucket is accepted by validation but has no API provider. A typo-adjacent trap: config validates, deploy-key registration and comments silently degrade.Key questions to resolve before adoption.
FORGE_DEPLOY_KEY entirely and avoid the scope escalation?FORGE_GITHUB_DEPLOY_KEY (the alias works, but the warning will appear on every run)?Stack fit. Harbor assumes a Laravel app deployed via Forge, with CI on GitHub Actions or GitLab CI. v2.1.0 hard-requires PHP 8.2+, so runner images must move accordingly (the docs' kirschbaumdevelopment/laravel-test-runner:8.1 example must become :8.3). If our app is not on Forge, or preview environments are already handled by a platform-native mechanism, this package is not a candidate regardless of release quality. There is no production runtime coupling, no database migration, and no application code change — integration surface is entirely env vars plus workflow YAML.
Compatibility posture. Backward compatible by design: default GitHub setups and the pre-v2 "GitHub custom" combination (FORGE_GIT_PROVIDER=custom + FORGE_GITHUB_DEPLOY_KEY=true) keep working. The only forced change is the PHP 8.2+ floor. The FORGE_GITHUB_DEPLOY_KEY alias means we can adopt v2.1.0 without touching secrets, then rename on a deliberate timeline rather than under pressure.
Migration path (sequenced).
| Step | Action | Gate |
|---|---|---|
| 1 | Pin laravel-harbor to ^2.1, bump CI runner to PHP 8.3 |
CI green on one repo |
| 2 | Decide credential model; create dedicated GitHub App or scoped PAT; store as GIT_TOKEN |
Security sign-off on Administration: Read & write |
| 3 | Rename FORGE_GITHUB_DEPLOY_KEY → FORGE_DEPLOY_KEY in workflow YAML; clear the deprecation warning |
Zero warnings in logs |
| 4 | Set FORGE_GIT_API_PROVIDER (+ GIT_API_URL for self-hosted) only where not inferable |
Deploy key auto-registered, comment posted |
| 5 | Add concurrency: group: harbor-${{ github.event.pull_request.number }}, cancel-in-progress: false to provision and teardown |
Two rapid pushes produce one site |
| 6 | Prove the failure paths deliberately: revoke token scope, confirm 403 message; disable API provider, confirm public-key fallback | Runbook written |
| 7 | Roll out repo by repo; keep a pinned fallback version for rollback | All repos migrated |
Sequencing note. Do step 5 before step 7. Concurrency is a one-line workflow change with outsized stability benefit, and shipping it late means the first wave of users experiences duplicate-site failures and blames the upgrade.
Maintenance. Low ongoing effort, but non-zero. Two items carry forward: (a) the deprecated FORGE_GITHUB_DEPLOY_KEY will be removed in a future major, so schedule the rename now rather than at forced-upgrade time; (b) PHP minor-version currency is now a hard dependency, so the runner image version becomes part of the package's maintenance envelope. Pin to a minor range and review on each release; Harbor's changelog is small and readable, which keeps upgrade cost near zero.
Support model. Almost every failure this release can produce is credential configuration, and the messages are unusually good — they name the missing permission and, for the token case, name the exact GitHub App permission and PAT scope. That is a genuine support-cost reduction and should be documented verbatim in the internal runbook rather than paraphrased. The single highest-value runbook entry: "403 while registering deploy key" → token lacks deploy-key management; the default GITHUB_TOKEN can never work.
Scaling characteristics. Three cost axes: (1) Forge servers — one per open PR, so cost scales with PR concurrency, and label-gating or a draft-PR filter may be warranted; (2) Git API rate limits — one key registration per provision and one comment per PR, negligible at normal volume but visible during bulk backfills; (3) key sprawl — every teardown must delete its key, so teardown reliability is a security-hygiene concern, not just a cost one. A weekly audit of Forge sites and repository deploy keys against open PRs is a cheap control.
Failure modes.
| Mode | Behavior | Operator action |
|---|---|---|
403 Forbidden on key registration |
Provisioning stops | Grant Administration: Read & write on the GitHub App (and accept on installation) or use a repo-scoped PAT; replace GITHUB_TOKEN |
| Deploy key not auto-registered | Warning, provisioning continues | Add printed public key manually, or set FORGE_GIT_API_PROVIDER + GIT_API_URL + GIT_TOKEN |
| PR/MR comment skipped | Warning, provisioning continues | Same config fix; also needs GIT_COMMENT_ENABLED=true and GIT_ISSUE_NUMBER |
git_provider validation error |
Hard stop before Forge call | Fix typo; custom requires FORGE_GIT_REPOSITORY_URL |
| Concurrent runs on one PR | Hard stop: site must be installed before it can be deployed |
Per-PR concurrency group; cancel-in-progress: false |
| Teardown fails / skipped | Orphaned Forge site and orphaned deploy key | Scheduled reconciliation; manual delete of both |
The two soft-degrade modes are the ones that will bite in practice: pipelines stay green while previews lack keys or authors lack feedback, and nobody notices until someone asks why their PR has no environment URL.
Ramp-up. An experienced Laravel/CI engineer can complete the upgrade and concurrency fix in well under a day for a single repository. The long pole is organizational, not technical: obtaining a GitHub App permission upgrade (which requires an installation owner to accept new permissions) or provisioning a suitably scoped PAT. Budget a security review alongside the engineering work, and treat the credential model decision as the actual adoption gate.
Verdict. Adopt v2.1.0. It fixes a hard blocker, adds self-hosted GitLab support at no configuration cost to existing users, and its failure messages are better than most tools in this category. The residual risks — elevated token scope, private-key custody, and soft-degrade silence — are manageable with a runbook and a dedicated credential, but they should be accepted deliberately rather than inherited by default.
How can I help you explore Laravel packages today?