Skip to main content

Install Permit on-premises

Install the self-hosted Permit Platform in your Kubernetes or OpenShift cluster with the installer package from Permit.io. This page is for the operator who runs the installation. For a condensed version of the same procedure, see the on-premises quick start.

Enterprise on-premises license required

The on-premises installer is available to Enterprise customers with an on-premises license. To get a license, contact Permit.io.

Prerequisites

  • A cluster, tools, and Git repository that meet the on-premises prerequisites.
  • The installer package (permit-platform-on-prem-installer-*.tar.gz) from the download link Permit.io sends you.
  • A container registry that your cluster can pull from, unless you install on OpenShift with its internal registry.

Step 1: Extract the installer package

Extract the package and make the installer script executable:

# Extract the installer package
tar -xzf permit-platform-on-prem-installer-*.tar.gz
cd permit-platform-on-prem-installer-*

# Make installation script executable
chmod +x scripts/install-permit-platform.sh

The package contains the installer script (scripts/install-permit-platform.sh), the registry push script (scripts/push-images-to-registry.sh), the Helm chart and its values.yaml file (charts/permit-platform/), and the container images as .tar files (images/). Every later step runs from the extracted directory.

Step 2: Set the frontend domain

Open charts/permit-platform/values.yaml and replace CHANGEME_FRONTEND_DOMAIN with the domain your users open in a browser:

global:
# CHANGE THIS: Replace with your actual domain
frontendDomain: "CHANGEME_FRONTEND_DOMAIN"

# TO SOMETHING LIKE:
frontendDomain: "permit.yourcompany.com"
# OR
frontendDomain: "permit-platform.internal.corp"
# OR
frontendDomain: "permit.local"

Set frontendDomain once. The Helm chart builds the frontend URLs, the Keycloak authentication URLs, the backend cookie origins, and the ingress rules from this value.

The installer exits with the error Frontend domain not configured in values.yaml if frontendDomain is empty or still CHANGEME_FRONTEND_DOMAIN.

Step 3: Configure Policy Sync

Policy Sync connects the platform to the Git repository that stores your policies. The configuration is required. In charts/permit-platform/values.yaml, replace the two placeholders under permitServices.policySync:

permitServices:
policySync:
enabled: true
# BEFORE: placeholder value (will cause installation to fail)
policyRepoUrl: "CHANGEME_GIT_REPO_URL"

# AFTER: your actual Git repository URL
policyRepoUrl: "git@github.com:yourorg/permit-policies.git"

# BEFORE: placeholder value (will cause installation to fail)
sshPrivateKey: | # ← DO NOT DELETE THIS "|" SYMBOL!
CHANGEME_SSH_PRIVATE_KEY

# AFTER: your actual SSH private key content
sshPrivateKey: | # ← DO NOT DELETE THIS "|" SYMBOL!
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAA...
-----END OPENSSH PRIVATE KEY-----
KeyValue
policyRepoUrlThe SSH URL of your policy repository, for example git@github.com:yourorg/permit-policies.git.
sshPrivateKeyThe private key whose public key is a deploy key with write access on that repository.

If you don't have a key pair yet, generate one and print the private key:

# Generate a new SSH key pair (if needed)
ssh-keygen -t rsa -b 4096 -C "permit-platform@yourcompany.com"

# Copy your private key content
cat ~/.ssh/id_rsa # or your specific key file

The Git repository setup in the prerequisites shows how to add the public key as a deploy key and test access.

Keep the | after sshPrivateKey:

The installer exits if CHANGEME_GIT_REPO_URL or CHANGEME_SSH_PRIVATE_KEY is still in values.yaml. If you delete the | symbol, YAML reads the key as a single line and the Helm install fails with SSH private key must be in multi-line format.

Step 4: Push the images to your container registry

On every target except OpenShift, the installer reads the image location from global.imageRegistry in values.yaml and exits with ImageRegistry not configured in values.yaml when the value is empty. Push the images from the package to a registry that your cluster can pull from, then run the installer with --skip-images.

On OpenShift, the installer loads the images into the OpenShift internal registry and sets imageRegistry for you. Skip to Step 5 unless you use an external registry with --skip-openshift-registry.

Create a Google Artifact Registry repository for GKE

The push script does not create the repository. To use Google Artifact Registry with Google Kubernetes Engine (GKE), create a Docker repository and configure Docker authentication:

# Authenticate with Google Cloud
gcloud auth login

# Set your project
gcloud config set project YOUR_PROJECT_ID

# Create Artifact Registry repository
gcloud artifacts repositories create permit-platform \
--repository-format=docker \
--location=us-central1 \
--project=YOUR_PROJECT_ID

# Configure Docker authentication
gcloud auth configure-docker us-central1-docker.pkg.dev

For other registries, sign in with docker login or your registry's CLI before you run the push script.

Run the push script

Pass the registry path as the only argument:

# Navigate to scripts directory
cd scripts

# Push to Google Artifact Registry (example for us-central1)
./push-images-to-registry.sh us-central1-docker.pkg.dev/YOUR_PROJECT_ID/permit-platform

For each .tar file in images/, the script:

  1. Loads the image with docker load.
  2. Tags the image as <registry>/<image-name>:<original-tag>.
  3. Pushes the tag to the registry.

After all images are pushed, the script sets imageRegistry in charts/permit-platform/values.yaml to your registry path and saves a copy of the previous file as values.yaml.backup.<timestamp>. The script's output looks like this:

╔══════════════════════════════════════════════════════════════╗
║ Permit Platform Image Push Script ║
╚══════════════════════════════════════════════════════════════╝

Target Registry: us-central1-docker.pkg.dev/my-project/permit-platform
Images Directory: /path/to/permit-platform-on-prem-installer/scripts/../images

Found <N> images to push

[1/<N>] Processing permit-backend-v2.tar
Target image name: permit-backend-v2
Loading image from tar...
Loaded (original): <source-image>:<tag>
Tagging as us-central1-docker.pkg.dev/my-project/permit-platform/permit-backend-v2:<tag>
Pushing us-central1-docker.pkg.dev/my-project/permit-platform/permit-backend-v2:<tag>...
✓ Done

...

╔══════════════════════════════════════════════════════════════╗
║ All images pushed successfully!
╚══════════════════════════════════════════════════════════════╝

Updating values.yaml with registry: us-central1-docker.pkg.dev/my-project/permit-platform
✅ Updated values.yaml with imageRegistry: "us-central1-docker.pkg.dev/my-project/permit-platform"

Next steps:
1. Update charts/permit-platform/values.yaml with your frontend domain:
frontendDomain: "your-domain.company.com"

2. If using a private registry requiring authentication:
Add imagePullSecrets to values.yaml and create the Kubernetes secret

3. Run the installer:
./install-permit-platform.sh --skip-images

The same script works with any Docker-compatible registry. Pass the registry path in the same form:

RegistryExample path
Amazon Elastic Container Registry (ECR)123456789012.dkr.ecr.us-east-1.amazonaws.com/permit-platform
Azure Container Registry (ACR)myregistry.azurecr.io/permit-platform
JFrog Artifactoryartifactory.company.com/permit-platform
Harborharbor.company.com/permit-platform

Verify the images in Artifact Registry

List the pushed images and describe one of them:

# List all images in your GAR repository
gcloud artifacts docker images list us-central1-docker.pkg.dev/YOUR_PROJECT_ID/permit-platform

# Check a specific image
# IMAGE_TAG is the tag shown in the push script output or in values.yaml
gcloud artifacts docker images describe \
us-central1-docker.pkg.dev/YOUR_PROJECT_ID/permit-platform/permit-backend-v2:IMAGE_TAG

Add credentials for a registry that requires authentication

When the registry requires a username and password or token (Artifactory, Harbor, or another private registry), the cluster needs an image pull secret. Without one, pods stay in ImagePullBackOff.

GKE with Artifact Registry, Amazon EKS with ECR, and AKS with ACR use node-level authentication and don't need a secret.

  1. Create the secret in the namespace the platform uses. If the namespace doesn't exist yet, create it first with kubectl create namespace permit-platform.
# For registries requiring username/password (Artifactory, Harbor, etc.)
kubectl create secret docker-registry registry-credentials \
--docker-server=artifactory.company.com \
--docker-username=YOUR_USERNAME \
--docker-password=YOUR_PASSWORD_OR_TOKEN \
--docker-email=your-email@company.com \
--namespace permit-platform

# Verify secret was created
kubectl get secret registry-credentials -n permit-platform
  1. Reference the secret under global.imagePullSecrets in charts/permit-platform/values.yaml. The Helm chart applies the list to all deployments and jobs.
global:
imageRegistry: "artifactory.company.com/permit-platform"
imagePullPolicy: "IfNotPresent"

# Add your image pull secrets here
imagePullSecrets:
- registry-credentials

To use more than one secret, list each one:

global:
imagePullSecrets:
- registry-credentials
- backup-registry-credentials

Run the installer with --skip-images

When the images are already in your registry, pass --skip-images so the installer does not load the .tar files again:

# After pushing images to your private registry, run installer with --skip-images
cd scripts
./install-permit-platform.sh --gke --skip-images # For GKE, EKS, AKS, or self-managed Kubernetes
./install-permit-platform.sh --openshift --skip-openshift-registry --skip-images # For OpenShift with an external registry

Step 5: Run the installer

Run scripts/install-permit-platform.sh with the flag for your cluster type. To print the options for your installer version, run:

# Display all available options and examples
./scripts/install-permit-platform.sh --help

Deployment targets

TargetFlagWhat the installer does
Existing Kubernetes cluster (Google GKE, Amazon EKS, Azure AKS, or self-managed)--gkeDeploys to the cluster in your current kubectl context, pulls images from imageRegistry, and reads image tags from values.yaml. You manage TLS through your ingress or load balancer. The script accepts the flag, but --help does not list it.
Red Hat OpenShift Service on AWS (ROSA) or OpenShift Container Platform--openshiftLoads images into the OpenShift internal registry, uses the OpenShift router, and generates self-signed certificates.
Kind, for local development only--kindCreates a local Kind cluster named permit-platform-on-prem, loads the images into the cluster, installs the NGINX ingress controller, and generates self-signed certificates.

--openshift and --kind can't be combined.

Pass a target flag on an existing Kubernetes cluster

The --help output lists a run without a target flag as the production Kubernetes target. Without --gke, --openshift, or --kind, the installer runs the Kind steps: it tries to create a Kind cluster named permit-platform-on-prem and reads the backend image tag from that cluster's control-plane container. On an existing EKS, AKS, or self-managed cluster, the installation fails at those steps.

Installer options

OptionUse it when
-n, --namespace <name>You want a namespace other than the default, permit-platform.
--generate-tlsYou want the installer to generate self-signed certificates with mkcert, or OpenSSL when mkcert is not installed.
--skip-tls-checkTLS terminates outside the cluster, for example at an AWS Application Load Balancer or with cert-manager.
--skip-imagesThe images are already in your registry, or you re-run the installer after a failure.
--skip-openshift-registryYou install on OpenShift and pull images from an external registry set in imageRegistry.
--dry-runYou want to see what the installer would deploy without applying it.
--yesYou run the installer non-interactively and the Keycloak version changes. The flag accepts the one-way Keycloak schema migration without a prompt. Take a pg_dump of the Keycloak database first, because the migration can't be rolled back.

The on-premises reference lists the commands and values.yaml settings in full.

Common flag combinations

Kubernetes with self-signed certificates generated by the installer:

# Production K8s with auto-generated certificates
./scripts/install-permit-platform.sh --gke --generate-tls

OpenShift with an external registry such as Amazon ECR, in a custom namespace:

# OpenShift using AWS ECR instead of internal registry
./scripts/install-permit-platform.sh \
--openshift \
--skip-openshift-registry \
--namespace permit-prod

Kind for local testing, without reloading images:

# Kind cluster, skip images for faster testing
./scripts/install-permit-platform.sh \
--kind \
--skip-images \
--namespace test-permit

Amazon EKS with TLS terminated at an Application Load Balancer:

# AWS EKS with ALB and ACM certificates
./scripts/install-permit-platform.sh \
--gke \
--skip-tls-check \
--namespace production

What the installer does during the run

  1. Checks that frontendDomain, policyRepoUrl, and sshPrivateKey are set.
  2. Generates passwords for PostgreSQL, RabbitMQ, the Keycloak admin, and the Keycloak database, and writes them to values.yaml. On a re-run, the installer keeps the passwords stored in the cluster.
  3. Checks imageRegistry and the required tools (kubectl and helm, plus oc for OpenShift or docker and kind for Kind).
  4. Prepares the cluster for the target. With --gke, uses the existing cluster. With --openshift, loads the images into the internal registry. With --kind, or without a target flag, creates the Kind cluster and loads the images, and with --kind also installs the NGINX ingress controller.
  5. Creates the namespace and the Open Policy Administration Layer (OPAL) server secret.
  6. Installs the third-party-services Helm release and waits for the databases, cache, search, message queue, and Keycloak to become ready.
  7. Generates the relay JSON Web Token (JWT) keys, then runs the database migrations with the migrations Helm release.
  8. Installs the permit-platform Helm release with the Permit services and checks that the pods are running.

Step 6: Verify the installation

Read the completion message

When the installer finishes, it prints the frontend URL, the admin credentials, and the dashboard URLs:

[SUCCESS] 🎉 Permit Platform On-Prem deployed successfully!
[INFO] Access your Permit Platform at:
[INFO] • Frontend: https://permit-frontend.local
[INFO] - Admin user: admin/[Generated Password]
[INFO] - Or register a new account
[INFO] Additional services:
[INFO] • OpenSearch Dashboard: https://permit-frontend.local/opensearch/ (no authentication required)
[INFO] • RabbitMQ Dashboard: https://permit-frontend.local/rabbitmq/ (permit/[Generated Password])
[INFO] • Keycloak Admin: https://permit-frontend.local/auth (admin/[Same Admin Password])
[INFO] • SCIM Service: https://permit-frontend.local/scim
[INFO] Important:
[INFO] • For Kind clusters (development), add to /etc/hosts:
[INFO] 127.0.0.1 permit-frontend.local
[INFO] • For OpenShift ROSA (development), find router IP and add to /etc/hosts:
[INFO] $(nslookup $(oc get ingress -n permit-platform -o jsonpath='{.items[0].status.loadBalancer.ingress[0].hostname}') | grep Address | tail -1 | awk '{print $2}') permit-frontend.local
[INFO] • Certificate warnings are normal for self-signed certificates
[INFO] • Click 'Advanced''Proceed to site' in browser
[INFO] For troubleshooting, run: kubectl get pods -n permit-platform
Generated passwords are stored in values.yaml

The installer writes the generated passwords, including the Keycloak admin and RabbitMQ passwords, into charts/permit-platform/values.yaml. Anyone with a copy of the file and network access to the frontend domain can sign in to those consoles as an admin. Store the file like a secret, and don't commit it to a Git repository. The passwords are also stored in the global-infrastructure-secret Kubernetes secret.

Check that the pods are running

# Check all pods are running
kubectl get pods -n permit-platform

# Check services and endpoints
kubectl get services -n permit-platform

# View deployment status
kubectl get deployments -n permit-platform

Every pod shows Running with all containers ready:

NAME READY STATUS
permit-backend-v2-xxx 1/1 Running
permit-frontend-xxx 1/1 Running
opal-server-xxx 1/1 Running
postgres-xxx 1/1 Running
redis-xxx 1/1 Running
keycloak-xxx 1/1 Running

Some pods restart a few times on first start while they wait for their dependencies. A pod that stays in CrashLoopBackOff needs attention. See the on-premises troubleshooting guide.

Sign in to the platform

  1. Open https://<your-frontend-domain> in a browser.
  2. If the platform uses a self-signed certificate, accept the browser warning. See Self-signed certificate warnings.
  3. Sign in as admin with the password from the completion message, or register a new account.
  4. Create your first organization and project when the platform prompts you.

The Permit sign-in page and the organization setup screen confirm that the frontend, the backend, and Keycloak work together.

Access the platform dashboards

All dashboards are served under the frontend domain:

ServiceURLSign-in
Permit Platformhttps://<your-frontend-domain>admin and the generated password, or a registered account
OpenSearch Dashboards (decision and audit logs)https://<your-frontend-domain>/opensearch/None
RabbitMQ managementhttps://<your-frontend-domain>/rabbitmq/permit and the generated RabbitMQ password
Keycloak admin consolehttps://<your-frontend-domain>/authadmin and the generated Keycloak admin password
System for Cross-domain Identity Management (SCIM) 2.0 APIhttps://<your-frontend-domain>/scimUsed by identity providers for user provisioning
OpenSearch Dashboards has no authentication

Anyone who can reach the frontend domain can open /opensearch/ and read decision logs without signing in. Restrict network access to the ingress to trusted networks.

Point the frontend domain at the cluster

Production DNS record

Create a DNS record that points the frontend domain at the external address of your ingress or load balancer:

# Point your domain to the server where Permit is installed
permit.yourcompany.com A <server-ip>

Hosts file entries for local and test clusters

For a .local domain or a test cluster without DNS, add the domain to the hosts file on the machine that runs the browser.

For Kind, map the domain to localhost:

# Add to /etc/hosts (Linux/Mac) or C:\Windows\System32\drivers\etc\hosts (Windows)
127.0.0.1 permit-frontend.local

For OpenShift on AWS (ROSA), resolve the router address and add it:

# Find the router IP and add to hosts file
ROUTER_IP=$(nslookup $(oc get ingress -n permit-platform -o jsonpath='{.items[0].status.loadBalancer.ingress[0].hostname}') | grep Address | tail -1 | awk '{print $2}')
echo "$ROUTER_IP permit-frontend.local" | sudo tee -a /etc/hosts

For Kubernetes with the NGINX ingress controller, use the external IP of the controller service:

# Get the LoadBalancer/NodePort IP
kubectl get svc -n ingress-nginx
# Add the external IP to hosts file
echo "<EXTERNAL-IP> permit-frontend.local" | sudo tee -a /etc/hosts

Configure TLS certificates

Choose one of three ways to serve the frontend domain over HTTPS:

MethodHow to enable itUse it for
Self-signed certificates--generate-tls flag. Kind and OpenShift installs generate them by default.Development and internal testing
Your own certificateingress.tls settings in values.yamlProduction with a certificate from your certificate authority (CA)
External TLS termination--skip-tls-check flagProduction behind an Application Load Balancer, cert-manager, or another TLS proxy

To use your own certificate, enable TLS in charts/permit-platform/values.yaml:

ingress:
tls:
enabled: true
certificate:
# Base64-encoded PEM files on one line, for example: base64 < tls.crt | tr -d '\n'
cert: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t..."
key: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t..."

The TLS secret is named permit-frontend-tls by default (ingress.tls.secretName).

Self-signed certificate warnings

Browsers don't trust self-signed certificates, so the first visit shows a security warning. The connection is encrypted, but the browser can't verify the server's identity. To continue:

BrowserAction
ChromeAdvanced, then Proceed to <domain> (unsafe)
FirefoxAdvanced, then Accept the Risk and Continue
SafariShow Details, then visit this website
EdgeAdvanced, then Continue to <domain> (unsafe)

For production, use a certificate from a trusted CA so users don't see the warning.

What the installer deploys

The installer deploys three Helm releases, in this order, into one namespace (permit-platform by default):

Helm releaseContents
third-party-servicesPostgreSQL with a read replica and PgBouncer, Redis, RabbitMQ, OpenSearch with OpenSearch Dashboards, and Keycloak with its own PostgreSQL database
migrationsJobs that create the backend and OPAL relay database schemas
permit-platformThe Permit services, grouped below

The permit-platform release runs these services:

GroupServices
API and UIpermit-backend-v2, permit-frontend
Policy distributionopal-server, permit-bundled-opa, permit-policy-sync-v2
PDP datapermit-pdp-data, permit-pdp-deltas, permit-pdp-cache-builder
Relaypermit-opal-interface-v2, permit-opal-relay-api-v2, permit-opal-relay-consumer-v2, permit-opal-relay-statistics-api-v2, permit-opal-relay-watcher-v2, permit-opal-relay-reports-consumer-v2, permit-relay-jwt-api
Background workerscelery-general, permit-celery-beat, permit-data-generator, permit-data-generator-heavy, permit-cache-rebuilder
Logs and integrationspermit-decision-logs, permit-dl-enricher-v2, permit-webhook-transmitter-v2, permit-scim-okta

To list what runs in your cluster, run kubectl get deployments,services,pvc -n permit-platform.

Persistent volume sizes are set per service in values.yaml under thirdPartyServices.<service>.persistence.size.

Persistent volumes can grow but not shrink

A persistent volume claim (PVC) can be expanded but never reduced. If you set a persistence.size lower than the current size of the volume, every later helm upgrade of third-party-services fails with field can not be less than status.capacity. Raise sizes. Don't lower them.

For day-to-day operations, upgrades, and scaling, see the on-premises management guide.

Troubleshoot installation issues

Start with the check that matches the symptom. The on-premises troubleshooting guide covers more cases.

Image loading fails

Check that Docker runs, that the disk has space for the images, and that Docker can reach a registry:

# Check Docker service
systemctl status docker

# Check available disk space
df -h

# Check image registry connectivity
docker pull hello-world

The frontend domain doesn't resolve

Check DNS, the hosts file, and the ingress:

# Test DNS resolution
nslookup [your-frontend-domain]

# Check hosts file (for .local domains)
cat /etc/hosts

# Verify ingress configuration
kubectl get ingress -n permit-platform

Services can't connect to the database

Check the PostgreSQL logs and whether PostgreSQL accepts connections:

# Check PostgreSQL pod
kubectl logs -n permit-platform deployment/postgres

# Test database connectivity
kubectl exec -n permit-platform deployment/postgres -- pg_isready

Policy Sync can't reach the Git repository

Check the Policy Sync logs for SSH or repository errors:

# Check Policy Sync service logs
kubectl logs -n permit-platform deployment/permit-policy-sync-v2

# Verify the repository URL (the SSH key and URL are environment variables on the deployment)
kubectl set env deployment/permit-policy-sync-v2 -n permit-platform --list | grep POLICY_REPO_URL

Check service logs

# Backend service logs
kubectl logs -n permit-platform deployment/permit-backend-v2

# Frontend service logs
kubectl logs -n permit-platform deployment/permit-frontend

# Authentication service logs
kubectl logs -n permit-platform deployment/keycloak

# Database logs
kubectl logs -n permit-platform deployment/postgres

Check ingress connectivity

# Check ingress controller
kubectl get pods -n ingress-nginx

# Test internal connectivity
kubectl exec -n permit-platform deployment/permit-backend-v2 -- curl -s -o /dev/null -w '%{http_code}\n' http://permit-frontend/

Next steps

For installation help, email support@permit.io or ask in the Permit community on Slack.