Deploying Laravel on fortrabbit
🚢
Ship Laravel.
Deploy a Laravel application to fortrabbit through GitHub — environment configuration, build commands, post-deploy migrations, queues and zero-downtime swaps. If you have shipped Laravel elsewhere, most of this will look familiar; we point out the differences as we go.
Laravel deployment is a solved problem on most platforms — and yet every host has its own quirks, build timing and limits. This guide is the short version of "how do I deploy a Laravel app to fortrabbit and not regret it later". We cover the path from a fresh repository to a production app with assets and queues.
This guide was written with Laravel 13 and Laravel 12 in mind. Have a look at our updated Laravel guides too.
Before you begin
You need a Laravel application that runs locally, a GitHub account, a fortrabbit account, and the fortrabbit GitHub App installed against the repos you want to deploy. The GitHub App is what turns a git push to your branch into a deploy on fortrabbit.
If you have not connected GitHub yet, install the app, then create a fortrabbit app from the repo. Each branch you connect maps to its own environment — keep main for production and add a develop environment for staging if you need one.
The rest of this post assumes the GitHub App is connected and your repo's main branch is wired to a production environment.
How fortrabbit handles a Laravel deployment
A push to the connected branch on GitHub triggers a deployment on fortrabbit. The deployment service pulls the new commit, runs your build commands (Composer, npm, anything else you configure), runs post-deploy commands, and syncs the result into the environment's storage. The previous build keeps serving traffic until the new one is ready.
A few consequences fall out of that:
- Anything that needs to persist across deploys must live in
storage/(Laravel already does this for logs, sessions and cached views by default). - Build-time secrets do not need to be in your repo — they live in the app's environment variables.
- The
.envfile you use locally is not copied to fortrabbit. Use the dashboard's environment variable editor instead. - Code travels in one direction only: from GitHub to fortrabbit. Files you change via SSH are not pulled back into Git.
Configure your environment
Laravel reads configuration from environment variables via config/*.php. On fortrabbit, set these in the ENV vars editor in the dashboard. The environment variables docs cover the full lifecycle, including the difference between regular and secret values.
The minimum set for a Laravel app:
APP_KEY=base64:...
APP_ENV=production
APP_DEBUG=false
APP_URL=https://my-app.eu-1wa.frbit.app
LOG_CHANNEL=errorlog
SESSION_DRIVER=database
CACHE_DRIVER=database
QUEUE_CONNECTION=database
Generate APP_KEY once with php artisan key:generate --show locally and paste the result into the dashboard. Do not commit it. Set APP_URL to your environment's frbit.app hostname for now; switch it to your own domain once DNS is pointed.
LOG_CHANNEL=errorlog writes Laravel logs to the PHP error log, which fortrabbit collects and surfaces in the dashboard and over SSH. The default stack channel writes to storage/logs/laravel.log, which works but is harder to follow when you are watching multiple processes.
For the database, fortrabbit injects MYSQL_* environment variables automatically when the MySQL component is attached. Map them in config/database.php rather than copy-pasting credentials.
// config/database.php
'mysql' => [
'host' => env('MYSQL_HOST', '127.0.0.1'),
'port' => env('MYSQL_PORT', '3306'),
'database' => env('MYSQL_DATABASE'),
'username' => env('MYSQL_USER'),
'password' => env('MYSQL_PASSWORD'),
// …
],
The point of doing it this way: when you scale, swap or add an environment, the credentials change but your code does not.
Your first Laravel deploy
The flow is the same as any other GitHub-based hosting, with one extra step at app creation time:
# locally — initialize the repo and push to GitHub
git init
git add -A
git commit -m "Init"
gh repo create --source=. --private --push
Then in the fortrabbit dashboard, create a new app, choose the Laravel software preset, and pick the GitHub repo and branch you pushed. The first deploy runs immediately and will be slower than the rest — Composer has no cache to lean on. The build log streams in the dashboard.
If your app needs PHP extensions beyond the defaults (Imagick, BCMath, GD …), declare them in composer.json under require:
{
"require": {
"php": "^8.3",
"ext-bcmath": "*",
"ext-gd": "*"
}
}
The PHP component docs list every extension that ships out of the box.
Build commands
The build commands step runs after the deployment service has pulled the latest commit and before the build is synced into the environment. fortrabbit auto-detects composer install from composer.json and npm run build from package.json — for most Laravel apps with Vite or Mix, the defaults are correct.
If you need to customize, edit them in the dashboard per environment:
composer install --no-dev --prefer-dist --no-interaction --no-ansi --no-progress
npm ci
npm run build
Node.js is available during the build step, so compiling front-end assets happens here — no GitHub Actions or pre-commit build is needed. Built files (Vite's public/build, Mix's public/css and public/js, or wherever your bundler writes) are part of the deploy package and end up in the environment alongside your PHP code.
Post-deploy commands
Post-deploy commands run after the build is synced into the environment. This is where Laravel's optimization steps belong: caching config, routes and views, and running migrations.
Set them in the dashboard under the deployment settings of the environment:
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --force
A few notes on this list:
--forceis required formigratebecause the deploy environment is non-interactive. Laravel will refuse without it.config:cachereads your.envonce at build time and freezes it into a compiled file. Any environment variable change after that needs a redeploy or a manualconfig:clear.view:cacheandroute:cacheare safe and worthwhile in production — they cut request latency noticeably.
If a post-deploy command fails, the deployment is marked failed; the previous build keeps serving traffic. Fix the cause, push again.
Queues, schedulers and cron jobs
Laravel's scheduler expects a single cron entry that calls schedule:run every minute. On fortrabbit, set this up via cron jobs in the dashboard:
| Schedule | Command |
|---|---|
| Every minute | php artisan schedule:run |
For queue workers, fortrabbit supports long-running PHP processes via workers. Workers are an optional component — book one for your app first, then add a worker with the command:
php artisan queue:work --sleep=3 --tries=3 --max-time=3600
--max-time=3600 restarts the worker hourly, which keeps memory usage predictable and picks up code changes after a deploy without manual intervention.
If you would rather not book a worker, the cron-based fallback works for low-volume queues:
* * * * * php artisan queue:work --stop-when-empty --max-time=55
This processes whatever is in the queue once a minute and exits. Not real-time, but adequate for most transactional emails and webhook fan-out.
When to reach for GitHub Actions
The deployment service handles Composer and Node out of the box. GitHub Actions is still useful when you want something the deployment service does not do for you:
- Run your test suite on every push and only deploy if it passes
- Lint, type-check or run static analysis as a gate
- Build artifacts that depend on tooling fortrabbit's build environment does not include
A common pattern: run your CI on pull_request and push, and let the fortrabbit GitHub App handle the actual deploy when CI passes and the branch updates. The GitHub integration docs cover the workflow patterns; our GitHub Actions blog post walks through a CI-then-deploy example.
Zero-downtime deployments
The deployment service swaps builds atomically — the previous build serves traffic until the new build is ready, then the switch flips. For most Laravel apps, that is the zero-downtime story. The deployment strategy docs go deeper into the merge and replace strategies and what guarantees they offer.
The two cases where you can still see brief errors:
- Schema-breaking migrations. A request hitting a model whose underlying column was renamed mid-migration will throw. The fix is a two-step migration: deploy the additive change first (add the new column, write to both), backfill, then deploy the removal of the old column. Laravel's migration files give you the structure for this; your discipline gives you the result.
- Cached config drift. If you change
config/*.phpand ship without runningconfig:cachein post-deploy, the live app keeps serving the old cached version until something forces a rebuild. Always includeconfig:cachein your post-deploy list.
Troubleshooting
A short list of issues that come up often, and what they mean:
SQLSTATE[HY000] [2002] Connection refused — your app is trying to reach 127.0.0.1:3306 because a fallback in config/database.php hardcoded localhost. Fix the config to read from MYSQL_HOST with no default, and check that the MySQL component is attached.
Permission denied on storage/ — your repo committed files into storage/ with the wrong permissions, or storage/logs/laravel.log exists in git. Add storage/logs/* and storage/framework/{cache,sessions,views}/* to .gitignore, keep only .gitkeep files, and redeploy.
Composer runs out of memory — fortrabbit's build environment has a fixed memory limit. Add COMPOSER_MEMORY_LIMIT=-1 to your environment variables, or audit your dependency tree with composer why-not to find the package pulling in giants.
Asset URLs return 404 after deploy — Vite's manifest is missing because npm run build did not run, or the build output path was overridden. Confirm the build command ran in the deployment log, and that your bundler writes to public/build.
No application encryption key has been specified — APP_KEY is missing from your environment variables, or config:cache ran before it was set. Set the key in the dashboard, then redeploy.
These two streams answer 90% of "why is the app behaving this way" questions.
Related reading
- Deploy to fortrabbit with GitHub Actions — when CI gating before deploy makes sense.
- Deploying code with rsync — alternative transport for cases that do not fit Git deployment.
- Composer 2 availability — what changed when fortrabbit moved to Composer 2.
- Multi-stage deployment for website development — staging, develop and production patterns.
- MySQL JSON column with Laravel — schema patterns we use ourselves.
For the complete reference, the Laravel guide covers framework-specific details and edge cases this post does not — including install and deployment steps.
Where to go from here
If you have a Laravel app and an empty fortrabbit account, the practical first move is: install the GitHub App, create a fortrabbit app from your repo, set the environment variables above, and push. Most issues surface in the first deploy, and fortrabbit's error messages are direct enough to fix without a support ticket.
Once that works, the rest is the same Laravel work you already know — caching strategies, queue tuning, schema design. The platform stays out of the way and gives you predictable deploys for the cost of one git push.