A serverless PHP platform that runs each tenant's application inside its own Firecracker microVM with FrankenPHP. VMs boot in ~1.2 seconds and are fully isolated at the hardware level.
βββββββββββββββββββββββββββββββββββββββ
β MANAGEMENT PLANE β
β Laravel 12 + React + Inertia β
β https://phpless.digitalno.de β
ββββββββββββββββ¬βββββββββββββββββββββββ
β Unix socket
ββββββββββββββββΌβββββββββββββββββββββββ
β CONTROL PLANE β
β Go daemon (phpless-manager) β
β /var/fc/manager.sock β
βββββ¬βββββββββββ¬βββββββββββ¬ββββββββββββ
β β β
βββββΌββββ βββββΌββββ βββββΌββββ
β VM 1 β β VM 2 β β VM N β
βFrank- β βFrank- β βFrank- β
βenPHP β βenPHP β βenPHP β
βββββββββ βββββββββ βββββββββ
EXECUTION PLANE
- Server: Bare-metal with KVM support (tested on Hetzner AX41-NVMe)
- OS: Ubuntu 24.04
- Local machine: macOS or Linux with SSH access to the server
- Go 1.23+ (local, for cross-compiling the manager)
- Node.js 20+ and npm (local, for building frontend assets)
- PHP 8.2+ and Composer (local, for panel development)
phpless/
βββ panel/ # Laravel 12 management panel (React + Inertia + shadcn/ui)
βββ phpless-manager/ # Go VM orchestration daemon
βββ rootfs/ # Firecracker microVM base filesystem (init, Caddyfile, php.ini)
βββ configs/ # Server config templates (systemd, Caddy, PHP-FPM)
βββ scripts/ # Setup and deployment scripts
βββ test-app/ # Sample PHP app for testing
βββ Makefile # Build and deploy targets
βββ GUIDE.md # Detailed implementation walkthrough
SSH into a fresh Ubuntu 24.04 bare-metal server and run:
# On your local machine
scp scripts/server-setup.sh root@<SERVER_IP>:/tmp/
ssh root@<SERVER_IP> 'bash /tmp/server-setup.sh'This installs: Firecracker 1.10.1, Go 1.23.5, PHP 8.4, Caddy 2.10, and all system dependencies. It also enables KVM, configures UFW, and sets up IP forwarding.
The rootfs is a minimal Debian bookworm image with FrankenPHP baked in:
# On the server
bash scripts/build-rootfs.shThis creates:
/srv/firecracker/base/rootfs-base.ext4(512 MB ext4 image)/srv/firecracker/base/kernel/vmlinux.bin(Linux 4.14.174)
Important: The 4.14 kernel is required. Linux 5.10 kernels fail with
virtio_blk: probe of virtio0 failedon Firecracker 1.10.1.
The Go daemon manages VM lifecycle, networking, and code deployment:
# From project root (local machine)
make deployOr manually:
cd phpless-manager
GOOS=linux GOARCH=amd64 go build -o bin/phpless-manager ./cmd/manager/
scp bin/phpless-manager root@<SERVER_IP>:/usr/local/bin/phpless-managerInstall the systemd service on the server:
# On the server
cp /var/www/phpless/configs/phpless-manager.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now phpless-managerVerify it's running:
curl -sf --unix-socket /var/fc/manager.sock http://localhost/health
# {"status":"ok","total_vms":0,"running_vms":0}The panel is a Laravel 12 app with React/Inertia frontend:
# From project root (local machine)
bash scripts/deploy-panel.shThis script:
- Builds frontend assets locally (
npm run build) - Installs server-side dependencies (PHP-FPM, Node, Composer)
- Rsyncs panel files to the server
- Runs
composer install,artisan migrate, route/view caching - Deploys PHP-FPM pool, queue worker, and Caddy configs
- Restarts all services
# On the server
cd /var/www/phpless/panel
php artisan tinker$user = \App\Models\User::create([
'name' => 'Admin',
'email' => 'admin@example.com',
'password' => bcrypt('your-password'),
'email_verified_at' => now(),
]);
$team = \App\Models\Team::create([
'name' => 'Default',
'slug' => 'default',
'owner_id' => $user->id,
]);
$team->users()->attach($user->id, ['role' => 'owner']);
$user->update(['current_team_id' => $team->id]);The panel uses a .env file. Copy the production template:
cp panel/.env.production panel/.env
php artisan key:generateKey settings:
| Variable | Description | Default |
|---|---|---|
PHPLESS_MANAGER_SOCKET |
Path to Go manager Unix socket | /var/fc/manager.sock |
PHPLESS_DOMAIN |
Base domain for app subdomains | phpless.digitalno.de |
PHPLESS_CADDYFILE_PATH |
Path to Caddy config file | /etc/caddy/Caddyfile |
PHPLESS_BUILDS_DIR |
Directory for app build artifacts | /var/www/phpless/builds |
PHPLESS_LOG_DIR |
Directory for per-app access logs | /var/log/phpless/apps |
The Go manager accepts these flags (see configs/phpless-manager.service):
| Flag | Description | Default |
|---|---|---|
--socket |
Unix socket path | /var/fc/manager.sock |
--bridge |
Linux bridge name | br-phpless |
--bridge-cidr |
Bridge network CIDR | 10.0.0.1/16 |
--kernel |
Path to Linux kernel | (required) |
--base-ext4 |
Path to base rootfs image | (required) |
--tenant-dir |
Per-tenant VM data directory | /srv/firecracker/tenants |
--socket-dir |
Firecracker socket directory | /srv/firecracker/sockets |
cd panel
# Install dependencies
composer install
npm install
# Run dev servers
php artisan serve
npm run devThe frontend uses React 19, TypeScript, Tailwind CSS 4, and shadcn/ui components.
cd phpless-manager
go build ./cmd/manager/
go test ./...| Target | Description |
|---|---|
make build |
Cross-compile Go manager for Linux amd64 |
make deploy |
Build + rsync everything to server |
make panel-deploy |
Deploy panel only |
make setup |
Run server setup script |
make rootfs |
Build base rootfs on server |
make test-vm |
Boot a test VM |
make benchmark |
Run performance benchmarks |
make validate |
Run validation checklist |
- User creates an app via the panel UI
- Panel calls the Go manager over a Unix socket to create a Firecracker VM
- VM boots with the base rootfs (Debian + FrankenPHP) in ~1.2 seconds
- User writes/deploys code β the manager mounts the rootfs and rsyncs app files +
.envinto/app/ - Caddy on the host routes
<slug>.phpless.digitalno.deto the VM's internal IP - Environment variables are merged (team-level + app-level, app overrides) and injected as
/app/.env
VMs are child processes of the Go manager β they die when the manager stops. The manager's systemd service includes an ExecStartPost that runs php artisan app:restore-vms after every (re)start. This recreates all VMs, redeploys code with environment variables, and regenerates Caddy routes. Apps survive server reboots and manager binary deploys automatically.
- FrankenPHP (not PHP-FPM) inside VMs β single static binary, no separate web server needed
- Overlay filesystem support for copy-on-write deployments (optional, per-VM)
- SQLite for the panel database β simple, no external DB server needed
- Unix socket for manager communication β no network exposure, no auth needed
| Component | Path |
|---|---|
| Kernel | /srv/firecracker/base/kernel/vmlinux.bin |
| Base rootfs | /srv/firecracker/base/rootfs-base.ext4 |
| Rootfs contents | /srv/firecracker/base/rootfs/ |
| VM Manager binary | /usr/local/bin/phpless-manager |
| Manager socket | /var/fc/manager.sock |
| Caddy config | /etc/caddy/Caddyfile |
| Panel | /var/www/phpless/panel/ |
| Panel database | /var/www/phpless/panel/database/database.sqlite |
| PHP-FPM pool | /etc/php/8.4/fpm/pool.d/phpless.conf |
| App builds | /var/www/phpless/builds/ |
| App access logs | /var/log/phpless/apps/ |
| Tenant VM data | /srv/firecracker/tenants/ |
- VM persistence: VMs die when the manager restarts. The
ExecStartPostin the systemd service handles auto-restore, but expect a brief window (~10-20s) where apps are unavailable during a manager restart. - Entropy: FrankenPHP (Go-based) blocks on
getrandom()in the 4.14 kernel. The rootfs init script runs a customadd_entropybinary to seed the kernel RNG before starting FrankenPHP. - Init script: Never use
set -e(mount commands may fail if already mounted) or2>/dev/nullbefore/devis ready. - File uploads to VM: Always use
scpfor the init script, never SSH heredocs (encoding issues causeENOEXEC). - FrankenPHP Caddyfile: The bare
frankenphpdirective (no braces) in the global block is required forphp_serverto work. - Deploy script: Do NOT overwrite
/etc/caddy/Caddyfilewith a static template β per-app routing blocks are generated dynamically from the database byCaddyConfigManager.
Proprietary. All rights reserved.