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.