PHP

Detected from composer.json. Served with FrankenPHP.

See Buildpacksfor how detection and version policy work. A tree that also has package.json gets Node.jsin the same build (Laravel + Vite).

Detects

composer.json in the base directory.

Versions

Default is PHP 8.3. Published lines as of September 2026: 8.2-8.5. You get the latest patch of the requested minor.

Pin, in this order:

  • .php-version
  • require.php in composer.json (a range picks the highest matching published version)

Composer itself is a build tool. You do not pin it.

Install and build

composer install --no-dev --optimize-autoloader. Laravel (artisan) then caches config, routes, views, and events when those Artisan commands exist. Symfony (bin/console plus symfony.lock) runs cache:warmup --env=prod.

Launch defaults: Laravel APP_ENV=production, LOG_CHANNEL=stderr, APP_DEBUG=false. Symfony APP_ENV=prod, APP_DEBUG=0.

Start command

Document root is public/, or web/, or the app root.

  • Laravel Octane (lockfile lists laravel/octane): php artisan octane:frankenphp --port=$PORT --host=0.0.0.0
  • Otherwise: frankenphp php-server --listen :$PORT --root <docroot>

Override with a Procfile. Listen on PORT.