Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions docs/migrate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Migrating from PHP-FPM

FrankenPHP replaces both your web server (Nginx, Apache) and PHP-FPM with a single binary.
This guide covers a basic migration for a typical PHP application.

## Key Differences

| PHP-FPM setup | FrankenPHP equivalent |
|---|---|
| Nginx/Apache + PHP-FPM | Single `frankenphp` binary |
| `php-fpm.conf` pool settings | [`frankenphp` global option](config.md#caddyfile-config) |
| Nginx `server {}` block | `Caddyfile` site block |
| `php_value` / `php_admin_value` | [`php_ini` Caddyfile directive](config.md#php-config) |
| `pm = static` / `pm.max_children` | `num_threads` |
| `pm = dynamic` | [`max_threads auto`](performance.md#max_threads) |

## Step 1: Replace Your Web Server Config

A typical Nginx + PHP-FPM configuration:

```nginx
server {
listen 80;
server_name example.com;
root /var/www/app/public;
index index.php;

location / {
try_files $uri $uri/ /index.php$is_args$args;
}

location ~ \.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
```

Becomes a single `Caddyfile`:

```caddyfile
example.com {
root /var/www/app/public
php_server
}
```

That's it. The `php_server` directive handles PHP routing, `try_files`-like behavior, and static file serving.

## Step 2: Migrate PHP Configuration

Your existing `php.ini` works as-is. See [Configuration](config.md) for where to place it depending on your installation method.

You can also set directives directly in the `Caddyfile`:

```caddyfile
{
frankenphp {
php_ini memory_limit 256M
php_ini max_execution_time 30
}
}

example.com {
root /var/www/app/public
php_server
}
```

## Step 3: Adjust Pool Size

In PHP-FPM, you tune `pm.max_children` to control the number of worker processes.
In FrankenPHP, the equivalent is `num_threads`:

```caddyfile
{
frankenphp {
num_threads 16
}
}
```

By default, FrankenPHP starts 2 threads per CPU. For dynamic scaling similar to PHP-FPM's `pm = dynamic`:

```caddyfile
{
frankenphp {
num_threads 4
max_threads auto
}
}
```

## Step 4: Docker Migration

A typical PHP-FPM Docker setup using two containers (Nginx + PHP-FPM) can be replaced by a single container:

**Before:**

```yaml
services:
nginx:
image: nginx:1
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- .:/var/www/app
ports:
- "80:80"

php:
image: php:8.5-fpm
volumes:
- .:/var/www/app
```

**After:**

```yaml
services:
php:
image: dunglas/frankenphp:1-php8.5
volumes:
- .:/app/public
- caddy_data:/data
- caddy_config:/config
ports:
- "80:80"
- "443:443"
- "443:443/udp"

volumes:
caddy_data:
caddy_config:
```

If you need additional PHP extensions, see [Building Custom Docker Image](docker.md#how-to-install-more-php-extensions).

For framework-specific Docker setups, see [Symfony Docker](https://github.com/dunglas/symfony-docker) and [Laravel](laravel.md#docker).

## Step 5: Consider Worker Mode (Optional)

In [classic mode](classic.md), FrankenPHP works like PHP-FPM: each request boots the application from scratch. This is a safe starting point for migration.

For better performance, you can switch to [worker mode](worker.md), which boots your application once and keeps it in memory:

```caddyfile
example.com {
root /var/www/app/public
php_server {
root /var/www/app/public
worker index.php 4
}
}
```

> [!CAUTION]
>
> Worker mode keeps your application in memory between requests. Make sure your code does not rely on global state being reset between requests. Frameworks like [Symfony](worker.md#symfony-runtime), [Laravel](laravel.md#laravel-octane), and [API Platform](https://api-platform.com) have native support for this mode.

## What You Can Remove

After migrating, you no longer need:

- Nginx or Apache
- PHP-FPM (`php-fpm` service/process)
- FastCGI configuration
- Self-managed TLS certificates (Caddy handles them automatically)