Example Deployments
These guides show how to run nine popular PHP applications on OxPHP, each as a self-contained Docker Compose project. Every recipe was built and verified end-to-end. Its Verify section lists the requests that were checked and the status each one returns.
Each page is a complete, copy-pasteable recipe: a Dockerfile, a docker-compose.yml, the install commands, and the OxPHP-specific details that the application's stock documentation (written for nginx + PHP-FPM) does not cover.
The applications
| Application | Type | Routing mode | PHP | Extra services | Install method |
|---|---|---|---|---|---|
| Laravel | Framework | Framework | 8.5 | MySQL | composer create-project |
| Symfony | Framework | Framework | 8.5 | — | composer create-project |
| Yii3 | Framework | Framework | 8.5 | — | composer create-project |
| WordPress | CMS | Traditional | 8.5 | MySQL | WP-CLI |
| Drupal | CMS | Framework | 8.4 | MySQL | drush site:install |
| Craft CMS | CMS | Framework | 8.5 | MySQL | craft install |
| October CMS | CMS | Framework + mirror | 8.4 | MySQL | october:migrate + mirror |
| Magento | E-commerce | Framework | 8.4 | MySQL + OpenSearch | bin/magento setup:install |
| OpenCart | E-commerce | Traditional | 8.4 | MySQL | CLI installer |
What every recipe has in common
Build on the published OxPHP image
OxPHP ships a ready PHP runtime as ghcr.io/oxphp/oxphp (PHP 8.5 by default; a PHP 8.4 variant is published as ghcr.io/oxphp/oxphp:<ver>-php8.4-alpine<X>). The published image already contains the oxphp binary, libphp.so, the OxPHP SAPI extension, the PHP CLI, and Composer-friendly tooling. The recipes extend it in one of two shapes:
-
Copy OxPHP into a
php:*-zts-alpinebase. Laravel, Symfony, Yii3, Craft, Magento, OpenCart, Drupal and October are built this way. A multi-stage build assembles adevimage from four stages:FROM php:8.4-zts-alpine3.23 AS php-base # your app's PHP extensions FROM composer:2 AS composer # the Composer binary FROM ghcr.io/oxphp/oxphp:0.13.0-php8.4-alpine3.23 AS oxphp # OxPHP artifacts FROM php-base AS dev # final image # ... copy the oxphp binary, bridge library, and SAPI extension across: COPY --from=oxphp /usr/local/bin/oxphp /usr/local/bin/oxphp COPY --from=oxphp /usr/local/lib/liboxphp_bridge.so /usr/local/lib/ COPY --from=oxphp /usr/local/lib/php/extensions/ /tmp/oxphp-ext/ RUN cp /tmp/oxphp-ext/*/oxphp_sapi.so "$(php -r 'echo ini_get("extension_dir");')/" \ && echo "extension=oxphp_sapi.so" > /usr/local/etc/php/conf.d/oxphp-ext.ini -
Extend the OxPHP runtime directly, as the WordPress recipe does. A builder stage compiles the extensions against a matching
php:*-zts-alpineand drops the.sofiles into the OxPHP image.
Either way, the PHP ABI must match. The OxPHP image's libphp.so and oxphp_sapi.so are compiled for one PHP version (e.g. 8.4 → no-debug-zts-20240924); the php-base/builder stage must use the same php:<X.Y>-zts-alpine<Z> so the extensions you compile are ABI-compatible. Mixing versions makes oxphp_sapi.so refuse to load or corrupts musl TLS at startup.
See the Docker Guide for the canonical multi-stage template, and examples/dockerfile/ in the repository for a ready-to-copy version.
Pick the PHP version deliberately
The default ghcr.io/oxphp/oxphp:<ver> image is PHP 8.5. That is fine for modern frameworks (Laravel, Symfony, Yii3, Craft). Older or more conservative codebases (Magento, OpenCart, Drupal, October CMS) pin PHP 8.4 via the …-php8.4-alpine… tag, because their component stacks predate 8.5 and emit deprecations on it. Each recipe states which it uses and why.
Choose the routing mode from the app's shape
OxPHP's routing mode maps directly onto how the application is laid out:
- Framework mode (
ENTRY_FILE=index.php) fits an app with one front controller under apublic/(orweb/,pub/) directory. Existing static files are served from disk and everything else is dispatched toindex.php. Use it for Laravel, Symfony, Yii3, Craft, Magento, Drupal, October. - Traditional mode (no
ENTRY_FILE) serves multiple physical PHP entry points (e.g.index.phpplus anadmin/directory) as real files. Use it for WordPress and OpenCart.
Install through the same container
The dev image carries the PHP CLI and Composer (and drush, bin/magento, php yii, php craft, php artisan as appropriate), so install and maintenance commands run inside the running container, without a separate toolchain. WordPress is the exception: its image extends the OxPHP runtime, which ships no wp binary, so WP-CLI runs in the wpcli sidecar container instead. For the other recipes:
docker compose exec app php artisan migrate # Laravel
docker compose exec app vendor/bin/drush cr # Drupal
docker compose run --rm app composer install # anySecurity defaults that apply everywhere
OxPHP gives you, for free, several protections that nginx + PHP-FPM require explicit config for:
- Dot-path blocking makes
.env,.git/,.htaccessand any other dot-segment path return404without configuration. This is why running an app from a directory that also contains.envdoes not leak it. - The traditional-mode recipes use the PHP execution deny-list (
PHP_DENY_PATHS). OpenCart blockssystem/andinstall/scripts, and WordPress blocks.phpexecution underwp-content/uploads/. It is a no-op in framework mode, where arbitrary.phpis never executed directly. - October CMS uses symlink allow-paths (
SYMLINK_ALLOW_PATHS) so that OxPHP follows the asset symlinks produced byoctober:mirror public. Symlink escapes stay blocked everywhere else.
The recipes
The pages are grouped by application type, mirroring the directory layout:
examples/
├── framework/ # Laravel, Symfony, Yii3
├── cms/ # WordPress, Drupal, Craft, October
└── ecommerce/ # Magento, OpenCartFramework — framework/
- Laravel is the canonical framework-mode app.
- Symfony is a minimal skeleton with no database.
- Yii3 is the leanest of all, with core extensions only.
CMS — cms/
- WordPress runs in traditional mode, with a runtime-extension build and a WP-CLI sidecar.
- Drupal runs in framework mode, with PDO +
drush. - Craft CMS runs in framework mode and installs from the console.
- October CMS runs in framework mode with a
public/mirror andSYMLINK_ALLOW_PATHS.