Self-Hosting
Deploy and upgrade ProBeya with the supported production Compose stack.
Supported deployment
ProBeya’s supported self-hosted path is the checked-in Docker Compose stack. Every production command must combine the base file and production overlay, in that order:
docker compose \
-f docker/docker-compose.yml \
-f docker/docker-compose.prod.yml \
<command>
docker/docker-compose.prod.yml is an overlay, not a standalone Compose
model. There is no separate api service: Next.js hosts the tRPC and HTTP
routes in web.
The hardened MVP excludes cron handlers, GraphQL, REST v1, the broad MCP tool
catalog, enterprise SSO/SAML, dedicated PPM/ingest/report/integration routes,
public sharing links, and global template galleries until they have
tenant-aware RLS contracts. Nginx returns 404 for the excluded API routes, and
the production profile fixes Authentik and Azure AD flags to false.
The hardened product shell advertises only Dashboard, Forms, Settings, and the dynamic Workspace → Project → Board tree. Settings are limited to profile, security, organization, members, and billing. The broader enterprise navigation remains available only in non-hardened development builds.
Requirements
| Resource | Minimum | Recommended production baseline |
|---|---|---|
| CPU | 2 cores | 4+ cores |
| RAM | 4 GB | 8+ GB |
| Storage | 20 GB SSD | 100+ GB persistent SSD plus off-host backups |
| OS | Linux | Current Ubuntu LTS or Debian stable |
| Docker | 24+ with Compose v2 | Current supported release |
| Network | Public IP/domain | TLS on ports 80 and 443 |
The bundled database is PostgreSQL 16.
Production topology
| Service | Internal port | Responsibility |
|---|---|---|
nginx | 80 / 443 | Public TLS entry point and hostname routing |
web | 8000 | Next.js UI, tRPC/HTTP API, outbox worker |
ws | 8003 | WebSocket collaboration |
docusaurus | 3001 | Generated documentation |
postgres | 5432 | PostgreSQL 16 |
redis | 6379 | Cache and pub/sub |
minio | 9000 / 9001 | Object API and console |
clamav | 3310 | Attachment scanning |
minio-init | one shot | Private bucket/application-user bootstrap |
ownership | one shot | Existing-schema ownership adoption |
schema | one shot | Schema, reviewed SQL, and RLS verification |
Only Nginx is publicly exposed. The production overlay binds infrastructure ports to loopback for operator access.
Configure
Clone and prepare the environment
git clone https://github.com/obeya-cloud/obeya.git
cd obeya
cp .env.example .env
Replace every placeholder. The critical wiring is:
POSTGRES_DB=probeya
POSTGRES_USER=probeya
POSTGRES_PASSWORD=<random-superuser-password>
PROBEYA_APP_DB_PASSWORD=<random-runtime-password>
PROBEYA_ADMIN_DB_PASSWORD=<different-schema-owner-password>
DATABASE_URL_DOCKER=postgresql://probeya_app:<encoded-runtime-password>@postgres:5432/probeya
DATABASE_ADMIN_URL_DOCKER=postgresql://probeya_admin:<encoded-schema-owner-password>@postgres:5432/probeya
REDIS_PASSWORD=<random-redis-password>
REDIS_URL_DOCKER=redis://:<encoded-redis-password>@redis:6379
MINIO_ROOT_USER=<random-root-access-key>
MINIO_ROOT_PASSWORD=<random-root-secret-key>
S3_ACCESS_KEY=<different-application-access-key>
S3_SECRET_KEY=<different-application-secret-key>
S3_ENDPOINT=http://minio:9000
S3_PUBLIC_URL=https://files.example.com
S3_BUCKET=probeya-uploads
AUTH_SECRET=<at-least-32-random-bytes>
WS_AUTH_SECRET=<different-random-secret>
CRON_SECRET=<different-random-secret>
RESEND_API_KEY=<production-resend-key>
EMAIL_FROM=ProBeya <[email protected]>
STRIPE_SECRET_KEY=<production-secret-key>
STRIPE_WEBHOOK_SECRET=<endpoint-signing-secret>
STRIPE_PRICE_STARTER=price_<starter-monthly-id>
STRIPE_PRICE_PRO=price_<pro-monthly-id>
NEXT_PUBLIC_APP_URL=https://probeya.example.com
NEXT_PUBLIC_WS_URL=wss://ws.example.com
URL-encode passwords embedded in connection strings. The runtime URL uses
probeya_app; only the schema one-shot service receives the
probeya_admin URL.
Install certificates and DNS
Provision the application, wildcard tenant, WebSocket, documentation, and
file-storage hostnames. Place certificates at the paths documented in
docker/README.md and expected by docker/nginx/probeya.conf.
Realtime clients connect directly to NEXT_PUBLIC_WS_URL; the application
hostname does not provide a /ws fallback.
Validate the merged model
docker compose \
-f docker/docker-compose.yml \
-f docker/docker-compose.prod.yml \
config --quiet
Database-first release sequence
Application images are not database administration images. The web image is
a minimal Next.js standalone artifact and does not contain the database
workspace.
Use this order for a new V2 database and every post-baseline V2 upgrade.
The one-time Phase 143 cutover does not upgrade MVP-v0 in place. Retain any required v0 backup, provision an empty PostgreSQL 16 database or volume, and run the V2 sequence against that target. The migrator rejects a non-empty schema without the immutable V2 ledger.
Stop writes and create a backup
docker compose \
-f docker/docker-compose.yml \
-f docker/docker-compose.prod.yml \
stop web ws
docker compose \
-f docker/docker-compose.yml \
-f docker/docker-compose.prod.yml \
exec -T postgres sh -ec \
'pg_dump --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" --format=custom' \
> probeya-before-upgrade.dump
Verify that the backup can be restored before continuing. On a brand-new, empty database, start PostgreSQL first; a pre-schema backup is optional only while no application data exists.
Adopt schema ownership
docker compose \
-f docker/docker-compose.yml \
-f docker/docker-compose.prod.yml \
--profile ownership run --rm ownership
This transactional, idempotent operation reconciles the runtime/admin roles
and transfers supported application objects to the non-superuser
probeya_admin owner.
Apply and verify schema/RLS
docker compose \
-f docker/docker-compose.yml \
-f docker/docker-compose.prod.yml \
--profile operations run --build --rm schema
The dedicated image runs db:migrate, apply-rls, and the fail-closed
check-rls catalog audit in that order. The migrator verifies immutable
journal, SQL, and snapshot hashes, requires an exact-prefix database ledger,
takes a PostgreSQL advisory lock, and commits all pending changes in one
transaction. --build prevents Compose from reusing a schema image cached
from the previous release.
Never run schema commands inside web or ws, and never give
DATABASE_ADMIN_URL_DOCKER to either runtime service.
Build and start
docker compose \
-f docker/docker-compose.yml \
-f docker/docker-compose.prod.yml \
build web ws docusaurus
docker compose \
-f docker/docker-compose.yml \
-f docker/docker-compose.prod.yml \
up -d
minio-init must complete successfully before Nginx and web start. It
keeps the bucket private and grants anonymous read only to avatar/logo
prefixes.
Verify
docker compose \
-f docker/docker-compose.yml \
-f docker/docker-compose.prod.yml \
ps
curl --fail https://probeya.example.com/api/health
curl --fail https://probeya.example.com/api/ready
curl --fail https://ws.example.com/health
curl --fail https://docs.example.com/healthz
| Endpoint | Meaning |
|---|---|
web /api/health | Process liveness |
web /api/ready | PostgreSQL and Redis readiness |
ws /health | WebSocket service health |
docusaurus /healthz | Documentation service health |
Backups and upgrades
Back up both PostgreSQL and S3_BUCKET, keep copies off-host, and regularly
prove restoration in an isolated environment. Attachment objects must remain
private during backup and recovery.
Before an upgrade:
git pull --ff-only origin main
pnpm mvp:gate
Then repeat the exact backup -> ownership -> schema -> build -> up -d
sequence above. If either one-shot database operation fails, keep application
writers stopped and correct/replay the operation or restore the backup.
Other orchestrators
The repository supplies Dockerfile.web, Dockerfile.ws, Dockerfile.docs,
and Dockerfile.schema. A different orchestrator must preserve the same role
separation, startup order, storage ACL bootstrap, and health checks. Supported
Helm charts and Kubernetes manifests are not currently included in this
repository.
Was this page helpful?