Skip to main content
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

1

Add the Permify Helm repository

helm repo add permify https://permify.github.io/helm-charts
helm repo update
Confirm the chart is available:
helm search repo permify
2

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
3

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.
4

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
5

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.
6

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
replicaCount
integer
default:"3"
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.pullPolicy
string
default:"Always"
Image pull policy. Use IfNotPresent to avoid re-pulling on every pod start.
image.tag
string
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
livenessProbe.enabled
boolean
default:"true"
Enable liveness checks. Kubernetes restarts the container when consecutive failures exceed failureThreshold.
livenessProbe.initialDelaySeconds
integer
default:"60"
Seconds to wait after container start before the first liveness probe fires.
readinessProbe.enabled
boolean
default:"true"
Enable readiness checks. Kubernetes removes the pod from Service endpoints until the probe passes.
readinessProbe.initialDelaySeconds
integer
default:"5"
Seconds to wait after container start before the first readiness probe fires.
startupProbe.enabled
boolean
default:"false"
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
autoscaling.enabled
boolean
default:"false"
Enable a HorizontalPodAutoscaler for the Permify deployment.
autoscaling.minReplicas
integer
default:"1"
Minimum number of replicas kept running at all times.
autoscaling.maxReplicas
integer
default:"100"
Maximum number of replicas the HPA can scale to.
autoscaling.targetCPUUtilizationPercentage
integer
default:"80"
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

Build docs developers (and LLMs) love