-
Notifications
You must be signed in to change notification settings - Fork 0
Self Hosting
This guide covers deploying Piro on your own infrastructure using Docker Compose, plus the full environment variable reference.
- Docker 24+
- Docker Compose v2
- A PostgreSQL 17 database (included in the Compose file, or bring your own)
git clone https://github.com/Heva-Co/piro.git
cd pirocp .env.example .envEdit .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.comdocker 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.
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/.
Go to Configuration → Workers → Register Worker, generate a token, and set it as WORKER_TOKEN in your .env, then restart:
docker compose up -d workerDeploy 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:latestOr download a self-contained binary from the Releases page — no runtime required.
docker compose pull
docker compose up -dMigrations run automatically on each startup.
| 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.
| Variable | Required | Default | Description |
|---|---|---|---|
Database__ConnectionString |
✅ | — | PostgreSQL connection string. Example: Host=db;Database=piro;Username=piro;Password=secret
|
| 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. |
| 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. |
| 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. |
| 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. |
| 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.
-
JWT_SECRETis a long random string (≥ 32 chars), not the default -
POSTGRES_PASSWORDis changed from the default - The
proxycontainer is behind a TLS-terminating reverse proxy (Caddy, external nginx, Traefik) - The
apiandwebcontainers are not exposed directly — onlyproxy(port 80) should be reachable -
api_uploadsvolume is backed up (contains uploaded logo/favicon) -
postgres_datavolume is backed up regularly - Workers use dedicated tokens (one token per worker deployment)