Permify publishes an official Helm chart at github.com/Permify/helm-charts. This is the recommended way to run Permify on Kubernetes.
Prerequisites
- A Kubernetes cluster (self-managed, EKS, GKE, AKS, etc.)
kubectl configured and pointing at the target cluster
- Helm 3 installed
- A PostgreSQL 13.8+ database reachable from the cluster
Deployment workflow
Add the Permify Helm repository
helm repo add permify https://permify.github.io/helm-charts
helm repo update
Confirm the chart is available:Download and review the default values
curl -o values.yaml \
https://raw.githubusercontent.com/Permify/helm-charts/main/charts/permify/values.yaml
Or inspect defaults directly with Helm:helm show values permify/permify
Edit values.yaml for your environment
At minimum, set the database connection and (for production) enable authentication.See the configuration reference below for all options. Install the chart
helm install permify permify/permify -f values.yaml
To install into a specific namespace:helm install permify permify/permify \
--namespace permify \
--create-namespace \
-f values.yaml
Verify the rollout
kubectl get deployments,pods,svc
Wait until all pods are Running and then check the health endpoint:kubectl port-forward svc/permify 3476:3476
curl http://localhost:3476/healthz
A healthy instance returns SERVING.Upgrade an existing release
After editing values.yaml:helm upgrade permify permify/permify -f values.yaml
Helm chart configuration
Deployment and image
# Number of Permify pods. Increase for high availability.
replicaCount: 3
image:
repository: ghcr.io/permify/permify
pullPolicy: Always
tag: v1.6.0 # pin to a specific version in production
Number of Permify pods to schedule. Set to at least 2 for high availability.
image.repository
string
default:"ghcr.io/permify/permify"
Container image repository.
Image pull policy. Use IfNotPresent to avoid re-pulling on every pod start.
Image tag. Defaults to the chart’s appVersion when not set. Pin this to a specific version in production.
Permify application config (app)
All Permify configuration options are nested under the app key in values.yaml. They map directly to the configuration reference.
Server
app:
server:
rate_limit: 100_000
http:
enabled: true
port: 3476
tls:
enabled: false
grpc:
port: 3478
tls:
enabled: false
Logger
app:
logger:
level: info # error | warn | info | debug
Authentication
Authentication is disabled by default in the Helm chart. Enable it before exposing Permify to a network you do not fully control.
app:
authn:
enabled: true
method: preshared
preshared:
keys: ["your-secret-key"]
For production, store keys in a Kubernetes Secret and reference it with keys_secret instead of inline keys.
Database
app:
database:
engine: postgres
uri: postgres://user:password@postgres-host:5432/permify?sslmode=require
auto_migrate: true
max_connections: 20
min_connections: 0
max_connection_lifetime: 300s
max_connection_idle_time: 60s
garbage_collection:
enabled: false
# interval: 200h
# window: 200h
# timeout: 5m
Store the database URI in a Kubernetes Secret and use uri_secret to reference it, rather than embedding credentials in values.yaml.
Distributed mode
Enable distributed mode when running multiple replicas so that the permission cache is routed consistently across pods:
app:
distributed:
enabled: true
address: "kubernetes:///permify.<namespace>:5000"
port: "5000"
Replace <namespace> with the Kubernetes namespace Permify is deployed into.
Health check probes
The Helm chart configures liveness, readiness, and optional startup probes against the HTTP health endpoint (/healthz on port 3476).
livenessProbe:
enabled: true
initialDelaySeconds: 60 # wait for Permify to fully initialize
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 12 # restart after 12 consecutive failures (~2 min)
successThreshold: 1
readinessProbe:
enabled: true
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6 # remove from service endpoints after 6 failures (~1 min)
successThreshold: 1
# Enable for slow-starting instances (e.g. large schema migrations)
startupProbe:
enabled: false
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
successThreshold: 1
Enable liveness checks. Kubernetes restarts the container when consecutive failures exceed failureThreshold.
livenessProbe.initialDelaySeconds
Seconds to wait after container start before the first liveness probe fires.
Enable readiness checks. Kubernetes removes the pod from Service endpoints until the probe passes.
readinessProbe.initialDelaySeconds
Seconds to wait after container start before the first readiness probe fires.
Enable startup probe for instances that take longer to boot, such as when auto-migration runs against a large database.
Service and ingress
service:
type: LoadBalancer # ClusterIP | NodePort | LoadBalancer
annotations: {}
ingress:
enabled: false
className: ""
annotations: {}
hosts:
- host: permify.example.com
paths:
- path: /
pathType: ImplementationSpecific
tls: []
If you use service.type: LoadBalancer, the gRPC port (3478) is also exposed. Consider ClusterIP with an ingress controller when you want finer-grained control over which ports are public.
Resource limits
Always set resource requests and limits in production to ensure stable scheduling:
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
Without resource limits, a single Permify pod can consume all available node memory during a traffic spike, causing evictions of other workloads.
Autoscaling
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
Enable a HorizontalPodAutoscaler for the Permify deployment.
Minimum number of replicas kept running at all times.
Maximum number of replicas the HPA can scale to.
autoscaling.targetCPUUtilizationPercentage
CPU utilization percentage that triggers a scale-up.
Pod scheduling
nodeSelector: {}
tolerations: []
affinity: {}
Use affinity to spread pods across availability zones:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- permify
topologyKey: topology.kubernetes.io/zone
Operational notes
Cache warm-up after scaling
Permify’s permission cache is pod-local. When pods are added or removed, the key ranges that move to a new pod start cold. Expect a temporary increase in database read load until caches warm up.
Watch API stream reconnection
Watch streams are tied to a specific pod and are not handed off during scale-in or rolling restarts. Clients must reconnect after such events.
Manual deployment without Helm
If you prefer plain Kubernetes manifests, the essential resources are a Deployment and a Service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: permify
labels:
app: permify
spec:
replicas: 2
selector:
matchLabels:
app: permify
template:
metadata:
labels:
app: permify
spec:
containers:
- name: permify
image: ghcr.io/permify/permify
args:
- serve
- --database-engine=postgres
- --database-uri=postgres://user:password@db-host:5432/permify
ports:
- containerPort: 3476
name: http
- containerPort: 3478
name: grpc
apiVersion: v1
kind: Service
metadata:
name: permify
spec:
selector:
app: permify
ports:
- name: http
port: 3476
targetPort: 3476
protocol: TCP
- name: grpc
port: 3478
targetPort: 3478
protocol: TCP
type: LoadBalancer
Apply both manifests:
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml