Self-Hosting
By default, RootCX manages the infrastructure for you. Create a project on rootcx.com and a dedicated Core instance is provisioned automatically. This guide is for teams that need to run the Core on their own servers.
Quick start
# docker-compose.yml
services:
postgres:
image: ghcr.io/rootcx/postgresql:16-pgmq-cron
user: root
entrypoint: ["/pg-entrypoint.sh"]
environment:
POSTGRES_USER: rootcx
POSTGRES_PASSWORD: rootcx
POSTGRES_DB: rootcx
PGDATA: /data/pgdata
volumes:
- pgdata:/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U rootcx -d rootcx"]
interval: 2s
timeout: 5s
retries: 10
core:
image: ghcr.io/rootcx/core:latest
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgres://rootcx:rootcx@postgres:5432/rootcx
ports:
- "9100:9100"
volumes:
- data:/data
volumes:
pgdata:
data:
docker compose up -d
curl http://localhost:9100/health
# {"status":"ok"}
ghcr.io/rootcx/postgresql:16-pgmq-cron image is PostgreSQL 16 with pgmq and pg_cron pre-installed. Both extensions are required by the Core (pgmq for the job queue, pg_cron for scheduled jobs).Environment variables
DATABASE_URL is required. All others are optional for local development. For production, set explicit secrets.
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
(required) | PostgreSQL connection URL (e.g., postgres://user:pass@host:5432/db). |
ROOTCX_BIND |
not set | Set to any value to listen on 0.0.0.0 instead of 127.0.0.1. Required for remote access. |
ROOTCX_JWT_SECRET |
Auto-generated | String (min 32 characters) for HS256 JWT signing. Auto-generated in config/jwt.key if not set. |
ROOTCX_MASTER_KEY |
Auto-generated | Hex-encoded 32-byte key for AES-256-GCM encryption. Auto-generated in config/master.key if not set. |
ROOTCX_PUBLIC_URL |
not set | Public URL for OIDC callbacks and magic links (e.g., https://core.example.com). Falls back to ROOTCX_URL. |
ROOTCX_URL |
not set | Core instance URL (fallback for ROOTCX_PUBLIC_URL). |
ROOTCX_OIDC_ISSUER |
not set | OIDC identity provider URL. Seeds a provider with id "rootcx" on first boot. |
ROOTCX_OIDC_CLIENT_ID |
not set | Client ID for OIDC provider. Required if ROOTCX_OIDC_ISSUER is set. |
ROOTCX_OIDC_CLIENT_SECRET |
not set | Client Secret for OIDC provider. Required if ROOTCX_OIDC_ISSUER is set. Encrypted in vault. |
ROOTCX_DISABLE_PASSWORD_LOGIN |
not set | Set to true or 1 to force SSO-only. A first-user bypass allows initial registration. |
RUST_LOG |
info |
Log filter. Values: trace, debug, info, warn, error. |
ROOTCX_JWT_SECRET and ROOTCX_MASTER_KEY. Relying on auto-generated secrets means losing them if the container is recreated without persistent storage.Development database
For local development, expose the PostgreSQL port to connect directly:
# docker-compose.dev.yml
services:
postgres:
image: ghcr.io/rootcx/postgresql:16-pgmq-cron
user: root
entrypoint: ["/pg-entrypoint.sh"]
environment:
POSTGRES_USER: rootcx
POSTGRES_PASSWORD: rootcx
POSTGRES_DB: rootcx
PGDATA: /data/pgdata
volumes:
- pgdata-dev:/data
ports:
- "5480:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U rootcx -d rootcx"]
interval: 2s
timeout: 5s
retries: 10
volumes:
pgdata-dev:
psql -h 127.0.0.1 -p 5480 -U rootcx -d rootcx
Managed database
For teams using a managed database (RDS, Cloud SQL, etc.), point DATABASE_URL at your instance. Requirements:
- PostgreSQL 16+
- pgmq extension available (on AWS RDS: enable via
shared_preload_librariesparameter group) - pg_cron extension available (on AWS RDS: enable via
shared_preload_libraries)
docker run -d \
--name rootcx-core \
-p 9100:9100 \
-e DATABASE_URL=postgres://user:pass@your-rds-host:5432/rootcx \
-e ROOTCX_BIND=1 \
-e ROOTCX_JWT_SECRET="your-secret-min-32-characters-here" \
-e ROOTCX_MASTER_KEY="hex-encoded-32-byte-key" \
ghcr.io/rootcx/core:latest
Health checks
Use these endpoints for load balancer or orchestrator health checks:
# Basic liveness (no auth required)
curl http://localhost:9100/health
# {"status":"ok"}
# Full status with subsystem details (no auth required)
curl http://localhost:9100/health?full=true
# {"status":"ok","memory":{...},"cpu":{...},"disk":{...},"database":{"reachable":true}}
Connect the CLI
Once your self-hosted Core is running:
rootcx auth login http://localhost:9100
The CLI detects the auth mode and prompts accordingly. After login, all commands (rootcx deploy, rootcx agents, etc.) target your self-hosted instance.
Production checklist
- Set explicit
ROOTCX_JWT_SECRETandROOTCX_MASTER_KEY. - Set
ROOTCX_BIND=1for the Core to accept connections from outside the container. - Set
ROOTCX_PUBLIC_URLto your public-facing URL (required for OIDC callbacks and magic links). - Use persistent volumes for
/data(contains worker code, frontend assets, auto-generated keys). - Configure your reverse proxy (nginx, Caddy, etc.) to forward to port 9100.
- Use TLS termination at the reverse proxy level.
- Set
ROOTCX_DISABLE_PASSWORD_LOGIN=trueif you are using OIDC exclusively.