DocsPHP and Laravel

PHP and Laravel

Deploy a PHP app, including Laravel, on Ruust: built with Nixpacks, served on HTTPS, with Composer install and migrations run on every deploy.

Ruust hands your repo to Nixpacks, which recognises a PHP app from composer.json, installs PHP and runs composer install for you. For a Laravel project Nixpacks also treats public/ as the web root, so the front controller public/index.php is what gets served. Your Egg is a real always-on server, so queued jobs, scheduled tasks and long-running requests all run without cold starts. If you would rather control the image yourself, add a Dockerfile at the repo root and Ruust builds that instead (in which case Nixpacks is not involved).

Deploy it

  1. Push your PHP or Laravel app to a Git repo.
  2. In the dashboard, choose New Egg and connect the repo.
  3. Set APP_KEY, APP_ENV=production and DATABASE_URL on the Egg before you build.
  4. Pick a size and a region, then lay the Egg. Ruust runs composer install, builds it, and hatches it on HTTPS.

The build

Nixpacks runs composer install for you. For Laravel, cache your configuration and routes during the build so the running Egg does not read them from disk on every request. Run php artisan config:cache (and, if it suits your app, php artisan route:cache and php artisan view:cache) as part of the build.

text
composer install --no-dev --optimize-autoloader
php artisan config:cache
Install dependencies and cache the Laravel config during the build.

The port

Ruust routes to a fixed container port, 8080, and injects it as the $PORT environment variable. Ruust does not auto-detect the port your app listens on, so your app MUST bind the injected $PORT (that is 8080) and bind to 0.0.0.0 (not localhost), or the Egg builds but never hatches. Set the start command with a Procfile at the repo root: the web: line is honoured by the Nixpacks build, not by Ruust itself, and is ignored when you supply your own Dockerfile. The simplest server is PHP built-in, told to bind 0.0.0.0 on $PORT with the document root pointed at public/.

text
web: php -S 0.0.0.0:$PORT -t public public/index.php
release: php artisan migrate --force
Procfile at the repo root: serve from public/ on PORT, and migrate on every deploy.

For heavier traffic, run FrankenPHP or a php-fpm and nginx pair instead of the built-in server. Whichever you choose, the listener has to bind 0.0.0.0:$PORT. With FrankenPHP, that is frankenphp php-server --listen 0.0.0.0:$PORT --root public.

Migrations on deploy

The release: line in your Procfile runs once per deploy, after the build and before the new Egg hatches. Put php artisan migrate --force there so your schema is always in step with the code you are shipping. The --force flag is required because Laravel refuses to migrate in production without it. If a migration fails, the release stops and the old Egg keeps serving.

Environment variables

Set variables on the Egg rather than committing a .env file: APP_KEY (generate one locally with php artisan key:generate --show), APP_ENV=production, APP_DEBUG=false and DATABASE_URL for your database. They are available at build and run time, encrypted at rest, and never printed in logs. Changing a run-time value rolls the container automatically: Ruust bumps an env hash in desired state, so you do not need to redeploy by hand. The one caveat is the Laravel config:cache command, which freezes values into a config cache at build time; if you cache the config, run php artisan config:clear (or rebuild the cache) so a changed value is picked up.

Custom domains

Every Egg gets a <name>.<region>.ruust.run URL immediately. To serve your own domain, add it under the Egg and point the DNS record we show you; Ruust issues and renews TLS automatically. Set APP_URL to your final HTTPS address so generated links, redirects and signed URLs are correct.