Required environment
PYLON_CORS_ORIGIN=*in non-dev mode is refusedPYLON_DEV_MODE=truewithPYLON_ADMIN_TOKENunset is refused/api/__test__/resetis disabled unless dev + in-memory + loopback
Ports
Pylon uses up to four adjacent ports, depending on which transports you enable:| Port | Purpose | When |
|---|---|---|
PYLON_PORT (default 4321) | HTTP | Always |
PYLON_PORT + 1 | WebSocket | When sync engine uses transport: websocket (default) |
PYLON_PORT + 2 | SSE (/events) | When clients use transport: sse fallback |
PYLON_PORT + 3 | Realtime shards | When apps use useShard / PylonRealtime |
PYLON_WS_URL so clients know where to connect.
Shape 1: single VPS (SSH + systemd)
Simplest and cheapest. One binary, one systemd unit, one reverse proxy.:443 → :4321 plus WebSocket upgrades for /ws → :4322, /events (SSE) → :4323, and shard WS → :4324. A sample nginx config ships in deploy/terraform/nginx.conf.
pylon backup /var/backups/pylon/$(date +%F) nightly. Test restore quarterly per the test at crates/runtime/tests/backup_restore.rs.
Caddy example
Shape 2: AWS ECS + Aurora (Terraform)
deploy/terraform/modules/pylon/ provisions:
- ECS Fargate service (0.25 vCPU, 512 MB) ~$9/mo
- Aurora Serverless v2 (0.5–2 ACU) ~$15/mo minimum
- ALB with TLS + WebSocket routing
- CloudFront CDN + Route53 DNS
--features postgres-live and DATABASE_URL=postgres://....
Shape 3: AWS via SST (TypeScript IaC)
For TypeScript-first infrastructure, SST v3 provisions the same AWS shape from a singlesst.config.ts. Same components (Aurora, Fargate, ALB, EFS, CloudFront), one CLI for deploys + secrets + per-PR preview environments.
Shape 4: Cloudflare Workers (edge, experimental)
crates/workers/ builds a WASM bundle (worker-build --release) that runs on Workers with a D1 binding. See crates/workers/README.md for current limitations. Scale-to-zero: idle apps cost $0. Cost rises with actual request volume. See Workers costs.
Realtime shards (tick-based sims) are not yet supported on Workers — they need persistent state that Workers-only can’t hold efficiently. Use shape 1 or 2 for game shards.
Shape 5: Pylon Cloud
Shape 6: local dev
PYLON_DEV_MODE=true defaults. Studio at /studio, hot-reload, permissive CORS. Not for production.
Health checks
GET /healthreturns 200 with{"status":"ok","uptime_seconds":N}GET /metricsreturns Prometheus text whenAccept: text/plainGET /readyzchecks DB connectivity
Graceful shutdown
SendSIGTERM. The server:
- Stops accepting new connections
- Lets in-flight HTTP requests finish (30s cap)
- Closes WS + SSE with a normal close frame
- Flushes the WAL
- Exits with 0
SIGTERM to the old one.
Native clients (iOS, macOS, Android)
Native clients hit the same HTTP + WebSocket endpoints as browsers, with two notes:- CORS doesn’t apply — native HTTP clients skip CORS entirely. Don’t worry about
PYLON_CORS_ORIGINfor them. - TLS is mandatory on iOS — App Transport Security rejects
ws://andhttp://to non-localhost hosts. Always usewss://andhttps://in production. Self-signed certs work in dev with an ATS exception inInfo.plist. - Background sessions — for large file uploads/downloads that should continue when the app is backgrounded, configure
URLSessionConfiguration.background(...)in your transport. See Swift SDK.
Scale-out
Single-process by design. For higher throughput:- Reads: cache + rely on the 4-connection read pool (already in)
- Writes: move to Postgres (
postgres-livefeature) - WS fanout: workers + Durable Objects; shape 3 amortizes edge
- Shards: run one process per game region; load-balance by match id
What about Docker?
deploy/docker-compose.yml.