Raised: $0
0% of monthly goal Help us cross the finish line!
Goal: $12,000
Raised: $0 Goal: $12,000
0% of monthly goal Help us cross the finish line!
Sponsor DDEV

If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.

add-on registry tests last commit release

DDEV Rapira

Note

This add-on was created with the assistance of AI. It has been verified end-to-end against real DDEV + Docker, but review it before relying on it in your environment.

Serve your PHP application with Rapira in DDEV, with DDEV’s nginx still in front.

Rapira is a PHP application server written in Rust. In classic mode it runs your entry script per request, so it serves any framework unchanged; in dispatcher mode it keeps a booted application resident between requests.

It embeds libphp.so rather than talking to a separate PHP process, so this add-on points it at the image’s own PHP. Your project keeps the PHP version, extensions, ini values and .ddev/php/*.ini overrides it already had, and ddev xdebug on, ddev xhprof on, ddev php, ddev composer and the rest keep working.

Install

ddev add-on get FluffyDiscord/ddev-rapira
ddev restart
git add -f .ddev/nginx_full/nginx-site.conf

The -f matters if you commit .ddev: DDEV lists that path in the .ddev/.gitignore it generates, and drops it again only on the next start. Commit it in the window between and a fresh clone silently falls back to php-fpm — which still answers 200, so it is a confusing thing to debug.

Requirements

Trusted proxies

nginx reaches Rapira over plain HTTP on 127.0.0.1, so the app sees REMOTE_ADDR=127.0.0.1, an empty HTTPS, and SERVER_PORT=8000. The add-on sends X-Real-IP, X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host; your app has to be told to trust them. For Symfony:

# config/packages/framework.yaml
framework:
    trusted_proxies: '127.0.0.1'
    trusted_headers: ['x-forwarded-for', 'x-forwarded-proto', 'x-forwarded-host']

Without this, Request::isSecure() is false and generated URLs come out as http://…:8000.

X-Forwarded-Port is deliberately not sent, so leave x-forwarded-port out of trusted_headers: nginx’s $server_port is its own listening port, so it would report 80 for a request ddev-router terminated as HTTPS, and an app that trusts it builds https://host:80/ URLs. With the header absent, Symfony derives 443 from the forwarded scheme.

A proxy cannot supply everything fastcgi_params did. What changes, measured:

$_SERVER key Under php-fpm Under Rapira What to use instead
SERVER_NAME the request’s host localhost, whatever the Host header says HTTP_HOST, or a trusted X-Forwarded-Host
SERVER_PORT 80 8000 Nothing — derive it from the forwarded scheme
HTTPS on / off empty Trusted X-Forwarded-Proto
REQUEST_SCHEME https http Trusted X-Forwarded-Proto
REMOTE_ADDR the client 127.0.0.1 Trusted X-Real-IP / X-Forwarded-For
SERVER_ADDR, DOCUMENT_URI, REMOTE_USER, REDIRECT_STATUS set absent Nothing
SERVER_SOFTWARE nginx/<version> Rapira
CONTENT_TYPE '' when absent absent when absent $_SERVER['CONTENT_TYPE'] ?? ''

X-Accel-Redirect and X-Accel-Buffering change hands too: nginx now acts on them itself instead of forwarding them to the client, which is what a resident-worker app wants in production.

Configuration

What Default How to change
Rapira config file rapira.toml in the project root, else classic mode on <docroot>/index.php ddev dotenv set .ddev/.env.web --rapira-config-file=rapira.dev.toml && ddev restart
Docroot (the override’s nginx root) your ddev config --docroot, set at install Edit root in .ddev/nginx_full/nginx-site.conf and ddev restart
Rapira version v0.8.1 ARG RAPIRA_VERSION in .ddev/web-build/Dockerfile.rapira, then ddev restart

The add-on does not write or manage rapira.toml — it is yours, DDEV bind-mounts it, and it stays live. Copy example.rapira.toml to your project root to start from something. One thing that file cannot control: the add-on passes --listen 127.0.0.1:8000 on the command line, which overrides [http] listen, because nginx proxies to that fixed port.

Edits to .ddev/nginx_full/nginx-site.conf are replaced on the next ddev add-on get (the old file is kept beside it as nginx-site.conf.ddev-rapira-backup-<epoch>). Put additive rules in .ddev/nginx/*.conf, which the override still includes.

ddev rapira-restart

ddev rapira-restart

In dispatcher or worker mode the application stays resident, so a code change needs this. In classic mode you do not — the entry script re-runs per request. It also starts the daemon if it is stopped, which is the fix for the case below.

Removal

ddev add-on remove rapira
git rm --cached .ddev/nginx_full/nginx-site.conf   # if you committed it
ddev restart

The git rm --cached matters: a force-added file stays tracked, so without it the next branch switch restores an nginx config pointing at a port nothing is listening on.

Known limitations

Resources

Credits

Contributed by @FluffyDiscord

If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.