Skip to main content
A PHP application runs on Sevalla as an application: Sevalla builds an image from your Git repository, then runs it as processes. The web process serves HTTP with FrankenPHP, a PHP server built on Caddy, background workers run queue consumers and schedulers, and jobs run migrations before each deployment.

Framework guides

Laravel

Deploy Laravel with PostgreSQL, then add queues, the scheduler, object storage and Redis caching.

Symfony

Deploy a Symfony application from a Git repository.

WordPress

Deploy WordPress from a Git repository with a MySQL database.

How PHP applications run on Sevalla

Every process (web, worker, cron job and job) runs from the same image, each with its own start command. Ingot recommends processes it finds a use for, such as a migration job when database/migrations has migrations, and lists them in the generated Dockerfile’s header; it doesn’t create them. See Run workers and jobs.

Pin the PHP version

Ingot builds on PHP 8.5, its default line, unless the project sets a version. Pin the line you develop with in require.php in composer.json, so the build doesn’t change when a new Ingot release moves its default line:
Ingot takes the version from the first of these that’s set: the INGOT_PHP_VERSION environment variable, versions.php in ingot.json, config.platform.php in composer.json, then require.php. A range such as ~8.5.0 or ^8.3 builds on the newest release of a line it allows, preferring Ingot’s default line, so every new build picks up the line’s security fixes. Ingot 0.4.1 knows PHP 8.2 to 8.5. The build plan’s PHP row shows the version and where it came from. Ingot also checks composer.lock, because Composer refuses to install a package that doesn’t support the PHP it runs on:
  • When only require.php sets the version, or nothing does, and a locked package doesn’t support the line Ingot would choose, Ingot builds on the newest line that every locked package supports and names the package in the PHP row.
  • When you set the version with the variable, ingot.json or config.platform.php, and a locked package doesn’t support it, the build fails with PHP 8.5 doesn't satisfy composer.lock: <package> requires php "<range>". Run composer update to lock newer packages, or choose a version they support.
  • Write ~8.5.0 or 8.5.* rather than 8.5: Composer reads 8.5 as exactly 8.5.0, so Ingot installs with --ignore-platform-req=php+ and warns about it.
See Set the language version.

PHP extensions

FrankenPHP’s image includes the extensions most applications need, among them ctype, curl, dom, fileinfo, mbstring, openssl, pdo_sqlite, sodium, tokenizer, xml, zlib and OPcache. Ingot installs more from these sources, and the build plan’s Extensions row names each one with its source: To add an extension Ingot doesn’t detect, add it to require in composer.json, for example "ext-imagick": "*". Composer then refuses to install without it on your computer too, which keeps your computer and the image in step.

Build with a Dockerfile

Ingot needs no Dockerfile. To control every layer of the image, the Dockerfile build strategy builds your own. A Dockerfile for PHP on Sevalla installs the extensions with install-php-extensions in a stage based on FrankenPHP’s official image and lists the Debian packages their libraries come from, installs the dependencies with composer install --no-dev from composer.lock in a build stage, builds front-end assets in a Node.js stage, and copies PHP, those packages and the application into a debian:trixie-slim final image that runs as a non-root user. FrankenPHP listens on PORT and trusts Sevalla’s proxy for HTTPS, as Ingot’s configuration does, and nothing secret is written into the image. Deploy Laravel, Symfony and WordPress each have a complete example.