This post is a detailed technical report on transforming a FrankenPHP-based PHP web site into a standalone, client-side peer-to-peer (P2P) web application—turning a traditional client-server app into a decentralized network where every client instance acts as both server and client.
Key points
FrankenPHP capabilities
- Static executable packaging: FrankenPHP can bundle PHP code, the PHP interpreter, the embedded Caddy web server, and extensions into a single statically-linked binary (Linux, macOS, Windows via WSL). Users just run
./frankenphp php-serverto start a full production web service. - Docker packaging: Official
dunglas/frankenphpbase images enable simple Dockerfiles and integration with Compose/Kubernetes. - Worker mode: Instead of PHP-FPM's per-request re-initialization, the app is loaded into a long-lived worker process (similar to Node.js/Go), cutting latency to milliseconds and multiplying throughput—crucial for P2P nodes that need persistent state and background tasks.
- Lack of mature P2P libraries: Unlike Go/Rust/JS (libp2p, WebRTC), PHP has almost no production-grade P2P libraries; existing projects (e.g.,
kairos_php,openthc/p2p, PHP-WebRTC) are niche, poorly documented, or platform-limited. - Execution model limits: PHP's stateless request lifecycle and weak native async/concurrency support (despite Swoole/ReactPHP) hinder long-lived, concurrent network nodes.
- NAT traversal & node discovery: Implementing STUN/TURN/ICE, Kademlia DHT, bootstrap nodes, or mDNS in PHP is complex and hard to make performant.
- A typical Dockerfile starts
FROM dunglas/frankenphp:latest, installs Composer deps, copies code, exposes 80/443. - Docker Compose can run separate
webandipfs(Kubo) services on a shared bridge network; PHP reaches IPFS viahttp://ipfs:5001. Kubo can be tuned (IPFS_PROFILE=server) or locked to a private network (LIBP2P_FORCE_PNET=1, swarm key). - Single-container (supervisord running both daemons) vs. multi-container architectures are compared; multi-container is recommended for production due to separation of concerns, independent scaling, and robustness.
- Users launch the binary or
docker-compose up; the IPFS node obtains a Peer ID, connects to bootstrap peers, and participates in Kademlia DHT for discovery and Bitswap for block exchange. - Caddy serves a local web UI/API with automatic HTTPS for localhost; the browser talks to local APIs (e.g.,
/api/add_file) which relay to the P2P node. - Use cases: P2P file sharing via CIDs, distributed computing (task fan-out and aggregation), and fully decentralized applications (DApps).
- Frontend P2P with WebRTC (built-in ICE NAT traversal), with PHP reduced to a signaling server and optional persistence—currently the mainstream web P2P pattern.
- Rewriting the P2P core in Go + libp2p or Node.js (
simple-peer,hypercore-protocol) for demanding workloads. - Future possibilities: official FrankenPHP/libp2p integration, Caddy P2P modules, or community Composer packages wrapping FFI-based P2P calls.
Challenges of P2P in PHP
Packaging approach 1: static binary
Build flow: prepare the app withcomposer install --no-dev --optimize-autoloader, production env settings, cleaned sources (git archive / .gitattributes), optional embedded php.ini; then run the official build-static.sh, producing one frankenphp binary containing Caddy, PHP 8.4, extensions, and app code.Three P2P integration strategies are compared:
| Strategy | Pros | Cons |
| :--- | :--- | :--- |
| A: PHP-FFI to Go/C library (e.g., compile go-libp2p with go build -buildmode=c-shared, call via FFI::cdef()) | High performance, full libp2p features | Complex multi-language dev, debugging, cross-platform builds |
| B: Embed external node (IPFS Kubo) launched via proc_open()/pcntl, talk to its HTTP API (localhost:5001) | Mature and stable, low dev cost | Large binary, process-management overhead |
| C: Raw PHP socket extension | No dependencies, simple | No NAT traversal, no discovery, poor performance—only for LAN demos |