A lightning-fast Go application server that runs BareMetalPHP via a persistent pool of PHP workers — similar to FrankenPHP and RoadRunner, but lightweight, transparent, and tailored for your framework.
- ⚡ Persistent PHP workers — no cold boots
- 🧵 Fast + slow worker pools with request classification
- 📁 Static file serving (Go handles assets before PHP)
- 🔥 Hot Reload (automatic worker restart on PHP file changes)
- 🧩 Config file support (
go_appserver.json) - 🛠 Automatic project root detection
- 🚦 Graceful worker recycling (timeouts + max request count)
- 🔒 Binary protocol between Go and PHP workers
go mod init my-appgo get github.com/google/uuid
go get github.com/fsnotify/fsnotifyExpected directory layout:
my-app/
├── go.mod
├── cmd/
│ └── server/
│ ├── main.go
│ └── config.go
├── server/
│ ├── worker.go
│ ├── pool.go
│ └── server.go
├── php/
│ ├── worker.php
│ ├── bridge.php
│ └── bootstrap_app.php
├── routes/
│ └── web.php
└── public/
go_appserver.json
{
"fast_workers": 4,
"slow_workers": 2,
"hot_reload": true,
"request_timeout_ms": 10000,
"max_requests_per_worker": 1000,
"static": [
{ "prefix": "/assets/", "dir": "public/assets" },
{ "prefix": "/css/", "dir": "public/css" },
{ "prefix": "/js/", "dir": "public/js" }
]
}If the file is missing, defaults are automatically applied.
From project root:
go run ./cmd/serverImportant:
Do not rungo run cmd/server/main.go— Go will ignoreconfig.goand break the build.
Instead run the whole package:
go run ./cmd/server
Server will start on:
http://localhost:8080
┌──────────────┐
│ Go HTTP Host │
└──────┬───────┘
│
├─► tryServeStatic() → serves /assets/, /css/, /js/ directly
│
└─► BuildPayload() → JSON message → WorkerPool
│
▼
┌────────────────────┐
│ PHP Worker (hot) │ ← Boots BareMetalPHP ONCE
│ handle_bridge_req │
└────────────────────┘
│
▼
JSON response → Go → Browser
Go = router + static host + supervisor
PHP = long-running application kernel
Enable via config:
{ "hot_reload": true }or environment variable:
export GO_PHP_HOT_RELOAD=1Hot reload watches for changes in:
php/routes/
When a file changes → workers marked dead → automatically restarted on next request.
my-app/
├── cmd/server
│ ├── main.go
│ └── config.go
├── server
│ ├── worker.go
│ ├── pool.go
│ └── server.go
├── php
│ ├── bootstrap_app.php
│ ├── bridge.php
│ └── worker.php
├── routes
│ └── web.php
├── public
│ └── index.html
└── go_appserver.json
You're running:
go run cmd/server/main.go
Correct:
go run ./cmd/server
Go ignores files in the package if you run a single file.
This means:
- Worker crashed
- Worker hit max request limit
- Hot reload replaced workers
The pool recovers automatically.
Check:
- Your
prefixroutes public/existsgo_appserver.jsonhas correct directory names- No directory traversal (
../) errors
{
"fast_workers": 12,
"slow_workers": 4,
"hot_reload": false,
"request_timeout_ms": 30000,
"max_requests_per_worker": 5000,
"static": [
{ "prefix": "/", "dir": "public" },
{ "prefix": "/assets/", "dir": "public/assets" }
]
}- WebSocket support
- HTTP/2 + QUIC
- Native TLS termination
- Worker dashboard + metrics endpoint
- Fiber-style async adapters
- Zero-downtime worker rotation
MIT License.
You can do whatever you want with this code :)