Skip to content

Deploy Laravel

This guide deploys a Laravel 11, 12 or 13 app from a Git repository to one or more servers. You end with zero-downtime deployments, migrations that run once, a working scheduler, and optional queue workers or Horizon. Statamic sites use the same flow with the Statamic preset.

  • A connected Git provider: GitHub App or a token.
  • An active server of type app or web with PHP installed (FrankenPHP is the default runtime). See Connect a server.
  • Optional: a database. Create one on an app server with a database engine, or on a db server. See Create databases.
  • Your repository has composer.json and artisan. If it has a package.json with a build script (Vite), assets are built too.
  1. Create a database (skip if your app does not need one). On the canvas: + Create → Database, pick the engine and server. Name the service, for example shop-db.

  2. Create the site. + Create → Git repository → choose your connection, repository and branch. Falak detects Laravel and selects the Laravel preset.

  3. Pick servers. Select one or more servers. The first one is the leader: migrations and the scheduler run there.

  4. Choose a domain. Keep Generate for an instant sslip.io URL, or enter your own domain. See Domains.

  5. Click Deploy. The panel opens on the Deployments tab and streams the first deployment.

  6. Connect the database. Open the site’s Variables tab and add references to the database service:

    Site variables
    DB_CONNECTION=${{ shop-db.DB_CONNECTION }}
    DB_HOST=${{ shop-db.DB_HOST }}
    DB_PORT=${{ shop-db.DB_PORT }}
    DB_DATABASE=${{ shop-db.DB_DATABASE }}
    DB_USERNAME=${{ shop-db.DB_USERNAME }}
    DB_PASSWORD=${{ shop-db.DB_PASSWORD }}

    Or use one line: DB_URL=${{ shop-db.DATABASE_URL }}. References resolve at deploy time. See Variable references.

    If the database lives on the same app server as the site, the reference gives DB_HOST=127.0.0.1 for a native site, or the server’s own address for a Docker site (engines on app servers serve that server only). That works only while the site runs on that server alone; on other servers the deploy fails with an explanation, so use a database on a db server and allow the app servers in its firewall.

  7. Redeploy to apply the variables. Variable changes always take effect on the next deployment.

Setting Value
Runtime frankenphp (switch to php-fpm if you prefer)
Web directory public
Shared paths storage/ (directory), .env (file) — kept across releases in shared/
Health check GET /up expecting 200 (Laravel’s built-in health route)
Scheduler On
Horizon, Octane Off
Initial variables APP_NAME, APP_ENV=production, APP_KEY (generated base64: key), APP_DEBUG=false, APP_URL (the chosen domain), LOG_CHANNEL=daily
Deploy script (Laravel preset)
$FALAK_FETCH
cd "$FALAK_RELEASE_DIR"
if [ "$FALAK_IS_LEADER" = "1" ]; then
$FALAK_PHP artisan migrate --force
fi
$FALAK_PHP artisan optimize
$FALAK_PHP artisan storage:link --force
$FALAK_ACTIVATE
$FALAK_RESTART_PROCS
  • $FALAK_FETCH downloads the built release and links shared paths.
  • The if block runs only on the leader, so migrations run once even with ten servers.
  • $FALAK_PHP is the PHP CLI of the site’s PHP version, for example php8.4.
  • $FALAK_ACTIVATE switches current on all servers together.
  • $FALAK_RESTART_PROCS restarts workers, Horizon and Octane with the new release.

Edit it under Settings → Deploy. All macros and variables are in Deploy scripts.

The server’s FrankenPHP process embeds Caddy and runs PHP in-process. There is no separate PHP-FPM. It is the default for new servers and sites, and it enables Octane in worker mode.

On FrankenPHP, PHP runs as the edge user, which joins each site’s group so it can read .env and write to storage/.

Add queue workers under the Processes tab:

Field Meaning Range
Connection Queue connection (empty = default)
Queue Comma-separated queue names
Processes Number of worker processes 1–64
Timeout, Sleep, Tries, Backoff Passed to queue:work
Max jobs, Max time Restart a worker after N jobs / seconds
Memory --memory in MB 32–65536
Command Replace queue:work with your own command
Servers Run on selected servers only (default: all)

The worker command is php8.4 artisan queue:work <connection> --queue=<queues> --sleep=<s> --tries=<n> --timeout=<s> --memory=<mb> …. Workers restart on every deployment. See Processes.

Turn on Horizon under Settings → Laravel. Falak supervises php artisan horizon on each server and sends horizon:terminate on deploys so running jobs finish (with a 120-second stop timeout). Install laravel/horizon in your app first.

The scheduler is on by default. Falak runs php artisan schedule:run every minute on the leader only, in the current release, as a cron job with a heartbeat. Missed runs show up under Insights → Heartbeats.

Laravel’s withoutOverlapping() handles overlaps; Falak never skips a minute because a previous run is still going.

Toggle Maintenance under Settings → Laravel. Falak runs php artisan down --retry=60 (or php artisan up) on every server immediately.

New Laravel sites use LOG_CHANNEL=daily. The agent tails shared/storage/logs/*.log, merges multi-line stack traces into one record, and ships them to Loki with the site’s labels. See Logs.

Install the Falak APM package to get request timelines, slow queries, N+1 hints, job traces and grouped exceptions:

Terminal window
composer require falak/apm-laravel

No configuration is needed on a Falak server. See APM for Laravel.

Use Settings → Commands to run a one-off command such as php artisan tinker --execute="…" or php artisan cache:clear on the site’s servers, with live output. Commands time out after 600 seconds (FALAK_SITE_COMMAND_TIMEOUT).

Symptom Cause and fix
Health check fails with 500 Open View logs, then the site’s Logs tab. Usually a missing variable (database, APP_KEY) or a failed artisan optimize.
Unresolved variable references: … A ${{ service.KEY }} points at a service or key that does not exist in this environment.
Permission denied writing to storage/ storage/ must be a shared path (default). Redeploy so permissions are fixed.
Migrations ran twice Your script runs migrations outside the FALAK_IS_LEADER block.
Scheduler never runs It only runs on the leader, and only after the first successful deployment.
Assets missing Your package.json needs a build script; node_modules is not shipped for PHP sites.