deploymanage ~ %

Laravel deployment tools to deploy Laravel to production with zero downtime

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.

zsh · deploymanage
deploymanage ~ % server create --provider hetzner --region ash1
provisioning ...... web-01.prod
online in 41s ..... hardened + firewalled
deploymanage ~ % deploy web-01.prod
ssh · pull · build · migrate · reload
live in 11.3s ..... zero downtime
deploymanage ~ %
# the short answer

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

# every step of a Laravel deploy, automated

$ deploymanage deploy
Atomic zero-downtime releases

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.

$ deploymanage health check
Health check before the flip

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.

$ deploymanage rollback
Instant rollback

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.

$ deploymanage artisan migrate --force
Migrations and caches in order

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.

$ deploymanage queue restart
Queue workers and scheduler

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.

$ deploymanage activity
Fleet-wide deploy log

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.

# how it works

01
Connect your server

Provision a hardened server on Hetzner, DigitalOcean, AWS, Vultr, Linode or OVHcloud, or bring a VPS you already own.

02
Link the Git repo

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.

03
Ship a release

Push to your deploy branch. DeployManage builds the release, migrates, caches, health checks, then flips traffic over with no downtime.

04
Roll back if you need to

One click returns to the previous release. Then fix the bug on your own schedule instead of under pressure.

Ways to deploy Laravel compared

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

# who it's for

Startups shipping several times a day

Deploy during business hours without a maintenance window. Atomic releases mean the current version keeps serving until the new one is proven healthy.

Agencies with many client apps

The same deploy pipeline on every client project, on whichever cloud that client insists on, with one activity log across the whole fleet.

Teams where only one person can deploy

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.

Apps with heavy queue workloads

Workers restart on every release so they run the new code, and scheduled jobs keep firing without anyone editing a crontab by hand.

Migrating off manual SSH deploys

If your deploy is still git pull and a prayer, moving to atomic releases removes the window where users hit a half-updated app.

Cost-sensitive production apps

Run on cheap hardware like Hetzner or Vultr and still get a managed zero-downtime pipeline, backups, SSL and a firewall.

What a Laravel deployment actually has to do

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.

Laravel deployment best practices

Most Laravel production incidents are not exotic. They come from the same short list of mistakes, and they are all avoidable.

  • Use atomic releases, not 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.
  • Keep .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.
  • Always run migrations with --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.
  • Never destroy data in a deploy. No migrate:fresh, no migrate:refresh, ever, on a production server. Take a database backup before a migration that alters real data.
  • Rebuild the caches, do not reuse them. config:cache, route:cache, view:cache, event:cache. A stale route cache is a genuinely maddening bug to chase.
  • Restart queue workers on every deploy. php artisan queue:restart tells workers to finish the current job and exit, and your process manager brings them back on the new code.
  • Health check before you flip, not after. Verifying the release only once it is live means your users are the health check. Verify first.
  • Make rollback a button, not a project. If rolling back requires thinking, you will not do it during an incident. You will try to fix forward while the site is down.
  • Use composer install, not composer update. Production installs exactly what composer.lock says. Updating on the server means you deploy dependency versions nobody tested.
  • Consider maintenance mode for risky migrations. For most deploys you do not need it. For a migration that rewrites a large table, php artisan down with a secret bypass URL is the honest call.
  • Log every deploy. When something breaks at 3pm, the first useful question is always "what shipped, and when?" An audit trail answers it in seconds.

Laravel deployment checklist before you go live

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.
  • HTTPS is enforced and the certificate auto-renews.
  • A firewall is on, with only the ports you actually need open, and SSH is key-only.
  • Database credentials are unique to the app, not a reused root user.
  • Automated database backups exist, and you have restored one at least once. An untested backup is a rumor.
  • Queue workers run under a process manager that restarts them if they die, and they get restarted on deploy.
  • The Laravel scheduler is running (a single cron entry hitting schedule:run every minute) and you have confirmed a scheduled job actually fired.
  • Error tracking and log aggregation are wired up. Production errors should reach you before a customer does.
  • You have deployed to a staging environment with the same pipeline first. A deploy process you have never rehearsed is not a deploy process.
  • You have practiced a rollback. Once. On purpose. Before you need it.

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.

Frequently asked questions

How do I deploy a Laravel app to production?

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.

What is the best way to deploy Laravel?

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.

How do I deploy Laravel with zero downtime?

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.

Should I run migrations during deployment?

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.

What commands should run on every Laravel deploy?

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.

How do I roll back a bad Laravel deploy?

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.

# related

deploymanage ~ % signup

Ship Laravel without the maintenance window.

Connect a server, link your repo, and run a zero-downtime deploy with one-click rollback. Free plan to start.