This section is only relevant to Enterprise customers who acquired an on-prem license.
Migrate from Permit Cloud to On-Premises
This guide explains how to import your existing Permit Cloud (SaaS) data into your on-premises Permit Platform deployment, so you can switch environments without rebuilding your authorization model from scratch.
Migration is a coordinated process: the Permit team exports your organization's data from Permit Cloud and delivers it to you as a migration data package. You then import that package into your own deployment using the steps below.
The migration has five phases: request the package → freeze & export (done by Permit) → import (steps 1-9 below) → verify → point your applications at on-prem. Your on-prem platform is offline between steps 2 and 9 - for a typical organization the import itself takes well under an hour, and your Permit account team is available to assist during the window. You are done when the row counts match the manifest and the UI checks in Verify the Import pass.
Requesting Your Migration Package
Contact your Permit account team to schedule the migration. You will agree on:
- A migration window - plan a short change freeze on your Permit Cloud workspace right before the export, so the package captures your final state
- Audit log history (optional) - historical audit logs are not part of the standard package. Permit Cloud retains them for a limited period, so history that is not exported around the migration window cannot be recovered later - if you want it, request it when scheduling and confirm the retention window with your account team
- Version alignment - Permit confirms that your on-prem installer version matches the data
package (the package's
manifest.jsonrecords the source schema version)
Permit then performs the export and sends you a secure, time-limited download link to the package. Your Permit contact will tell you when the link expires - download it promptly.
What Gets Migrated
The migration data package contains a complete snapshot of your organization's data:
- Authorization model - resources, actions, roles, permissions, relations, condition sets
- Directory data - users, tenants, role assignments, relationship tuples, resource instances
- Configuration - PDP configurations, webhooks, proxy configs, Permit Elements, email templates
- Workspace members - your team members and their access levels
- API keys - your existing
permit_key_*tokens migrate as-is; your applications keep using the same keys and only the endpoints change (see Point Your Applications at On-Prem)
What is not migrated:
- Login credentials - on-prem uses its own identity provider (Keycloak). Team members sign in again and regain their workspace access on first login, provided they use the same verified email address they used in Permit Cloud (see Team member access)
- Audit log history - optional, by request only (see Requesting Your Migration Package). New audit logs start flowing as soon as your on-prem deployment is running
- Runtime state - PDPs re-register automatically, and generated policy is rebuilt from the imported data on startup
The migration data package contains your API keys. Store it securely, restrict access to it, and delete all copies once the import is verified.
Package Contents
permit-migration-<org>-<date>/
├── data/
│ ├── ... (one CSV per table)
│ └── v2_identity.csv # reference only - do NOT import (see below)
├── manifest.json # row counts, checksums, and the import order
├── policy-repo/ # only if you used the default Permit-managed policy repo
└── README.md
v2_identity.csvThis file is a reference snapshot of your previous cloud login identities. It is intentionally excluded from the import steps below - importing it can prevent team members from regaining access when they first sign in to your on-prem deployment.
Policy repository
Your policy-as-code (Rego) lives in a Git repository, not in the database:
- If you connected your own Git repository in Permit Cloud (GitOps) - keep using it. Point
your on-prem deployment at the same repository in
values.yaml - If you used the default Permit-managed repository - your package includes a
policy-repo/clone. Push it to a Git server you control and point your on-prem deployment at it, as described in the packageREADME.md
Any custom Rego you wrote exists only in the Git repository - it is not part of the database import, so make sure the repository is connected before you rely on custom policies.
Prerequisites
- A healthy on-premises Permit Platform deployment
(see the Installation Guide) - all pods
Running, migrations jobCompleted - The installer version confirmed by your Permit contact to match your migration data package
- Access to the Kubernetes cluster with
kubectlconfigured jq,tar, andsha256sum(macOS:shasum) available on the workstation where you extract the package- The migration data package downloaded from the secure link provided by Permit
Step-by-Step Import
1. Verify the package integrity
Extract the package and verify every file against the manifest checksums:
tar -xzf permit-migration-<org>-<date>.tar.gz
cd permit-migration-<org>-<date>/
# Verify checksums (on macOS use: shasum -a 256 -c -)
jq -r '.tables[] | "\(.sha256) \(.file)"' manifest.json | sha256sum -c -
Every line should print OK. If any file fails verification, re-download the package before
continuing.
2. Stop services that write to the database
First note the current replica counts, so you can restore them in step 9:
kubectl get deployment -n permit-platform \
permit-backend-v2 celery-general permit-dl-enricher-v2
Then pause these services so nothing changes mid-import:
kubectl scale deployment -n permit-platform \
permit-backend-v2 celery-general permit-dl-enricher-v2 --replicas=0
3. Back up the database
Take a backup before importing, so you can roll back cleanly if anything goes wrong:
PG_POD=$(kubectl get pods -n permit-platform -l app=postgres \
--field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n permit-platform $PG_POD -- \
pg_dump -U permit -d permit -F c -f /tmp/pre-migration.dump
kubectl cp permit-platform/$PG_POD:/tmp/pre-migration.dump ./pre-migration.dump
kubectl exec -n permit-platform $PG_POD -- rm -f /tmp/pre-migration.dump
If your PostgreSQL runs outside the cluster, use your own snapshot mechanism instead.
To roll back later (with the services still scaled to 0):
kubectl cp ./pre-migration.dump permit-platform/$PG_POD:/tmp/pre-migration.dump
kubectl exec -n permit-platform $PG_POD -- \
pg_restore -U permit -d permit --clean --if-exists /tmp/pre-migration.dump
4. Copy the data files to the PostgreSQL pod
kubectl exec -n permit-platform $PG_POD -- mkdir -p /tmp/import
kubectl cp data permit-platform/$PG_POD:/tmp/import/data
5. Import the data
The manifest lists every table to import (v2_identity is deliberately not in the list). The
whole import runs as one database transaction: if anything fails, nothing is imported - fix
the cause and simply run it again. psql prints COPY <row-count> for each table as it loads:
{
echo '\set ON_ERROR_STOP on'
echo 'BEGIN;'
echo "SET session_replication_role = 'replica';"
jq -r '.import.tables_in_order[]' manifest.json | while read -r TABLE; do
echo "\\COPY v2.${TABLE} FROM '/tmp/import/data/${TABLE}.csv' WITH CSV HEADER"
done
echo 'COMMIT;'
} | kubectl exec -i -n permit-platform $PG_POD -- psql -U permit -d permit
Some Permit tables reference each other in both directions, so no import order can satisfy
per-row referential checks - the import therefore turns them off for this session
(session_replication_role). This is safe: your package was exported as a single consistent
database snapshot. The one known exception - references to members who left your organization
before the export - is cleaned up in step 6. Use this technique only for this import - not for
ad-hoc database changes.
6. Clear references to departed members
Clear any references to members that are not part of the imported data (these come from members who left your organization before the export):
kubectl exec -n permit-platform $PG_POD -- psql -U permit -d permit -c "
UPDATE v2.v2_api_key k SET created_by_member_id = NULL
WHERE created_by_member_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM v2.v2_member m WHERE m.id = k.created_by_member_id);
UPDATE v2.v2_user_invite ui SET member_id = NULL
WHERE member_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM v2.v2_member m WHERE m.id = ui.member_id);"
7. Review cloud-specific configuration
Run the short cloud-reference cleanup included in your package (it removes settings that only
apply to Permit Cloud) - see the Cleanup section of the package README.md.
Afterwards, review these imported settings - they may carry over values that only made sense in Permit Cloud:
- SSO connections - imported SSO settings reference the Permit Cloud login integration. Reconfigure SSO against your on-prem Keycloak (or delete the stale entries)
- Webhooks - your on-prem deployment sends webhooks from a different source IP; update firewall allowlists on the receiving side if needed
- PDP configurations - review any PDP settings that point at Permit Cloud endpoints
8. Grant your admin access to the imported organization
The admin account you created during installation is not a member of the imported organization yet. Make it a superuser so it can see and manage all organizations, including the imported one:
kubectl exec -n permit-platform $PG_POD -- psql -U permit -d permit -c "
UPDATE v2.v2_member SET is_superuser = true WHERE email = '<your-admin-email>';"
After you restart the services in step 9, log out and log back in for the change to take effect.
A superuser can see and manage all organizations on your deployment, not just the imported one. Grant it only to your installation admin account.
9. Clean up and restart
# Remove the import files from the pod
kubectl exec -n permit-platform $PG_POD -- rm -rf /tmp/import
# Scale each service back to the replica count you noted in step 2
kubectl scale deployment -n permit-platform permit-backend-v2 --replicas=<count-from-step-2>
kubectl scale deployment -n permit-platform celery-general --replicas=<count-from-step-2>
kubectl scale deployment -n permit-platform permit-dl-enricher-v2 --replicas=<count-from-step-2>
Verify the Import
Compare the imported row counts against the values in manifest.json. The counts must be
filtered to your imported organization - your deployment may already contain other organizations
(for example, the one created by your installation admin):
# Your organization id is recorded in the manifest
ORG_ID=$(jq -r '.org_id' manifest.json)
kubectl exec -n permit-platform $PG_POD -- psql -U permit -d permit -c "
SELECT 'projects' AS table_name, count(*) FROM v2.v2_project WHERE org_id = '$ORG_ID'
UNION ALL SELECT 'environments', count(*) FROM v2.v2_environment WHERE org_id = '$ORG_ID'
UNION ALL SELECT 'users', count(*) FROM v2.v2_user WHERE org_id = '$ORG_ID'
UNION ALL SELECT 'tenants', count(*) FROM v2.v2_tenant WHERE org_id = '$ORG_ID'
UNION ALL SELECT 'roles', count(*) FROM v2.v2_role WHERE org_id = '$ORG_ID'
UNION ALL SELECT 'relationship_tuples', count(*) FROM v2.v2_relationship_tuple WHERE org_id = '$ORG_ID'
UNION ALL SELECT 'api_keys', count(*) FROM v2.v2_api_key WHERE org_id = '$ORG_ID';"
Then confirm in the UI:
- Log in to your on-prem dashboard as the admin - the imported organization should be visible
- Open the Members page - your team members appear with their original access levels
- Open the Policy page of a migrated environment - your resources, roles, and permissions are present
Team Member Access After Import
Your team members' profiles and permission levels are fully migrated. To sign in, each member authenticates against your on-prem identity provider (Keycloak) using the same email address they used in Permit Cloud - the platform automatically links them to their migrated profile and restores their workspace access on first login.
Depending on how you configured Keycloak, members either:
- Sign in through your corporate SSO (if you federated your IdP with Keycloak), or
- Self-register on the on-prem login page with their work email
The automatic account linking only happens for verified email addresses. Make sure email
verification is enabled in your Keycloak realm (or that your federated IdP passes
email_verified=true). A member who registers with a different or unverified email will start
with a fresh account and no access.
Point Your Applications at On-Prem
Your API keys were migrated as-is, so your applications keep using the same tokens - they just need to talk to your on-prem deployment instead of Permit Cloud. Two endpoints change:
- The PDP address - deploy a PDP against your on-prem platform (see PDP Deployment) and point your application at it
- The API address - point the SDK's API URL at your on-prem platform, so management calls (like syncing users) reach your deployment and not Permit Cloud
# Before (Permit Cloud)
permit = Permit(
pdp="https://cloudpdp.api.permit.io",
token="permit_key_XXXX",
)
# After (On-Premises) - same key, new endpoints
permit = Permit(
pdp="http://<your-pdp-address>:7766", # the PDP you deployed on-prem
api_url="https://<your-permit-domain>/api", # your on-prem Permit API
token="permit_key_XXXX",
)
If api_url still points at Permit Cloud, your migrated API keys remain valid there - your
application would silently keep writing users and permissions to your old cloud workspace
instead of your on-prem deployment.
Troubleshooting
The import fails partway through
The import runs in a single transaction, so a failure means nothing was imported - the database is unchanged. Fix the cause shown in the error (usually a missing file or a wrong path), then run step 5 again from the start. If you are unsure of the state, roll back using the backup from step 3, or contact Permit support.
The import prints no COPY lines
Your package was produced before manifests included the import list
(.import.tables_in_order). Check the package README.md for the ready-to-run import script
matching your package version, or contact Permit support.
The imported organization is not visible in the UI
Complete step 8 (superuser grant), then log out and log back in. You can list all organizations with:
kubectl exec -n permit-platform $PG_POD -- psql -U permit -d permit -c \
"SELECT id, name, key FROM v2.v2_organization;"
A team member logs in and sees an empty workspace
Their login email does not match their Permit Cloud email, or their email is not verified. Check the email on the Members page, and verify your Keycloak realm has email verification enabled.
Support
Need help with your migration?
- 📧 Email: support@permit.io
- 💬 Slack: Join our community