-
Notifications
You must be signed in to change notification settings - Fork 0
Hosting Docker
This page covers a full production-grade Docker Compose deployment of Piro.
For a quick local setup in under 5 minutes, see Getting Started → Docker.
- Docker 24+
- Docker Compose v2
- A domain or subdomain pointed at your server
- A TLS-terminating reverse proxy in front (Caddy, nginx, Cloudflare Tunnel, etc.)
Piro ships as four containers managed by a single Compose file:
Browser
└── Your TLS proxy (Caddy / nginx / Cloudflare)
└── Piro Nginx proxy :80
├── / → web (Next.js status page)
├── /admin/* → static admin SPA (built into proxy image)
├── /api/* → api (ASP.NET Core)
├── /hub/* → api (SignalR WebSocket)
└── /uploads/* → api (file serving)
Internal Docker network (no external ports except :80):
api :8080
web :3000
db :5432
Only the proxy container exposes port 80. All other services communicate over the internal Docker network.
| Image | Platform |
|---|---|
ghcr.io/heva-co/piro-proxy |
linux/amd64 |
ghcr.io/heva-co/piro-web |
linux/amd64 |
ghcr.io/heva-co/piro-api |
linux/amd64, linux/arm64
|
Images are published to GitHub Container Registry on each tagged release.
The repository ships with docker-compose.yml for production (uses GHCR images) and docker-compose.local.yml for local source builds.
For production, you only need docker-compose.yml and a .env file:
curl -O https://raw.githubusercontent.com/Heva-Co/piro/main/docker-compose.yml
curl -O https://raw.githubusercontent.com/Heva-Co/piro/main/.env.example
cp .env.example .envEdit .env:
# Image version — pin to a specific release or use latest
PIRO_VERSION=latest
# Required
POSTGRES_PASSWORD=a_strong_random_password
JWT_SECRET=a_long_random_string_at_least_32_chars
# Optional
ACCESS_TOKEN_EXPIRY_MINUTES=60
REFRESH_TOKEN_EXPIRY_DAYS=30Start the stack:
docker compose pull
docker compose up -d- Visit http://your-server/admin/ — you will be redirected to the setup wizard automatically if the database is empty
- Create your owner account and fill in site configuration (name, URL, email settings, etc.)
- Sign in at http://your-server/admin/auth/sign-in
All configuration (site name, SMTP, OIDC/SSO, etc.) lives in the database and is managed through the admin panel. No env vars are needed beyond what's in
.env.
The proxy container listens on port 80 (plain HTTP). TLS should be terminated by whatever sits in front of it.
status.example.com {
reverse_proxy localhost:80
}Caddy handles certificate provisioning automatically via Let's Encrypt.
server {
listen 443 ssl;
server_name status.example.com;
ssl_certificate /etc/letsencrypt/live/status.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/status.example.com/privkey.pem;
location / {
proxy_pass http://localhost:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Required for SignalR WebSocket (/hub/)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 80;
server_name status.example.com;
return 301 https://$host$request_uri;
}Point the tunnel to http://localhost:80. Cloudflare handles TLS. No cert management required.
| Volume | Mount | Description |
|---|---|---|
postgres_data |
/var/lib/postgresql/data |
All database data |
api_uploads |
/app/wwwroot/uploads |
Uploaded logos and favicons |
Both volumes are created automatically. Include them in your backup strategy.
docker compose pull
docker compose up -dDatabase migrations run automatically on API startup. No manual migration steps are needed.
To pin to a specific version, set PIRO_VERSION in .env:
PIRO_VERSION=v0.2.0-
JWT_SECRETis a long random string (≥ 32 chars) -
POSTGRES_PASSWORDis not the default value - TLS is terminated by a reverse proxy — do not expose port 80 directly to the internet
-
postgres_datavolume is backed up regularly -
api_uploadsvolume is backed up regularly - Image versions are pinned in
.envfor reproducible deployments