Runway
Runway is built and operated in the EU!

Workers & Init

First steps

Most PHP apps are a single web process, and Runway starts that for you without any configuration. Some apps need more:

Runway orchestrates this with a Procfile. The process types and their general semantics are the same for every language; see the multi-process guide and the Procfile reference for the full picture. This page covers the PHP-specific parts and gives a working example.

The web command for PHP

Every Procfile needs a web: process.

When there is no Procfile, Runway starts that default web process for you: php-fpm together with the web server. To run the same web process from a Procfile, use:

Procfile
web: procmgr-binary /layers/paketo-buildpacks_php-start/php-start/procs.yml

procmgr-binary and the generated procs.yml both come from Runway’s PHP buildpack. That YAML file already contains the correct php-fpm and web-server commands for the server you build with, so you don’t have to hand-write them.

A working example

A minimal, framework-free app with all three process types. The layout:

.
├── Procfile
├── bin
│   ├── init.php
│   └── worker.php
└── htdocs
    └── index.php

web

htdocs/index.php
<?php
echo "Hello from the web process!\n";

init

init runs once per deploy, before web starts. If it exits non-zero, the deploy fails and the previous version keeps serving. This is where migrations belong: Runway has no separate “release” phase.

bin/init.php
<?php
// A real app runs its migration tool here and lets a failure abort the deploy.
fwrite(STDERR, "running migrations...\n");
// ... apply migrations ...
fwrite(STDERR, "migrations done\n");

worker

worker runs continuously and does not listen on a port. It can reach the web process over the loopback interface on PORT. Keep it running: if it exits, the whole app restarts.

bin/worker.php
<?php
while (true) {
    // pull one job off your queue and handle it
    fwrite(STDERR, sprintf("worker tick %s\n", date(DATE_ATOM)));
    sleep(5);
}

Procfile

Because the Procfile replaces the default process list entirely, web: is declared explicitly next to init and worker:

Procfile
web: procmgr-binary /layers/paketo-buildpacks_php-start/php-start/procs.yml
init: php bin/init.php
worker: php bin/worker.php

Deploy

$ runway app create
$ runway app config set BP_PHP_SERVER=httpd
$ git add -A && git commit -m "initial commit"

Deploy to Runway

runway app deploy

Run runway app open to see it live, or check the Runway UI. Check runway app logs if something’s off.

Check that all three process types are running:

$ runway app ps
$ runway app logs

Framework alternatives

In a real project the init and worker commands call your framework’s tools instead of the stand-in scripts above. The web: line stays the same.

Symfony

Procfile
web: procmgr-binary /layers/paketo-buildpacks_php-start/php-start/procs.yml
init: php bin/console doctrine:migrations:migrate --no-interaction
worker: bin/worker

bin/console is Symfony’s CLI entrypoint. A Messenger consumer has to stop and come back periodically: after a transient database error Doctrine’s EntityManager can be left broken and fail every following message. messenger:consume --time-limit does the stopping, but the process has to be brought straight back up itself, so point the worker at a small committed script that loops:

bin/worker
#!/bin/sh
# Restart loop for the Messenger consumer: --time-limit exits are intentional
# (poisoned EntityManager recovery), the process must come back up.
while true; do
    php bin/console messenger:consume async --time-limit=300 --memory-limit=256M
    sleep 1
done

Laravel

Procfile
web: procmgr-binary /layers/paketo-buildpacks_php-start/php-start/procs.yml
init: php artisan migrate --force
worker: php artisan queue:work

--force lets migrate run without the interactive confirmation Laravel asks for in production. queue:work runs the queue in one long-lived process. If a job can leave the database connection broken, wrap it in the same loop shown for Symfony above.

How the processes run

All processes run from the same image and share the configuration you set with runway app config set, but each runs in its own environment and cannot share local memory with the others.

To inspect what is running, and to open a shell in the worker container:

runway app ps
runway app exec -w sh

Next steps