Px/

Self-hosting

Run Permix in self-hosted mode — a single-tenant deployment with a single JWKS endpoint and no admin tenant management overhead.

Edit this page on GitHub

Self-hosted mode (MODE=selfhost) is the default operating mode. It is designed for teams that run their own infrastructure and have a single identity provider. There is no multi-tenancy — all resources, policies, and rules belong to one implicit tenant.

When to use self-hosted mode#

  • You have one identity provider (Keycloak, Okta, Azure AD, etc.)
  • You don't need per-tenant policy isolation
  • You want the simplest possible deployment — one service, one database, zero SaaS overhead

For multi-tenant SaaS deployments, see the SaaS mode documentation.

Quickstart with Docker Compose#

yaml
version: "3.9"
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: authz
      POSTGRES_PASSWORD: changeme
      POSTGRES_DB: authz
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U authz"]
      interval: 5s
      retries: 5

  authorization-service:
    image: ghcr.io/lhtuwrk/authorization-service:latest
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "8080:8080"
    environment:
      MODE: selfhost
      POSTGRES_HOST: postgres
      POSTGRES_USER: authz
      POSTGRES_PASSWORD: changeme
      POSTGRES_DB: authz
      OIDC_ENABLED: "true"
      AUTH_SERVER_URL: https://keycloak.example.com/realms/myrealm
      JWKS_URI: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs
      ROLES_CLAIM: realm_access.roles
      DOMAIN_CLAIM: dom

volumes:
  pgdata:

Start with:

bash
docker compose up -d
curl http://localhost:8080/healthz/ready
# {"status":"ok","checks":{"database":"ok","casbin":"ok"}}

Required environment variables#

VariableDescription
POSTGRES_HOSTPostgreSQL hostname
POSTGRES_USERDatabase user
POSTGRES_PASSWORDDatabase password
POSTGRES_DBDatabase name
AUTH_SERVER_URLOIDC issuer URL — used to derive the JWKS endpoint when JWKS_URI is not set explicitly
JWKS_URIDirect JWKS URL — overrides the auto-derived URL

See Deployment for the complete variable reference.

Claim configuration#

In self-hosted mode, claim paths are global environment variables:

env
ROLES_CLAIM=realm_access.roles        # where to find the roles array in the JWT
DOMAIN_CLAIM=dom                       # tenant/domain claim (use a constant if you have none)
ADMIN_DOMAIN_CLAIM=adm                 # optional: admin elevation claim
EXCLUDED_ROLES=offline_access,uma_authorization

If your tokens don't have a domain claim, set DOMAIN_CLAIM to any constant claim that exists (e.g. iss) and use a consistent value in your check requests.

Database migrations#

Migrations run automatically at startup. The service will crash on startup if the database is unreachable — ensure PostgreSQL is healthy before the service starts (use depends_on with a healthcheck as shown above).

All tables are created on first run:

  • resources — registered resources
  • casbin_rule — Casbin RBAC rules
  • abac_policy — ABAC condition-tree policies
  • service_api_keys — service-to-service API keys

Kubernetes deployment#

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: authorization-service
spec:
  replicas: 2
  template:
    spec:
      containers:
        - name: authorization-service
          image: ghcr.io/lhtuwrk/authorization-service:latest
          ports:
            - containerPort: 8080
          env:
            - name: MODE
              value: selfhost
            - name: POSTGRES_HOST
              value: postgres-service
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: authz-secrets
                  key: postgres-password
            - name: OIDC_ENABLED
              value: "true"
            - name: AUTH_SERVER_URL
              value: https://keycloak.example.com/realms/myrealm
            - name: JWKS_URI
              value: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs
          livenessProbe:
            httpGet:
              path: /healthz/live
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /healthz/ready
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5

Disabling OIDC (internal-only deployments)#

If your Permix is called only by internal services using service API keys and no user JWTs are involved:

env
OIDC_ENABLED=false

All JWT validation is skipped. Requests authenticated only with X-Service-Api-Key will still be processed normally.

⚠

Do not disable OIDC in production

Only disable OIDC validation if all callers use service API keys and the service is unreachable from the internet. Disabling OIDC with internet-facing endpoints removes all token authentication.