Self-hosting (Docker)
PLUR Enterprise runs in Docker Compose behind Caddy; works on public internet (auto-TLS) or intranet (your own cert). This page is the shape of the deployment — the full installation guide ships with the deployment bundle.
Prerequisites
Section titled “Prerequisites”- Linux server (Ubuntu 22.04+ recommended)
- Docker Engine 24+ and Docker Compose v2
- ≥ 2 vCPU, ≥ 4 GB RAM, ≥ 20 GB disk
- Ports 80 and 443 reachable from your users
- A hostname or IP (public domain or internal)
- Container registry access (images are pulled from GHCR by default; a custom/air-gapped registry is supported via
PLUR_REGISTRY)
1. Get the deployment bundle
Section titled “1. Get the deployment bundle”The bundle contains:
docker-compose.prod.ymlCaddyfile.example.env.exampledocker/ # DB init scripts (extensions + role bootstrap)2. Configure
Section titled “2. Configure”cp .env.example .env$EDITOR .envRequired values:
| Variable | Example | Notes |
|---|---|---|
PLUR_BASE_URL | https://plur.your-org.com | Public-facing URL |
DATABASE_URL | postgresql://plur_app:...@db:5432/plur_enterprise | Runtime DSN — least-privilege plur_app role |
MIGRATION_DATABASE_URL | postgresql://plur_admin:...@db:5432/plur_enterprise | Admin DSN — used only by the startup migration step |
JWT_SECRET | <openssl rand -base64 32> | Token signing |
ORG_ID / ORG_NAME | myorg / "My Organization" | Org identifier + display name |
ADMIN_USERS | alice,bob | IdP usernames granted the admin role |
PLUR_OIDC_ENCRYPTION_KEY | <openssl rand -base64 32> | Encrypts SSO secrets + webhook signing keys at rest |
AUDIT_HMAC_KEY | <openssl rand -base64 32> | Signs the tamper-evident audit chain |
POSTGRES_ADMIN_PASSWORD / POSTGRES_APP_PASSWORD | — | Must match the two DSNs above |
PLUR_SMTP_* | optional | If unset, email runs in stub mode (logs to console) |
There is no local admin account: admins are IdP-authenticated users listed in ADMIN_USERS (fine-grained roles via ROLE_USERS).
Optional auth:
| Variable | Notes |
|---|---|
GITHUB_CLIENT_ID / _SECRET / GITHUB_ORGS | GitHub OAuth |
GITLAB_CLIENT_ID / _SECRET / GITLAB_URL | GitLab OAuth (self-hosted GitLab works) |
| OIDC / SAML | Configured from the /admin dashboard, not env vars |
3. Configure Caddy
Section titled “3. Configure Caddy”cp Caddyfile.example Caddyfile$EDITOR CaddyfileFor public domains, the default config gets free TLS from Let’s Encrypt. For intranet, swap the auto-TLS for tls /path/to/cert.pem /path/to/key.pem.
4. Start
Section titled “4. Start”docker compose -f docker-compose.prod.yml up -dWhat this starts:
db— Postgres 16 with Apache AGE + pgvector (prebuilt image).app— the Node server.caddy— TLS termination + reverse proxy.
First start takes 1–2 minutes: extensions load, DB roles are created, and the app runs all migrations (via MIGRATION_DATABASE_URL; runtime traffic then uses least-privilege DATABASE_URL).
5. Verify
Section titled “5. Verify”curl https://plur.your-org.com/healthExpected (JSON):
{ "status": "ok", "version": "0.x.y", "uptime_seconds": 12}/status serves a human-readable HTML status page.
Log in at /admin via your configured IdP; users listed in ADMIN_USERS get admin access.
6. Configure auth
Section titled “6. Configure auth”Add at least one IdP for browser users (OIDC/SAML/GitHub OAuth/GitLab OAuth) from the admin dashboard. For agents (Claude Code, Cursor, OpenClaw), issue API keys from /me/api-keys — see Authentication for the right method per client.
Operational tasks
Section titled “Operational tasks”Backups
Section titled “Backups”Daily GPG-encrypted Postgres dumps, driven by the BACKUP_* env vars (BACKUP_ENCRYPTION_KEY, retention by days and count). Off-host push to a separate machine via rsync-over-SSH (or restic/object storage) is documented in the bundled setup guide. Store the encryption passphrase in a vault — without it, backups are unrecoverable.
Updates
Section titled “Updates”cd /opt/plur-enterprisedocker compose -f docker-compose.prod.yml pulldocker compose -f docker-compose.prod.yml up -dMigrations are idempotent and run automatically on container start. Upgrades require zero manual steps by design.
Postgres role hardening
Section titled “Postgres role hardening”Two roles, two DSNs: plur_admin (bootstrap + privileged migrations) and plur_app (least-privilege runtime). The compose stack creates both automatically. See Postgres & backups.
Structured JSON via pino. Tail:
docker compose -f docker-compose.prod.yml logs -f appAudit events go to the append-only audit_log table; query via the admin dashboard or directly with SQL.