Skip to content

Self Hosting

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

Self-Hosting Guide

This guide covers deploying Piro on your own infrastructure using Docker Compose, plus the full environment variable reference.


Prerequisites

  • Docker 24+
  • Docker Compose v2
  • A PostgreSQL 17 database (included in the Compose file, or bring your own)

Quick Start

1. Clone the repository

git clone https://github.com/Heva-Co/piro.git
cd piro

2. Configure environment

cp .env.example .env

Edit .env and set at minimum:

POSTGRES_PASSWORD=your_secure_password
JWT_SECRET=replace_with_a_long_random_string_at_least_32_chars
WORKER_TOKEN=generate_after_first_login
ORIGIN=https://status.example.com

3. Start the stack

docker compose up -d
Service Port Description
proxy 80 nginx reverse proxy — routes / to web, /admin/* to the admin SPA, /api/* and /hub/* to the API
web internal Next.js public status page (apps/web)
api internal Piro REST API
worker Default monitoring worker
db 5432 PostgreSQL

The admin panel (apps/admin, a Vite SPA) is built as static assets and served by the proxy container under /admin/* — it is not a separate running service.

The API applies database migrations automatically on startup.

4. Complete setup

Open http://localhost/admin/ and you'll be redirected to the Setup Wizard to create your owner account and configure the instance (general settings, account, and a required, verified email configuration). After that, sign in at http://localhost/admin/auth/sign-in. The public status page is at http://localhost/.

5. Register your worker token

Go to Configuration → Workers → Register Worker, generate a token, and set it as WORKER_TOKEN in your .env, then restart:

docker compose up -d worker

6. Add remote workers (optional)

Deploy additional workers from any region to get multi-region check coverage:

docker run -d --restart unless-stopped \
  -e PIRO_API_URL=https://your-api.example.com \
  -e PIRO_WORKER_TOKEN=<token> \
  -e PIRO_WORKER_REGION=eu-west \
  ghcr.io/heva-co/piro-worker:latest

Or download a self-contained binary from the Releases page — no runtime required.

Updating

docker compose pull
docker compose up -d

Migrations run automatically on each startup.


Docker Images

Image Tags
ghcr.io/heva-co/piro-api latest, v0.1.0, …
ghcr.io/heva-co/piro-worker latest, v0.1.0, …

Both images support linux/amd64 and linux/arm64.


Environment Variable Reference

Piro API

Database

Variable Required Default Description
Database__ConnectionString PostgreSQL connection string. Example: Host=db;Database=piro;Username=piro;Password=secret

Authentication

Variable Required Default Description
Auth__JwtSecret Secret used to sign JWT tokens. Must be at least 32 characters.
Auth__AccessTokenExpiryMinutes 60 Access token lifetime in minutes.
Auth__RefreshTokenExpiryDays 30 Refresh token lifetime in days.

Email (alert triggers)

Variable Required Default Description
Email__Host SMTP host.
Email__Port 587 SMTP port.
Email__UseSsl false Use SSL/TLS.
Email__Username SMTP username.
Email__Password SMTP password.
Email__From Sender address for alert emails.

Observability (optional)

Variable Required Default Description
Sentry__Dsn Sentry DSN for error tracking.
Telemetry__PostHogApiKey PostHog API key for anonymous usage analytics.
Telemetry__Enabled true Set to false to disable telemetry entirely.

Piro Worker

Variable Required Default Description
PIRO_API_URL Base URL of the Piro API. Example: http://api:8080
PIRO_WORKER_TOKEN Worker token from Configuration → Workers → Register Worker.
PIRO_WORKER_REGION default Region label attached to all check results from this worker.

Web (apps/web — Next.js public status page)

Variable Required Default Description
INTERNAL_API_URL Internal URL of the API (used for server-side requests). Example: http://api:8080
ORIGIN Public URL of the site. Used for CORS and redirects. Example: https://status.example.com

The admin panel (apps/admin) is a static Vite SPA built at image-build time and served by the proxy container — it has no runtime environment variables of its own; all its API calls go through /admin/api.


Production Checklist

  • JWT_SECRET is a long random string (≥ 32 chars), not the default
  • POSTGRES_PASSWORD is changed from the default
  • The proxy container is behind a TLS-terminating reverse proxy (Caddy, external nginx, Traefik)
  • The api and web containers are not exposed directly — only proxy (port 80) should be reachable
  • api_uploads volume is backed up (contains uploaded logo/favicon)
  • postgres_data volume is backed up regularly
  • Workers use dedicated tokens (one token per worker deployment)

Clone this wiki locally