Laravel on OxPHP
Laravel is the canonical framework-mode application: a single front controller at public/index.php, with static assets served straight from public/. OxPHP's framework routing mode maps onto this exactly. Existing files are served from disk; everything else is dispatched to index.php.
Stack at a glance
- OxPHP image:
ghcr.io/oxphp/oxphp:0.13.0(PHP 8.5) - Routing mode: Framework (
ENTRY_FILE=index.php;DOCUMENT_ROOTleft at the default/var/www/html/public) - Extensions added:
pdo_mysql,intl - Services: OxPHP + MySQL
- URL:
http://localhost:8091· internalhttp://localhost:9092/health
Project layout
laravel-oxphp/
├── docker-compose.yml
└── src/ # composer create-project laravel/laravel src
├── Dockerfile
└── … # the Laravel applicationGenerate the app first:
mkdir -p laravel-oxphp/src
docker run --rm -v "$PWD/laravel-oxphp/src":/app -w /app \
composer:2 create-project laravel/laravel . --prefer-distThen point src/.env at the database service:
APP_URL=http://localhost:8091
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=laravelDockerfile and Compose
src/Dockerfile copies OxPHP into a php:8.5-zts-alpine base that carries Laravel's runtime extensions.
ARG OXPHP_VERSION=0.13.0
ARG PHP_VERSION=8.5
ARG ALPINE_VERSION=3.23
# ── PHP base: Laravel runtime extensions ──────────────────────
FROM php:${PHP_VERSION}-zts-alpine${ALPINE_VERSION} AS php-base
RUN apk add --no-cache icu-libs \
&& apk add --no-cache --virtual .build-deps icu-dev \
&& docker-php-ext-install pdo_mysql intl \
&& apk del .build-deps
# ── Composer ──────────────────────────────────────────────────
FROM composer:2 AS composer
# ── OxPHP artifacts ───────────────────────────────────────────
FROM ghcr.io/oxphp/oxphp:${OXPHP_VERSION} AS oxphp
# ── dev: OxPHP server + PHP CLI + Composer ────────────────────
FROM php-base AS dev
RUN apk add --no-cache libgcc
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
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");')/" \
&& rm -rf /tmp/oxphp-ext \
&& echo "extension=oxphp_sapi.so" > /usr/local/etc/php/conf.d/oxphp-ext.ini
RUN { \
echo "[opcache]"; echo "opcache.enable=1"; echo "opcache.enable_cli=1"; \
echo "opcache.validate_timestamps=1"; echo "opcache.revalidate_freq=0"; \
} > /usr/local/etc/php/conf.d/opcache-dev.ini
RUN getent passwd www-data >/dev/null \
|| adduser -D -H -u 82 -G www-data -s /sbin/nologin www-data
RUN mkdir -p /var/www/html/public && chown -R www-data:www-data /var/www/html
ENV LD_LIBRARY_PATH=/usr/local/lib
WORKDIR /var/www/html
COPY --chown=www-data:www-data . /var/www/html
EXPOSE 80 9090
CMD ["oxphp"]services:
app:
build:
context: ./src
target: dev
image: laravel-oxphp/app:dev
container_name: laravel-oxphp
ports:
- "8091:80"
- "9092:9090"
volumes:
- ./src:/var/www/html # live-edit without rebuilding
environment:
# LISTEN_ADDR (0.0.0.0:80) and DOCUMENT_ROOT (/var/www/html/public) are
# the OxPHP defaults, so both are omitted.
ENTRY_FILE: index.php # framework mode
INTERNAL_ADDR: 0.0.0.0:9090
ACCESS_LOG: all
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://0.0.0.0:9090/health"]
interval: 10s
timeout: 3s
retries: 5
start_period: 5s
db:
image: mysql:9
container_name: laravel-oxphp-db
ports:
- "3308:3306"
environment:
MYSQL_DATABASE: laravel
MYSQL_USER: laravel
MYSQL_PASSWORD: laravel
MYSQL_ROOT_PASSWORD: root
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"]
interval: 5s
timeout: 5s
retries: 20
start_period: 15s
restart: unless-stopped
volumes:
db_data:Install and first run
The dev image carries the PHP CLI and Composer, so artisan runs in the same container:
docker compose up -d --build
docker compose exec app php artisan key:generate
docker compose exec app php artisan migrateOxPHP notes
ENTRY_FILE=index.phpselects framework mode. Laravel'spublic/becomesDOCUMENT_ROOT, and the framework's own.env,app/andvendor/stay outside the document root, where they are never reachable over HTTP.- The container environment overrides
.env. OxPHP passes the container environment to PHP, and Laravel's dotenv loader does not override real environment variables, soDB_HOST,APP_ENV, etc. set in Compose win. - In the
devtarget OPcache revalidates on every request, so edits on the bind-mountedsrc/take effect immediately. For production, setopcache.validate_timestamps=0and drop the bind mount.
Verify
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8091/ # 200
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8091/up # 200 (health route)
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:9092/health # 200Prompts for your agent
These prompts assume an agent with the OxPHP documentation MCP server connected. The agent reads the pages it needs itself.
Run my Laravel application on OxPHP locally. Read the `examples/framework/laravel` page through the OxPHP documentation MCP server.
Write the Dockerfile and the Compose file for this repository specifically — keeping the database, cache and queue services it already uses — and give me the commands to bring it up the first time.My Laravel app runs on OxPHP but something behaves differently than it did under PHP-FPM. Read the `examples/framework/laravel` page through the OxPHP documentation MCP server.
Go through the OxPHP-specific notes on that page against my code and tell me which one explains what I am seeing.See also
- Routing · Docker Guide · Configuration Reference
- Symfony and Yii3, the other framework-mode recipes