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.
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-----
| Key | Value |
|---|---|
policyRepoUrl | The SSH URL of your policy repository, for example git@github.com:yourorg/permit-policies.git. |
sshPrivateKey | The 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.
| 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:
- Loads the image with
docker load. - Tags the image as
<registry>/<image-name>:<original-tag>. - 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:
| Registry | Example 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 Artifactory | artifactory.company.com/permit-platform |
| Harbor | harbor.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.
- 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
- Reference the secret under
global.imagePullSecretsincharts/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
| Target | Flag | What the installer does |
|---|---|---|
| Existing Kubernetes cluster (Google GKE, Amazon EKS, Azure AKS, or self-managed) | --gke | Deploys 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 | --openshift | Loads images into the OpenShift internal registry, uses the OpenShift router, and generates self-signed certificates. |
| Kind, for local development only | --kind | Creates 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.
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
| Option | Use it when |
|---|---|
-n, --namespace <name> | You want a namespace other than the default, permit-platform. |
--generate-tls | You want the installer to generate self-signed certificates with mkcert, or OpenSSL when mkcert is not installed. |
--skip-tls-check | TLS terminates outside the cluster, for example at an AWS Application Load Balancer or with cert-manager. |
--skip-images | The images are already in your registry, or you re-run the installer after a failure. |
--skip-openshift-registry | You install on OpenShift and pull images from an external registry set in imageRegistry. |
--dry-run | You want to see what the installer would deploy without applying it. |
--yes | You 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
- Checks that
frontendDomain,policyRepoUrl, andsshPrivateKeyare set. - 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. - Checks
imageRegistryand the required tools (kubectlandhelm, plusocfor OpenShift ordockerandkindfor Kind). - 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--kindalso installs the NGINX ingress controller. - Creates the namespace and the Open Policy Administration Layer (OPAL) server secret.
- Installs the
third-party-servicesHelm release and waits for the databases, cache, search, message queue, and Keycloak to become ready. - Generates the relay JSON Web Token (JWT) keys, then runs the database migrations with the
migrationsHelm release. - Installs the
permit-platformHelm 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
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
- Open
https://<your-frontend-domain>in a browser. - If the platform uses a self-signed certificate, accept the browser warning. See Self-signed certificate warnings.
- Sign in as
adminwith the password from the completion message, or register a new account. - 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:
| Service | URL | Sign-in |
|---|---|---|
| Permit Platform | https://<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 management | https://<your-frontend-domain>/rabbitmq/ | permit and the generated RabbitMQ password |
| Keycloak admin console | https://<your-frontend-domain>/auth | admin and the generated Keycloak admin password |
| System for Cross-domain Identity Management (SCIM) 2.0 API | https://<your-frontend-domain>/scim | Used by identity providers for user provisioning |
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:
| Method | How to enable it | Use it for |
|---|---|---|
| Self-signed certificates | --generate-tls flag. Kind and OpenShift installs generate them by default. | Development and internal testing |
| Your own certificate | ingress.tls settings in values.yaml | Production with a certificate from your certificate authority (CA) |
| External TLS termination | --skip-tls-check flag | Production 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:
| Browser | Action |
|---|---|
| Chrome | Advanced, then Proceed to <domain> (unsafe) |
| Firefox | Advanced, then Accept the Risk and Continue |
| Safari | Show Details, then visit this website |
| Edge | Advanced, 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 release | Contents |
|---|---|
third-party-services | PostgreSQL with a read replica and PgBouncer, Redis, RabbitMQ, OpenSearch with OpenSearch Dashboards, and Keycloak with its own PostgreSQL database |
migrations | Jobs that create the backend and OPAL relay database schemas |
permit-platform | The Permit services, grouped below |
The permit-platform release runs these services:
| Group | Services |
|---|---|
| API and UI | permit-backend-v2, permit-frontend |
| Policy distribution | opal-server, permit-bundled-opa, permit-policy-sync-v2 |
| PDP data | permit-pdp-data, permit-pdp-deltas, permit-pdp-cache-builder |
| Relay | permit-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 workers | celery-general, permit-celery-beat, permit-data-generator, permit-data-generator-heavy, permit-cache-rebuilder |
| Logs and integrations | permit-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.
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
- Manage the on-premises platform: configuration changes, upgrades, backups, and scaling.
- Deploy PDPs in your cluster: connect policy decision points (PDPs) to the self-hosted control plane.
- Look up installer commands and values.yaml settings.
- Troubleshoot the on-premises platform.
For installation help, email support@permit.io or ask in the Permit community on Slack.