DeployManage runs the whole Laravel deployment: git checkout into a fresh release, composer install, migrations, config and route caching, health check, atomic symlink flip, queue restart. If the health check fails, the old release stays live and nothing breaks.
Deploy to Hetzner, DigitalOcean, AWS, Vultr, Linode, OVHcloud or your own VPS.
Laravel deployment is the process of getting a new commit of your app onto a production server safely: pull the code, install dependencies, run database migrations, rebuild the config, route, view and event caches, reset OPcache, restart queue workers, and swap traffic to the new version without dropping requests. Doing this by hand over SSH is where most production outages start, because the app is half-updated while users are still hitting it. DeployManage automates the whole sequence as a zero-downtime atomic deploy: each release is built in its own directory, health checked, and only then made live by flipping a symlink, so a bad build never reaches a user. If something slips through anyway, one click rolls back to the previous release instantly.
Last updated July 2026
Each deploy builds into a brand new release directory while the current one keeps serving traffic. The symlink flips only at the very end, so users never see a half-installed app.
The new release is checked before it goes live. If it fails, the flip never happens and the previous release keeps running. A broken build stays out of production.
Previous releases stay on disk. Rolling back points the symlink at the last good release and reloads, which takes seconds instead of a panicked redeploy at 2am.
Migrations, config cache, route cache, view cache and OPcache reset run as part of the deploy, in the right order, every time. No one forgets a step.
Workers are restarted on every deploy so they pick up the new code, and the Laravel scheduler is wired up for you rather than living in a crontab someone has to remember.
Every deploy, rollback and server change is recorded across your whole fleet, with who did it and when. Useful when a Tuesday afternoon incident needs a timeline.
Provision a hardened server on Hetzner, DigitalOcean, AWS, Vultr, Linode or OVHcloud, or bring a VPS you already own.
Point a site at a repository and branch. Your .env stays on the server, outside the release directories, and gets symlinked into each new release.
Push to your deploy branch. DeployManage builds the release, migrates, caches, health checks, then flips traffic over with no downtime.
One click returns to the previous release. Then fix the bug on your own schedule instead of under pressure.
There is no single correct answer here. Plenty of good teams deploy Laravel with a shell script they wrote themselves and never think about it again. The honest tradeoff is between how much deployment tooling you want to own and maintain, and how much you want handled for you.
| Approach | Setup effort | Zero downtime | Rollback | Best for |
|---|---|---|---|---|
| Manual git pull + SSH | None | No, the app is broken mid-pull | Manual, git revert and hope | A side project, a staging box, a demo |
| Bash/deploy script | A day or two to write, then ongoing upkeep | Only if you build releases and symlinks yourself | Whatever you coded | Teams who want to own their tooling and pay nothing |
| Deployer (open source PHP tool) | Moderate, a deploy.php recipe per project | Yes, atomic releases are built in | Yes, dep rollback | PHP teams happy to maintain a recipe and their own servers |
| GitHub Actions pipeline | Moderate to high, plus secrets and runner config | Yes, if you script the release strategy | Re-run an older workflow, or script it | Teams already living in CI who want deploys in the same place |
| A managed panel like DeployManage | Minutes, connect a provider and a repo | Yes, atomic releases with a health check before the flip | Yes, one click to the previous release | Teams who want provisioning, deploys, queues and rollback handled |
Deploy during business hours without a maintenance window. Atomic releases mean the current version keeps serving until the new one is proven healthy.
The same deploy pipeline on every client project, on whichever cloud that client insists on, with one activity log across the whole fleet.
If deploys live in a script only the lead knows, you have a bus factor problem. A panel makes deploying (and rolling back) something the whole team can do safely.
Workers restart on every release so they run the new code, and scheduled jobs keep firing without anyone editing a crontab by hand.
If your deploy is still git pull and a prayer, moving to atomic releases removes the window where users hit a half-updated app.
Run on cheap hardware like Hetzner or Vultr and still get a managed zero-downtime pipeline, backups, SSL and a firewall.
People say "deploy" like it is one action. It is really about a dozen, and they have to happen in a specific order, on a live machine, while real users are making requests. Skip one and you get a 500 page, or worse, a queue worker quietly running last week's code against this week's database schema.
Here is the real sequence. The new commit gets checked out into a fresh directory, usually something like releases/20260714093000, so the version currently serving traffic is untouched. Then composer install --no-dev --optimize-autoloader runs, along with whatever front end build you have (npm ci && npm run build). Your .env file is never in the repo, so it lives in a shared directory on the server and gets symlinked into the release. The same goes for storage/: user uploads, logs and framework caches must survive across deploys, so storage is a symlink to a shared path rather than a fresh empty folder every time. Get this wrong and every deploy silently deletes your uploads.
Next, database migrations with php artisan migrate --force. The --force flag is required because Laravel refuses to run migrations in production interactively. Then the caches get rebuilt: config:cache, route:cache, view:cache and event:cache. These turn your config and routes into a single compiled PHP file, which is a real performance win and also a real footgun, because a stale route cache will happily serve routes that no longer exist. This is exactly why the caches must be rebuilt in the new release, not carried over from the old one.
Then OPcache. PHP caches compiled bytecode keyed by file path, which is one of the underrated reasons atomic releases work so well: because each release lives at a new path, PHP sees new files rather than stale cached ones. If your setup uses a stable path or aggressive opcache.validate_timestamps=0 settings, the deploy needs to reset OPcache explicitly or you will serve old code from a new release and lose an hour to confusion.
Only now does the release get health checked. A request goes to the new code to confirm the app actually boots, the database is reachable and the caches compiled. If that check fails, the deploy stops and the old release keeps serving. If it passes, the current symlink flips to the new release directory and PHP-FPM reloads. The flip is a single atomic filesystem operation, which is the whole point: there is no moment where half the app is old and half is new.
Finally, php artisan queue:restart. Queue workers are long-running PHP processes that hold your application code in memory forever. If you do not restart them, they keep executing the previous release's code indefinitely. This is one of the most common Laravel deployment bugs, and it is nasty because the web app looks perfectly fine while the jobs behave strangely.
DeployManage runs all of that for you on every push, in that order, on every server in your fleet. The interesting part is not that it saves typing. It is that the sequence is identical every single time, including at 6pm on a Friday when someone is deploying a hotfix in a hurry.
Most Laravel production incidents are not exotic. They come from the same short list of mistakes, and they are all avoidable.
git pull. Building into a new directory and flipping a symlink at the end is the single highest-value change you can make. It removes the window where a user hits your app mid-update..env and storage/ out of the release. Both belong in a shared directory that gets symlinked in. Secrets never go in the repo, and uploads must survive a deploy.--force, and make them backward compatible. Deploy code that works with both the old and new schema, then clean up in a later release. Dropping a column in the same deploy that stops using it is how you take down the version still serving traffic.migrate:fresh, no migrate:refresh, ever, on a production server. Take a database backup before a migration that alters real data.config:cache, route:cache, view:cache, event:cache. A stale route cache is a genuinely maddening bug to chase.php artisan queue:restart tells workers to finish the current job and exit, and your process manager brings them back on the new code.composer install, not composer update. Production installs exactly what composer.lock says. Updating on the server means you deploy dependency versions nobody tested.php artisan down with a secret bypass URL is the honest call.Before a Laravel app takes its first real production traffic, walk this list. It takes twenty minutes and saves the kind of afternoon nobody enjoys.
APP_ENV=production and APP_DEBUG=false. Debug mode on in production leaks your environment variables to anyone who triggers an exception. This is the most damaging misconfiguration in PHP, and it is a one-line fix.APP_KEY is set and backed up. Lose it and every encrypted value in your database becomes unreadable.schedule:run every minute) and you have confirmed a scheduled job actually fired.DeployManage covers a good chunk of this list by default: hardened servers with a firewall, SSL, managed MySQL and PostgreSQL, queue workers, the Laravel scheduler, and reusable provisioning recipes so server number nine comes up exactly like server number one. The rest is your app, and that part is your job.
Check out the new commit into a fresh release directory, run composer install --no-dev, symlink your shared .env and storage folders in, run php artisan migrate --force, rebuild the config, route and view caches, health check the release, then flip a symlink to make it live and restart queue workers.
The best way is an automated, atomic deploy: every release is built in its own directory and only becomes live after it passes a health check. You can get this from Deployer, a well-written CI pipeline, or a managed panel like DeployManage. Manual git pull over SSH is the one approach to retire.
Build each release into a new directory while the current release keeps serving traffic, then point a current symlink at the new release and reload PHP-FPM. The symlink swap is atomic, so no request ever sees a half-updated app. DeployManage does this on every deploy, with a health check before the flip.
Yes, run php artisan migrate --force as part of every deploy, but write migrations that are backward compatible with the currently running code. Add columns before you use them and drop them a release later. Back up the database before any migration that alters or removes existing production data.
composer install --no-dev --optimize-autoloader, your front end build, php artisan migrate --force, then config:cache, route:cache, view:cache and event:cache, an OPcache reset, and php artisan queue:restart so workers pick up the new code. DeployManage runs this sequence in order on every push, so nothing gets skipped.
Point the current symlink back at the previous release directory and reload PHP-FPM. That reverts code in seconds. Database migrations are the hard part, which is why backward compatible migrations matter. In DeployManage, previous releases stay on disk and rollback is one click.
Connect a server, link your repo, and run a zero-downtime deploy with one-click rollback. Free plan to start.