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 GitHubSelf-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#
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:
docker compose up -d
curl http://localhost:8080/healthz/ready
# {"status":"ok","checks":{"database":"ok","casbin":"ok"}}Required environment variables#
| Variable | Description |
|---|---|
POSTGRES_HOST | PostgreSQL hostname |
POSTGRES_USER | Database user |
POSTGRES_PASSWORD | Database password |
POSTGRES_DB | Database name |
AUTH_SERVER_URL | OIDC issuer URL — used to derive the JWKS endpoint when JWKS_URI is not set explicitly |
JWKS_URI | Direct 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:
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_authorizationIf 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 resourcescasbin_rule— Casbin RBAC rulesabac_policy— ABAC condition-tree policiesservice_api_keys— service-to-service API keys
Kubernetes deployment#
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: 5Disabling OIDC (internal-only deployments)#
If your Permix is called only by internal services using service API keys and no user JWTs are involved:
OIDC_ENABLED=falseAll 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.