Skip to content

Self-hosting (Docker)

Total time: ~30 minutes. PLUR Enterprise runs in Docker Compose behind Caddy; works on public internet (auto-TLS) or intranet (your own cert).

  • 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)
  • GitHub Container Registry access (provided by the PLUR team)

Create a GitHub Personal Access Token with read:packages scope at github.com/settings/tokens, then:

Terminal window
echo YOUR_GITHUB_TOKEN | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin
Terminal window
mkdir -p /opt/plur-enterprise && cd /opt/plur-enterprise

You need these files (released as a tarball or via the PLUR team):

docker-compose.prod.yml
Caddyfile.example
.env.example
docker/init.sql
docker/02-plur-app-role.sh
docker/02-plur-app-role.sql
Terminal window
cp .env.example .env
$EDITOR .env

Required values:

VariableExampleNotes
PLUR_PUBLIC_URLhttps://plur.your-org.comPublic-facing URL
PLUR_DATABASE_URLpostgresql://plur_app:...@postgres:5432/plurApp role only
PLUR_JWT_SECRET<openssl rand -hex 32>Token signing
PLUR_ADMIN_EMAILyou@your-org.comFirst admin
PLUR_ADMIN_PASSWORD<one-time>Forced to change at first login
PLUR_SMTP_*optionalIf unset, runs in stub mode (logs to console)

Optional auth (configure later from /admin/sso):

VariableNotes
PLUR_GITHUB_CLIENT_ID / _SECRETGitHub OAuth
PLUR_GITLAB_CLIENT_ID / _SECRET / _URLGitLab OAuth (self-hosted GitLab works)
PLUR_OIDC_*OIDC SSO — Google, Okta, Microsoft Entra
PLUR_SAML_*SAML 2.0 — older enterprise IdPs
Terminal window
cp Caddyfile.example Caddyfile
$EDITOR Caddyfile

For 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.

Terminal window
docker compose -f docker-compose.prod.yml up -d

What this starts:

  • postgres — Postgres 16 with Apache AGE.
  • plur-enterprise — the Node server.
  • caddy — TLS termination + reverse proxy.

First start runs migrations and creates the admin user from the env vars.

Terminal window
curl https://plur.your-org.com/status

Expected:

{
"status": "ok",
"version": "0.x.y",
"database": "ok",
"graph": "ok",
"uptime_s": 12
}

Log in to /admin with the email + password from .env. You’ll be forced to change the password and (optionally) enable 2FA on first login.

Go to /admin/sso and add at least one IdP for browser users (OIDC/SAML/GitHub OAuth/GitLab OAuth). For agents (Claude Code, Cursor, OpenClaw), issue API keys from /me/api-keys — see Authentication for the right method per client.

Daily Postgres dumps run inside the container — see docker/backup.sh. Push them off-host with PLUR_BACKUP_S3_URL or a sidecar.

Terminal window
cd /opt/plur-enterprise
docker compose pull
docker compose -f docker-compose.prod.yml up -d

Migrations are idempotent and run automatically on container start.

The compose stack creates two roles automatically: plur_test (bootstrap superuser, used only for extension install and role creation) and plur_app (least-privilege, used at runtime). The full grant list lives in docker/02-plur-app-role.sql. The runtime DSN should always point at plur_app, not plur_test. See Postgres & backups.

Structured JSON via pino. Tail:

Terminal window
docker compose logs -f plur-enterprise

Audit events go to the audit_log table; query via the admin dashboard or directly with SQL.

Full production deployment (auto-restart policies, off-host backup S3 push, monitoring exporters) is tracked in issue #79. The Docker Compose path described here is the documented baseline.