Skip to main content

PHP on Miren

PHP isn't auto-detected, so you deploy it with a Dockerfile.miren. This guide uses FrankenPHP — a modern PHP application server that runs your app from a single image, no separate nginx + php-fpm wiring. It works for plain PHP and for frameworks like Laravel and Symfony.

Let your agent do this

Ask your AI coding agent to "set up this Laravel app on Miren" after installing the Miren agent skills. It adds the Dockerfile.miren, points the server at 0.0.0.0:$PORT, wires up environment variables, and deploys — using this page as its reference.

Does this source build need a Dockerfile?

Yes. Miren doesn't auto-detect PHP, so add a Dockerfile.miren to your project root. Miren builds from it instead of guessing the stack — see Using Dockerfile.miren.

Want native support?

Miren auto-detects and builds common stacks (Python, Node, Bun, Go, Ruby, Rust) without a Dockerfile. This language isn't one of them yet — if you'd like first-class support, request it.

The Dockerfile

Create Dockerfile.miren in your project root. FrankenPHP serves your public/ directory:

FROM dunglas/frankenphp:1-php8.3

WORKDIR /app
COPY . /app

EXPOSE 8080
CMD ["sh", "-c", "exec frankenphp php-server --listen 0.0.0.0:$PORT --root /app/public"]

For a Laravel or Composer app, install dependencies during the build:

FROM dunglas/frankenphp:1-php8.3

# Composer from its official pinned image — no piping a remote installer into php
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
RUN install-php-extensions pdo_pgsql pdo_mysql zip

WORKDIR /app
# Copy the manifests first so the install layer is deterministic and cache-friendly;
# committing composer.lock pins exact dependency versions.
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts --no-interaction

COPY . /app
RUN composer dump-autoload --optimize --no-interaction

EXPOSE 8080
CMD ["sh", "-c", "exec frankenphp php-server --listen 0.0.0.0:$PORT --root /app/public"]

(install-php-extensions ships with the FrankenPHP image.)

.dockerignore

.git
vendor

Deploy

The Dockerfile's CMD starts the app, so you don't need a Procfile or service command. The FrankenPHP base image exposes several HTTP, HTTPS, and admin ports; select this app's HTTP port explicitly.

Create .miren/app.toml naming your app and deploy from your project root:

name = "php-bench"

[services.web]
port = 8080
miren deploy

Environment variables

Set variables with miren env set-e for plain values, -s for secrets (masked in output and logs). Read them with getenv() or your framework's config (Laravel's env()):

miren env set -e APP_ENV=production
miren env set -s APP_KEY
miren env set -s DATABASE_URL

Generate a Laravel APP_KEY locally with php artisan key:generate --show. You can also declare variables in .miren/app.toml:

[[env]]
key = "APP_ENV"
value = "production"

Need a managed Postgres database? Add a miren-postgresql addon and Miren injects DATABASE_URL for you. See App Configuration — Environment Variables.

Agent quick reference

  • Detection: none — requires Dockerfile.miren
  • Base image: dunglas/frankenphp:1-php8.3; use install-php-extensions for pdo_pgsql, etc.
  • Composer: composer install --no-dev --optimize-autoloader during the build
  • Startup: inherited from the Dockerfile CMD; no Procfile or service command needed
  • Port: FrankenPHP --listen 0.0.0.0:$PORT
  • Env vars: miren env set -e/-s; read with getenv() / Laravel env()
  • Database: optional [addons.miren-postgresql] injects DATABASE_URL

Next steps