Craft CMS on OxPHP

Craft CMS uses web/index.php as its front controller and serves control-panel resources from web/cpresources/. It runs in OxPHP's framework routing mode: existing static files, including the control-panel assets, are served from disk, and 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_ROOT=/var/www/html/web overrides the default …/public)
  • Extensions added: bcmath, gd, intl, pdo_mysql, mbstring, zip
  • Services: OxPHP + MySQL (8.4 LTS)
  • URL: http://localhost:8092 · control panel /admin · internal http://localhost:9093/health

Project layout

bash
mkdir -p craft-oxphp/src docker run --rm -v "$PWD/craft-oxphp/src":/app -w /app \ composer:2 create-project craftcms/craft . \ --ignore-platform-reqs --no-scripts --no-interaction --prefer-dist
Note

--ignore-platform-reqs --no-scripts is needed because the stock composer:2 image lacks bcmath/gd/intl; those are only required at runtime, and Craft's post-create setup wizard runs later, inside the built container.

Craft reads its database connection from the environment, so no .env editing is required beyond the keys the console writes.

Dockerfile and Compose

Both files below are shown; the Dockerfile lives at src/Dockerfile.

src/Dockerfile
ARG OXPHP_VERSION=0.13.0 ARG PHP_VERSION=8.5 ARG ALPINE_VERSION=3.23 # ── PHP base: Craft runtime extensions ──────────────────────── FROM php:${PHP_VERSION}-zts-alpine${ALPINE_VERSION} AS php-base RUN apk add --no-cache \ icu-libs libpng libjpeg-turbo freetype oniguruma libzip \ && apk add --no-cache --virtual .build-deps \ icu-dev libpng-dev libjpeg-turbo-dev freetype-dev oniguruma-dev libzip-dev \ && docker-php-ext-configure gd --with-jpeg --with-freetype \ && docker-php-ext-install -j"$(nproc)" bcmath gd intl pdo_mysql mbstring zip \ && 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 + Craft extensions ── 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 { \ echo "memory_limit=256M"; echo "upload_max_filesize=64M"; \ echo "post_max_size=64M"; echo "max_execution_time=120"; \ } > /usr/local/etc/php/conf.d/craft.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/web && 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"]

Install and first run

Craft is installed through its console, which lives in the same container:

bash
docker compose up -d --build docker compose exec app php craft setup/security-key # writes CRAFT_SECURITY_KEY docker compose exec app php craft setup/app-id # writes CRAFT_APP_ID docker compose exec app php craft install/craft \ --username=admin --email=admin@example.com --password=password \ --site-name="Craft OxPHP" --site-url='http://localhost:8092' --language=en-US

OxPHP notes

Note
  • Control-panel resources are static files. web/cpresources/<hash>/… exists on disk and framework mode serves it. Check that it returns 200; if it does not, the control panel renders unstyled.
  • Use MySQL 8.4 LTS rather than MySQL 9. Craft is verified against MySQL 8.x, and the 8.4 LTS image is the safe choice. MySQL 8.4 removed the --default-authentication-plugin server flag, so do not pass it.
  • Database settings (CRAFT_DB_*) come from the container environment, so they live in Compose.

Verify

bash
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8092/ # 200 curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8092/admin # 302 → login curl -s -o /dev/null -w "%{http_code}\n" http://localhost:9093/health # 200

Prompts for your agent

These prompts assume an agent with the OxPHP documentation MCP server connected. The agent reads the pages it needs itself.

prompt
Run my Craft CMS project on OxPHP locally. Read the `examples/cms/craft` page through the OxPHP documentation MCP server. Write the Dockerfile and the Compose file for this repository, with the extensions my installed plugins need on top of Craft's own, and give me the console sequence that installs it against a fresh database.
prompt
My Craft install on OxPHP fails at the database — it will not connect, or MySQL refuses to start. Read the `examples/cms/craft` page through the OxPHP documentation MCP server. Check my Compose file's MySQL image tag and server flags and my `CRAFT_DB_*` variables against what my `.env` still sets, and tell me which of them Craft is actually reading.

See also

Found a mistake? Report it →