DocsDevelopersSelf-Hosting

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"}
The 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.
For production, always provide explicit values for 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_libraries parameter 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_SECRET and ROOTCX_MASTER_KEY.
  • Set ROOTCX_BIND=1 for the Core to accept connections from outside the container.
  • Set ROOTCX_PUBLIC_URL to 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=true if you are using OIDC exclusively.