Skip to content

Hosting Overview

Arael Espinosa edited this page Jul 13, 2026 · 2 revisions

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.


Why not serverless?

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.


Minimum Requirements

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.


Database

Piro requires PostgreSQL 15 or later. The included docker-compose.yml bundles a PostgreSQL 16 container for convenience. For production, you have two options:

Bundled (Compose)

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.

External (managed or self-hosted)

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.


Architecture

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.


Ports

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

TLS / HTTPS

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

Data Persistence

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.

Clone this wiki locally