Skip to main content

Self-Hosting Superposition

Superposition runs as a single HTTP service that serves both the API and the admin UI. It stores global metadata and workspace data in PostgreSQL, and can use Redis as a shared cache when REDIS_URL is configured.

Use ghcr.io/juspay/superposition:latest for self-hosted deployments. The image ghcr.io/juspay/superposition-demo:latest is only for local evaluation with preloaded sample data.

What You Need

  • A PostgreSQL database that Superposition can create schemas and tables in.
  • A container runtime or process manager for the app.
  • psql for bootstrapping the global schema with superposition.sql.
  • A DNS name and TLS-terminating proxy for public deployments.

Quick Start

This example uses PostgreSQL for persistence, Redis for caching, and superposition.sql to initialize the global schema automatically on first boot.

services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: superposition
POSTGRES_PASSWORD: superposition
POSTGRES_DB: superposition
volumes:
- postgres_data:/var/lib/postgresql/data
- ./superposition.sql:/docker-entrypoint-initdb.d/001-superposition.sql:ro

redis:
image: redis:7
command: redis-server
restart: unless-stopped

superposition:
image: ghcr.io/juspay/superposition:latest
depends_on:
- postgres
- redis
ports:
- "8080:8080"
restart: unless-stopped
environment:
PORT: "8080"
APP_ENV: DEV
DB_USER: superposition
DB_PASSWORD: superposition
DB_HOST: postgres:5432
DB_NAME: superposition
SERVICE_PREFIX: ""
CAC_HOST: http://localhost:8080
API_HOSTNAME: http://localhost:8080
SUPERPOSITION_VERSION: self-hosted
WORKER_ID: "1"
RUST_LOG: info,superposition=info,service_utils=info
MAX_DB_CONNECTION_POOL_SIZE: "10"
ACTIX_KEEP_ALIVE: "120"
ALLOW_SAME_KEYS_OVERLAPPING_CTX: "true"
ALLOW_DIFF_KEYS_OVERLAPPING_CTX: "true"
ALLOW_SAME_KEYS_NON_OVERLAPPING_CTX: "true"
TENANT_MIDDLEWARE_EXCLUSION_LIST: "/health,/assets/favicon.ico,/pkg/frontend.js,/pkg,/pkg/frontend_bg.wasm,/pkg/tailwind.css,/pkg/style.css,/assets,/admin,/oidc/login,/admin/organisations,/organisations,/organisations/switch/{organisation_id},/"
AUTH_PROVIDER: DISABLED
AUTH_Z_PROVIDER: DISABLED
REDIS_URL: redis://redis:6379

volumes:
postgres_data:

Start the stack:

docker compose up -d
curl http://localhost:8080/health

Expected response:

Health is good :D

Runtime Architecture

ComponentRequiredNotes
Superposition appYesRuns the Rust API server and bundled admin UI on PORT, default 8080.
PostgreSQLYesStores organisations, workspace metadata, and one schema per workspace.
RedisNoEnables shared caching. If REDIS_URL is unset, the app reads from PostgreSQL directly.
OIDC providerRecommendedRequired when using AUTH_PROVIDER=OIDC or AUTH_PROVIDER=OIDC_SAAS.
Casbin policy DBOptionalRequired only when AUTH_Z_PROVIDER=CASBIN; it can use the same PostgreSQL instance.
AWS KMSRequired for APP_ENV=PRODIn production mode, secret values such as database passwords are read through KMS.

The app is mostly stateless. Multiple replicas can run behind a load balancer as long as they share the same PostgreSQL database and, if enabled, the same Redis instance.

First Organisation And Workspace

After the service is healthy, open the admin UI:

http://localhost:8080/admin/organisations

For an auth-disabled private deployment, you can also bootstrap through the API:

curl -X POST http://localhost:8080/superposition/organisations \
-H 'Content-Type: application/json' \
-d '{"name":"Acme","admin_email":"admin@example.com"}'

Use the returned organisation id to create a workspace:

curl -X POST http://localhost:8080/workspaces \
-H 'Content-Type: application/json' \
-H 'x-org-id: <organisation-id>' \
-d '{"workspace_name":"production","workspace_admin_email":"admin@example.com"}'

Workspace APIs require these headers:

  • x-org-id: organisation ID
  • x-workspace: workspace name

See Organisations and Workspaces for the data model.

Next Steps