Last updated on 6th May 2026

Migrating from Laravel Forge to DeployHQ

Read the title and then ignore it slightly: this is not a wholesale "stop using Forge" guide. Forge is an excellent server provisioning and management tool — it sets up nginx, PHP-FPM, MySQL, Redis, Let's Encrypt, queue workers, cron jobs, and firewalls in a couple of clicks, and there is nothing in DeployHQ that replaces any of that.

What DeployHQ replaces is Forge's deploy script — the bash text-box that runs git pull && composer install && php artisan migrate on every push. That feature is genuinely lightweight, and once you have hit its limits (no audit log, no atomic releases, no rollback, no per-environment permissions, no build pipeline that runs off-target, deploy logs you have to scroll a UI to read), DeployHQ is the natural place to move it.

The rest of Forge stays exactly where it is.

For a side-by-side feature comparison, see DeployHQ vs Laravel Forge. If you are also using Envoyer alongside Forge, the Envoyer migration guide is the matching pair — Envoyer goes away, Forge stays.

What stays, what moves

Today, in Forge After migration
Server provisioning (DigitalOcean, AWS, Linode, Vultr, Hetzner) Stays in Forge
nginx, PHP-FPM, MySQL, Redis configuration Stays in Forge
Let's Encrypt SSL management Stays in Forge
Queue workers (Horizon, Supervisor) Stays in Forge
Cron jobs / scheduled tasks Stays in Forge
Firewall rules Stays in Forge
Database creation and backups Stays in Forge
Forge environment variables (.env written to the server) Stays in Forge or moves to DeployHQ — your call (see FAQ)
Deployment script (bash in the Forge UI) Moves to DeployHQ — split across build pipeline and SSH commands
Quick Deploy (auto-deploy on push) Moves to DeployHQ — replaced by automatic deployments on the same Git webhook
In-place file updates (no atomic releases by default) Replaced by atomic symlinked releases — gained, not lost

What you gain

  • Atomic releases. Forge's default deploy is in-place (git pull against the live site directory). DeployHQ uploads each release to its own directory and swaps a symlink in one operation. Failed deploys cannot leave you with half-updated files.
  • Audit log. Who deployed what, when, from which branch, with which commit hash — separately from CI runs and Forge SSH sessions.
  • One-click rollback. The previous release stays on disk; rollback flips the symlink back. No Forge equivalent.
  • Build pipeline off the target. composer install --no-dev, npm ci && npm run build, and asset compilation run once on our build infrastructure. The Forge deploy script ran them on the production server, every deploy.
  • Per-environment deploy permissions. Grant deploy-to-staging without granting deploy-to-production.
  • Deploy from anywhere. Browser, CLI, API, or a CI step.

Migration in three steps

1. Add DeployHQ as a deploy target on the existing Forge server

Connect a DeployHQ project to the same Git repository, and add the Forge-managed server as a DeployHQ server. Use the same forge SSH user Forge uses, and the same site directory (e.g. /home/forge/example.com) as the deploy path. Add DeployHQ's SSH key to the server through Forge's SSH Key UI — same flow as adding a key for any other deploy tool.

2. Move composer / npm into the build pipeline

Whatever your Forge deploy script was running on the target — composer install, npm ci, npm run build, asset publishing — moves into a DeployHQ build pipeline:

composer install --no-dev --optimize-autoloader --no-interaction
npm ci
npm run build

These run once on our build server. The compiled vendor/ and built assets ship as part of the release. Your Forge server stops doing this work on every deploy.

3. Wire migrations and post-deploy hooks as SSH commands

The remaining lines from your Forge deploy script — php artisan migrate --force, php artisan config:cache, php artisan queue:restart, php artisan horizon:terminate — become DeployHQ SSH commands tied to the right stage:

  • php artisan migrate --forceafter upload, before symlink change
  • php artisan config:cache && php artisan route:cacheafter upload (or in the build pipeline if your config does not depend on values resolved on the target)
  • php artisan queue:restart, php artisan horizon:terminateafter deploy

4. Point Forge's nginx vhost at the symlinked path

This is the only Forge-side change. Update your site's web directory in Forge from /home/forge/example.com/public to /home/forge/example.com/current/public. Forge's nginx config picks up the change; DeployHQ's symlink swap now drives the live release.

5. Disable Forge Quick Deploy

In the site's settings, switch off Quick Deploy. From now on, the Git webhook is owned by DeployHQ — see automatic deployments. If you skip this step you will get double-deploys (Forge and DeployHQ both reacting to every push).

For most Forge sites, end-to-end migration is about an hour. The bulk of the time is updating the nginx web directory and running a couple of test deploys side-by-side before disabling Quick Deploy. Concierge migration is available on Pro and above if you want a hand.

What teams ask before they switch

Do I lose Forge's server management UI? No. You keep using Forge for everything except the deploy script. SSL renewal, queue worker config, cron jobs, server monitoring — all still happen in the Forge dashboard.

Where should environment variables live — Forge or DeployHQ? Either works. Most Forge users keep the .env file in Forge's UI because Forge already writes it to /home/forge/example.com/.env and rotation flows through Forge. DeployHQ then treats that path as a shared file (linked into each release directory automatically). If you prefer central env-var management with audit history per change, move them to DeployHQ's environment variables and let DeployHQ write the file.

Will queue workers and Horizon survive? Yes — and you do not touch them. Forge's daemon and queue worker configuration runs on the server independently of how code gets there. The php artisan queue:restart SSH command tells the running workers to reload after each deploy; Forge keeps managing the worker processes themselves.

What about scheduled tasks (php artisan schedule:run)? Stay in Forge. Forge writes the cron entry; the scheduled job runs against /home/forge/example.com/current (the symlinked release) once you complete step 4 above. No further change needed.

Can I keep using Forge's recipes and SSH key management? Yes. Forge recipes that run shell commands across servers, and Forge's SSH key UI for adding team members, both stay in place.

What if I have multiple sites on one Forge server? Migrate them one at a time. Each Forge site becomes its own DeployHQ project pointing at its own subdirectory under /home/forge/. Forge sees them as independent sites and DeployHQ deploys them independently.

I am also using Envoyer alongside Forge. Then DeployHQ replaces Envoyer (Forge stays). See Migrating from Envoyer to DeployHQ for the Envoyer side of the move.

My team is fully bought into the Laravel ecosystem (Forge + Envoyer + Vapor). Worth weighing carefully. DeployHQ is stack-agnostic, not Laravel-flavoured. The pitch is "broader stack support and a flat per-account price" rather than "Laravel-native." If Laravel-native tooling is a deliberate philosophical choice, staying with Forge + Envoyer is reasonable.

Start your migration

Connect a DeployHQ project to your repo and the same Forge-managed server, copy your Forge deploy script's lines into DeployHQ as build pipeline commands and SSH commands, point Forge's nginx web directory at the symlinked release path, and disable Quick Deploy. Most teams have the new path working in under an hour with a side-by-side test or two before cutting over.

Forge keeps doing the things it does well. DeployHQ just takes over the deploy step.

Laravel Forge and Envoyer are trademarks of Laravel LLC. DeployHQ is not affiliated with Laravel. We just understand where Forge's deploy script feature ends and where a deploy automation tool begins.