Magic Containers on Bunny
Magic Containers are bunny.net's edge container runtime. Your app runs as one pod on bare metal, sandboxed by gVisor, restricted to linux/amd64. Containers in the pod share localhost. The pod autoscales on CPU; there are no persistent volumes for stateless apps, and the pod disk is ephemeral — anything written to it disappears on recycle. Magic Containers runs the HTTP app + the first-run installer only; the queue worker, Reverb, and scheduler are self-hosted always-on processes (compose / k8s / VPS), not CaaS sidecars (ADR 0009). CaaS that scales to zero or idles is the wrong host for a long-lived daemon.
gVisor
gVisor is a user-space kernel sandbox that intercepts a subset of syscalls. Standard PHP apps — including FrankenPHP — run unmodified. Anything that depends on exotic syscalls or raw kernel access will not.
Statelessness doctrine
Because the pod disk is ephemeral, every piece of state moves off the pod:
- Sessions, cache, and queue table live in Bunny Database (libSQL,
databasedriver). The queue worker is not on the pod — it is a self-hosted always-on process (ADR 0009). On CaaS, setQUEUE_CONNECTION=sync(no worker) or point at a vendor queue (redis/sqs) per Laravel queue docs. - User uploads live in Bunny Storage (S3-compatible).
- Passport signing keys live in env, not in
storage/oauth-*.key.
This is why the first-run installer uses a DB lock (installer_locks), not a file: a file lock would not survive a pod recycle, and would not be visible to the next replica.
Bunny Database
Managed libSQL, globally replicated, currently Public Preview and free with a 1 GB per-DB cap. Reach it at libsql://[id].lite.bunnydb.net. Replication has a ~10s window and no read-your-writes on replicas — fine for template-scale apps.
Bunny Storage
S3-compatible object storage. The S3 API flag is set only at zone creation — you cannot retrofit it onto an existing zone. There are no ACLs and no lifecycle rules; set retain_visibility=false so copies do not call GetObjectAcl. Env map: zone name → AWS_ACCESS_KEY_ID + AWS_BUCKET, zone password → AWS_SECRET_ACCESS_KEY, region code → AWS_DEFAULT_REGION, AWS_ENDPOINT=https://{region}-s3.storage.bunnycdn.com, pull zone → AWS_URL.
CDN and WebSockets
Sticky sessions (a CDN-endpoint option that pins a client to one pod) are not needed when sessions live in Bunny DB — any pod can serve any request. For Reverb, enable CDN WebSockets on the pull zone in front of the pod. It is off until you turn it on, with a default of 500 concurrent connections.
Build and push
docker compose -f docker-compose.build.yml build
The default build target is linux/amd64 — the only arch Magic Containers accept. Push only with your own registry user:
IMAGE_NAME=<registry-user>/qcentic-edge-template:latest \
docker compose -f docker-compose.build.yml build --push
Architecture
flowchart LR
Client[Client browser] --> CDN[Bunny CDN pull zone]
CDN --> Pod[Magic Container pod]
subgraph Pod[Pod - linux/amd64, gVisor]
App[FrankenPHP app :8080]
end
App --> BunnyDB[(Bunny Database libSQL)]
App --> BunnyS3[(Bunny Storage S3)]
SelfHost[Self-hosted VPS / k8s] -.queue / reverb / scheduler.-> BunnyDB
CDN -.optional CDN WebSockets.-> SelfHost