# Deploying Laravel on fortrabbit

> Deploy Laravel to fortrabbit via GitHub — environment variables, build commands, post-deploy migrations, queue workers and zero-downtime swaps.

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.

<call-out>

This guide was written with Laravel 13 and Laravel 12 in mind. Have a look at our [updated Laravel guides](https://docs.fortrabbit.com/guides/laravel) too.

</call-out>

## Before you begin

You need a Laravel application that runs locally, a GitHub account, a fortrabbit account, and the <content-link prefix="docs" text="fortrabbit GitHub App" href="/platform/deployment/github-app">



</content-link>

 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 <content-link prefix="docs" text="environment" href="/platform/objects/environment">



</content-link>

 — 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 `.env` file 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 <content-link prefix="dash" text="ENV vars editor">



</content-link>

 in the dashboard. The <content-link prefix="docs" text="environment variables docs" href="/dev/env-vars/intro">



</content-link>

 cover the full lifecycle, including the difference between regular and secret values.

The minimum set for a Laravel app:

```env
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 <content-link prefix="docs" text="MySQL component" href="/platform/components/mysql">



</content-link>

 is attached. Map them in `config/database.php` rather than copy-pasting credentials.

```php
// 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:

```bash
# 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`:

```json
{
  "require": {
    "php": "^8.3",
    "ext-bcmath": "*",
    "ext-gd": "*"
  }
}
```

The <content-link prefix="docs" text="PHP component docs" href="/platform/components/php">



</content-link>

 list every extension that ships out of the box.

## Build commands

The <content-link prefix="docs" text="build commands" href="/platform/deployment/build-commands">



</content-link>

 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:

```bash
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

<content-link prefix="docs" text="Post-deploy commands" href="/platform/deployment/post-deploy-commands">



</content-link>

 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:

```bash
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --force
```

A few notes on this list:

- `--force` is required for `migrate` because the deploy environment is non-interactive. Laravel will refuse without it.
- `config:cache` reads your `.env` once at build time and freezes it into a compiled file. Any environment variable change after that needs a redeploy or a manual `config:clear`.
- `view:cache` and `route:cache` are 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 <content-link prefix="docs" text="cron jobs" href="/platform/jobs/crons">



</content-link>

 in the dashboard:

<table>
<thead>
  <tr>
    <th>
      Schedule
    </th>
    
    <th>
      Command
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Every minute
    </td>
    
    <td>
      <code>
        php artisan schedule:run
      </code>
    </td>
  </tr>
</tbody>
</table>

For queue workers, fortrabbit supports long-running PHP processes via <content-link prefix="docs" text="workers" href="/platform/jobs/workers">



</content-link>

. Workers are an optional component — book one for your app first, then add a worker with the command:

```bash
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:

```bash
* * * * * 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 <content-link prefix="docs" text="GitHub integration docs" href="/integrations/git-providers/github">



</content-link>

 cover the workflow patterns; our <content-link prefix="blog" text="GitHub Actions blog post" href="/how-to-use-github-actions">



</content-link>

 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 <content-link prefix="docs" text="deployment strategy docs" href="/platform/deployment/strategy">



</content-link>

 go deeper into the merge and replace strategies and what guarantees they offer.

The two cases where you can still see brief errors:

1. **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.
2. **Cached config drift.** If you change `config/*.php` and ship without running `config:cache` in post-deploy, the live app keeps serving the old cached version until something forces a rebuild. Always include `config:cache` in 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

- <content-link prefix="blog" text="Deploy to fortrabbit with GitHub Actions" href="/how-to-use-github-actions">



</content-link>

 — when CI gating before deploy makes sense.
- <content-link prefix="blog" text="Deploying code with rsync" href="/deploying-code-with-rsync">



</content-link>

 — alternative transport for cases that do not fit Git deployment.
- <content-link prefix="blog" text="Composer 2 availability" href="/composer-2-availability">



</content-link>

 — what changed when fortrabbit moved to Composer 2.
- <content-link prefix="blog" text="Multi-stage deployment for website development" href="/multi-stage-deployment-for-website-development">



</content-link>

 — staging, develop and production patterns.
- <content-link prefix="blog" text="MySQL JSON column with Laravel" href="/mysql-json-column-with-laravel">



</content-link>

 — schema patterns we use ourselves.

For the complete reference, the <content-link prefix="docs" text="Laravel guide" href="/guides/laravel">



</content-link>

 covers framework-specific details and edge cases this post does not — including <content-link prefix="docs" text="install" href="/guides/laravel/install">



</content-link>

 and <content-link prefix="docs" text="deployment" href="/guides/laravel/deployment">



</content-link>

 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`.
