beyondcode/forge-cli
A command-line interface for Laravel Forge. Manage servers, sites, databases, and deployments right from your terminal using your API token, with simple commands for provisioning resources, triggering deployments, and monitoring your infrastructure. Handy for scripting routine tasks.
This is a Laravel Zero CLI that wraps the Forge API, so "getting started" means proving three things in about an hour: the binary runs on your PHP, your Forge token resolves, and the API contract hasn't drifted since the last release (1.2.0, April 2021).
php -v # 1.2.0 added PHP 8 support — confirm your actual minor (8.2/8.3, not just 8.0)
composer global require beyondcode/forge-cli:1.2.0 # pin the exact version; this is not a moving target
export PATH="$PATH:$HOME/.composer/vendor/bin" # or ~/.config/composer/vendor/bin
forge --help
Where to look first, in order:
forge --help, then forge <command> --help. There is no published manual. The help text is the documentation and the contract. Command names below are illustrative — confirm each against your installed version before scripting it.chmod/umask accordingly.src/ on GitHub — the command classes and the API client wrapper. For each command you'll rely on, note which endpoint it hits. That's your drift-detection checklist.release/1.2.0.md — four lines, but two of them change what you can safely do: PHP 8 support and SSH key management. The third (a deployment-script hook fix) is a warning about the write path.First use case — read-only listing. Before anything destructive:
forge servers # list servers
forge sites # list sites (or scope to a server)
This proves the token is valid, the client talks to Forge without drift, and gives you the server/site IDs every write command needs. Only after this succeeds should you try forge deploy <server> <site> — and only against a non-production site the first time.
The second use case is where the day-to-day value actually lands: pulling a site's environment variables down to a local file instead of round-tripping through the browser.
umask 077
forge env:pull <server> <site> > .env.forge # verify the exact subcommand via `forge --help`
Time-box the first session to "does CLI-driven Forge reduce my toil?" — not "can I script my whole infrastructure today."
Pin-and-wrap. Treat the binary as an unstable primitive. Route routine operations through thin shell scripts or Makefile targets you own, so when an upstream command changes you patch a few files, not your team's muscle memory.
deploy-staging:
forge deploy $(STAGING_SERVER) $(STAGING_SITE)
env-sync-staging:
umask 077 && forge env:pull $(STAGING_SERVER) $(STAGING_SITE) > .env.forge
sites-on-staging:
forge sites $(STAGING_SERVER)
Read-only surface first, writes second. The safe automation catalog is reads: server lists for audit scripts, site inventories for onboarding docs, deployment history for a changelog feed. Each write verb — deploy, env var change, SSL, SSH key — gets its own explicit decision and a named owner. "We installed the CLI" is not implicit permission to mutate production.
Deploy-trigger with a guard. The canonical loop is "merge to main → trigger Forge deploy → surface the result." Because this package isn't designed for non-interactive CI, keep it off anything that pages a human, and wrap it defensively if you script it at all:
#!/usr/bin/env bash
set -euo pipefail
forge deploy "$SERVER_ID" "$SITE_ID" || { echo "deploy trigger failed" >&2; exit 1; }
Env-var sync as the highest-value weekly read. Keeping local .env aligned with Forge's system of record — no browser tab — is the pattern most engineers will use every week. Treat the output as secrets: restrictive umask, redirect to a file, never echo into a shared log or a CI artifact, and add the destination to .gitignore.
Bulk operations over lists. Iterate: list servers → map to sites → act per site. This is where the CLI genuinely beats the dashboard. But you're looping over destructive verbs, so echo first and gate the real run behind an explicit flag:
# dry run by default; APPLY=1 to actually execute
for site in $(forge sites "$SERVER_ID" --format=ids); do
if [ "${APPLY:-0}" = "1" ]; then
forge deploy "$SERVER_ID" "$site"
else
echo "would deploy $site on $SERVER_ID"
fi
done
SSH key lifecycle as a separate, reviewed workflow. 1.2.0 adds SSH key management. Pattern it as its own gated process: rotate on a schedule, keep key material out of shell history (set +o history around the calls, or feed via stdin), and never run these from CI with a broadly-scoped token. This is the highest-privilege surface in the package.
Treat the CLI as a scaffold, not an architecture. The portable part is the patterns — guarded deploys, dry-run loops, restricted env files, named owners. If you later move to Laravel's official Forge CLI or direct API calls, you keep the Makefile targets and swap the implementation underneath.
No maintainer cadence. One release (2021-04-12), zero Composer dependents, no roadmap, no deprecation policy. Every issue is yours alone. Pin the version and keep a fallback: the Forge API is directly callable, so a broken command costs an hour to reimplement as a curl call — not a crisis.
The 1.2.0 hook fix is a warning, not just a fix. "Fixes an issue with the deployment script if a hook already exists" means earlier versions could mangle an existing deployment script — a production-affecting defect on the write path. Before trusting the deploy path in any workflow that writes a script, test the hook-exists scenario explicitly against a sandbox site. There's no test-coverage signal to reassure you.
PHP 8 support is a changelog line, not a guarantee. Verify the install on your actual runtime (8.2/8.3), not just 8.0. Record the resolved Symfony Console version — that's where subtle breakage hides.
API drift is the silent failure mode. The wrapper was written against Forge's API as of 2021. Commands can fail loudly (fine — you notice) or silently target the wrong resource (not fine). Diff read-command output against the dashboard before you automate any writes on top of them, and again after any Forge-side change you hear about.
Exit codes and non-interactive mode are not pipeline-grade. Don't depend on this tool's exit status or prompts, and don't let it page anyone. If CI-driven deploys are the goal, call the Forge API or deploy webhooks directly with your own retries, paging, and idempotency. The hook bug is a textbook example of the idempotency class you must own.
SSH key commands are the sharp edge. They may touch key material in addition to the token, and there is no security documentation — no stated token scope, no log-redaction guarantee, no storage guidance. Mitigations: scope the token to the minimum, never run these in CI or with a shared/global token, keep shell history clean, and treat them as a separate gated decision rather than part of a deploy pilot.
Extension points are narrow — plan to fork. MIT-licensed Laravel Zero means the realistic extension strategy is fork-and-own: patch the command class, pin your fork, absorb the periodic compatibility check. Price that maintenance before adopting, not after.
Don't let it become a shadow standard. One engineer with it globally installed is personal convenience. A team depending on it is organizational debt. Decide explicitly: pilot with a named owner and a time-box, or build the durable version on the official Forge CLI / the Forge API directly.
How can I help you explore Laravel packages today?