-
Notifications
You must be signed in to change notification settings - Fork 0
Hosting Overview
Piro is designed to run on long-lived, stateful infrastructure. It is not compatible with serverless or ephemeral compute platforms such as AWS Lambda, Vercel Functions, or Cloudflare Workers.
Piro relies on several capabilities that serverless runtimes cannot provide:
- Persistent SignalR connections — Workers maintain a long-lived WebSocket connection to the API. Serverless functions are stateless and terminate after each invocation, which breaks the real-time worker communication model.
- Background services — The API runs hosted background services (check scheduler, alert dispatcher, ingester) that must remain alive between requests.
- Database migrations — Piro applies EF Core migrations on startup. Concurrent cold starts on serverless would create race conditions.
Use a standard container runtime, a VM, or a bare-metal server.
These are the minimums for a single-node installation with light traffic (< 50 checks, < 5 users):
| Component | Minimum |
|---|---|
| CPU | 1 vCPU |
| RAM | 512 MB |
| Disk | 2 GB |
| OS | Linux (any modern distro), macOS, Windows Server |
For production workloads with hundreds of checks and multiple workers, 2 vCPU / 2 GB RAM is a comfortable baseline.
Piro requires PostgreSQL 15 or later. The included docker-compose.yml bundles a PostgreSQL 16 container for convenience. For production, you have two options:
The simplest path — PostgreSQL runs as a container alongside the API. Data is stored in a named Docker volume (postgres_data). Back up this volume regularly.
Point Database__ConnectionString at any PostgreSQL instance — Amazon RDS, Google Cloud SQL, Azure Database for PostgreSQL, Supabase, Neon, or a self-managed server. Piro creates its own schema on first startup.
SQLite and MySQL are not supported.
A minimal production deployment has these components:
Internet → [Reverse Proxy / TLS] → nginx proxy (:80)
├── / → web (Next.js public status page, apps/web)
├── /admin/* → admin panel (Vite SPA, apps/admin, static)
├── /api/* → API (ASP.NET Core)
└── /hub/* → API (SignalR WebSocket)
↓
PostgreSQL
apps/web (the public status page) and apps/admin (the admin panel) are two distinct frontend applications — Next.js and a Vite SPA respectively — both served behind the bundled nginx proxy. Workers connect outbound to the API over SignalR (WebSocket); they do not need inbound ports open.
For multi-region monitoring, deploy additional workers from any cloud or on-prem location — they only need outbound HTTPS access to your API URL.
| Service | Default Port | Notes |
|---|---|---|
| nginx proxy | 80 |
The only port that should be exposed; put it behind a reverse proxy for TLS |
| web (Next.js) | internal | Not exposed directly — routed through the proxy |
| API | internal | Not exposed directly — routed through the proxy |
| PostgreSQL | 5432 |
Should never be publicly accessible |
Piro does not handle TLS termination itself. Use a reverse proxy in front of the nginx proxy:
- Caddy — automatic HTTPS with zero config
- nginx — traditional option; requires certbot or similar for cert management
- Traefik — popular with Docker environments; native Let's Encrypt support
- Cloudflare Tunnel — zero-trust option that avoids opening inbound ports entirely
Two volumes need to survive container restarts:
| Volume | Contents |
|---|---|
postgres_data |
All application data (services, checks, incidents, users, settings) |
api_uploads |
Uploaded logo and favicon files |
Back up both regularly. The api_uploads volume is small but contains branding assets that are not stored in the database.