Your Laravel app runs perfectly on your laptop. Getting that same code live on a VPS, correctly, is a surprisingly long list of steps, and every one of them is a chance for a 500.

Deploying Laravel by hand is not one command. It is a sequence: pull the code, install the right dependencies, write an .env, generate an app key, run migrations, link storage, rebuild the caches, configure Nginx, point PHP-FPM at the right socket, fix file permissions, and start the queue worker and scheduler. Miss one and the site is either down or quietly broken.

The problem

The first deploy is the one you plan carefully. It is the fifth deploy, the one you do in a hurry, that bites you. A forgotten php artisan config:cache, a migration you ran against the wrong database, permissions that reset after a git pull, a queue worker that silently died three days ago. None of these throw an error at deploy time. They surface later, as a blank page, a stuck job, or a customer emailing to say the checkout is broken.

Get a Laravel deploy slightly wrong and you end up with:

  • A white screen and a 500, because the config cache still points at the old .env
  • The stream or file "storage/logs/laravel.log" could not be opened from a permissions slip
  • Jobs piling up in the queue because the worker was never restarted after the deploy
  • A half-applied migration, because the deploy failed midway and left the app between two states

Every manual deploy is a little different from the last one. Six months in, nobody is quite sure what the "correct" deploy sequence for this box actually is.

The traditional way: an honest walkthrough

A correct manual deploy onto an already-provisioned Ubuntu server (Nginx, PHP-FPM, a database and Composer already in place) is not one command. It is a dozen or so discrete steps that all have to be right, in the right order, every single time. Here is the shape of it, abbreviated.

# Pull the code, then install PHP dependencies for production
cd /var/www/app && git pull
composer install --no-dev --optimize-autoloader --no-interaction

# then: write the production .env, php artisan key:generate --force,
#       migrate --force (against the RIGHT database), storage:link,
#       config:cache + route:cache + view:cache,
#       and chown/chmod so PHP-FPM can actually write to storage/
#       and bootstrap/cache (get the permissions wrong and every
#       request 500s on a log file it cannot open)...

# ...plus an Nginx server block rooted at public/ with the right
#    try_files and a fastcgi_pass to the correct PHP-FPM socket
#    (this is where most people get it subtly wrong: a stale socket
#    path, a missing try_files, and you're staring at a blank page)...

# ...then the background processes Laravel needs to actually run:
#    a Supervisor program for the queue worker (queue:work with the
#    right --tries/--max-time, restarted on every deploy), and a
#    cron line for the scheduler so schedule:run fires every minute.

Count the steps: that is roughly a dozen commands, plus two config files that live outside artisan entirely, and the whole thing has to land in the correct order. Any one of them is easy to get subtly wrong, a stale socket path, a forgotten cache rebuild, a worker you never restarted, and none of those fail loudly at deploy time. They surface later as a blank page or a stuck queue. The first careful deploy might take you the better part of an afternoon. The real cost is that you do the whole sequence again on every deploy, in a hurry, from memory.

The StackPilot App workflow

StackPilot App is a local-first VPS Operations Platform. It runs on your machine, connects to your server over SSH, and turns that entire deploy sequence into a repeatable recipe you trigger with one click. Your credentials never leave your computer, and there is no cloud control plane sitting between you and your server.

Here is the whole flow end to end before we break it down step by step:

Deploy Laravel to your own VPS: zero to production

Step 1: Connect your repository

Point StackPilot App at your Git repository. GitHub, GitLab or any repo you can reach works, and the connection uses a deploy key so the server can pull without exposing your personal credentials.

Step 2: Pick a branch and site

Choose the branch to deploy (usually main or production) and the site it belongs to. StackPilot App knows this is a Laravel app, so it already understands the shape of the deploy that follows.

Step 3: Review the deploy recipe

Instead of a script you maintain in your head, you get a deploy recipe: install Composer dependencies, run migrations, rebuild the config, route and view caches, link storage, and restart the queue worker. Every step from the manual walkthrough is here as an editable recipe, and you can add your own commands (an npm run build, a cache warm) where you need them.

Step 4: Click deploy and watch the logs

Trigger the deploy and StackPilot App runs the recipe over SSH, streaming every line live. You see composer install resolve, the migration output, the caches rebuild, the worker restart. It is not a black box. It is the same commands you would run yourself, in the correct order, every time.

Step 5: Ship an atomic release

When the recipe finishes cleanly, StackPilot App promotes the new code into place as a release. The build happens off to the side, and the live site only switches to it once the deploy has succeeded. If a step fails, the currently-live release is left untouched, so a broken build never takes the site down.

Automatic deploys on push

Once the site is connected, you can have StackPilot App redeploy on every push. It listens for an HMAC-signed webhook from your Git host, verifies the signature, and runs the same recipe. Push to main, and the deploy you just watched happens on its own.

Why this works

It is worth understanding what StackPilot App actually does, because it is the same work you would do by hand, made repeatable:

  • The recipe is standardised. Composer, migrations, caches, storage link, worker restart, in the same order every deploy. There is no "which step did I forget this time?" because the recipe does not forget.
  • Releases are atomic. StackPilot App builds the new release in its own directory and only switches the live symlink to it after the deploy succeeds. A failed build leaves the running site exactly as it was.
  • A health check runs before the switch. The new release is checked before it takes traffic, so you are not promoting a build that cannot boot.
  • Deploys are visible. Every step streams live and every deploy is recorded, so "what changed on this server, and when?" has an answer that is not git log on the box and guesswork.
  • Webhooks are signed. Push-to-deploy uses HMAC-signed webhooks, so only a legitimately signed push from your Git host can trigger a release.

Atomic, symlinked releases are also what make a zero-downtime deploy possible. That is a topic in its own right, and we go deep on it in the follow-up guide below.

FAQ

Do I still need to provision the server first? Yes. StackPilot App deploys onto a server that already has Nginx, PHP, a database and the rest of the stack. If you are starting from a bare VPS, provision it first (see the related guide), then connect your repository and deploy.

Which Git providers work? GitHub, GitLab and any Git host you can reach over SSH or HTTPS. The server pulls using a deploy key, so your personal Git credentials are never placed on the box.

Can I run my own commands during a deploy? Yes. The deploy recipe is editable. Add steps like npm run build, a cache warm, or a custom artisan command, and they run as part of the same streamed deploy.

Does it handle the queue worker and scheduler? Yes. StackPilot App manages the Supervisor program for your queue worker and restarts it as part of the deploy, and it sets up the scheduler cron so schedule:run fires every minute.

Will a failed deploy take my site down? No. Releases are atomic. The live site only switches to the new release once the deploy succeeds, so a build that fails midway leaves the running site untouched.

What operating systems does StackPilot App run on? macOS is generally available, and Windows is in public beta.

Summary

Deploying Laravel by hand is a long, order-sensitive checklist you run in a hurry and easily fumble: pull, composer install, .env, key, migrate, storage link, rebuild caches, Nginx, PHP-FPM, permissions, queue worker, scheduler. The commands are worth understanding once. They are not worth retyping, in the right order, on every deploy. With StackPilot App you connect your repository, review the deploy recipe, and ship an atomic release with one click, watching every step stream live, with your credentials never leaving your machine.

Ready to try it on your own server? Download StackPilot App or explore the features first. When you are ready to go further, read the follow-up on how to deploy Laravel without downtime.

Coming from a hosted panel? See how StackPilot App compares as a Laravel Forge alternative.