Install on Kubernetes
Deploy Pi Durable Platform on Kubernetes with the kubernetes launcher: images, databases, secrets, the platform's RBAC, Deployment, Service, ingress, and the agent pods it creates.
This page installs Pi Durable Platform with AGENTS_LAUNCHER=kubernetes: the platform creates a pod for each running agent and deletes it when the agent hibernates. v0.1.1 ships no manifests, so the YAML below is an example to adapt, written from what the platform needs. Names such as pi-durable-platform and agents.example.com are placeholders.
1. The images
Use the release's images, pinned by digest (the digests are on the release page). Verify their signatures with cosign v3 or newer:
for image in pi-durable-platform pi-durable-platform-runtime; do
cosign verify ghcr.io/ncecere/$image:v0.1.1 \
--certificate-identity https://github.com/ncecere/pi-durable-platform/.github/workflows/image.yml@refs/tags/v0.1.1 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
done2. Databases
Create the platform database and the agents database, and their roles, as Databases describes. The agents database's role needs CREATEROLE and CREATE on that database. Agent pods connect to the agents database with roles of their own, so it must be reachable from the agents' namespace.
3. Namespaces
Agents run in AGENTS_NAMESPACE, by default the platform's own namespace. A separate namespace for agents makes RBAC and network policies simpler:
kubectl create namespace pi-durable-platform
kubectl create namespace pi-durable-agents4. Secrets
kubectl -n pi-durable-platform create secret generic pi-durable-platform \
--from-literal=DATABASE_URL='postgres://pdp:<password>@db.example.com:5432/pdp?sslmode=require' \
--from-literal=AGENTS_DATABASE_URL='postgres://pdp_agents_admin:<password>@db.example.com:5432/pdp_agents?sslmode=require' \
--from-literal=ENCRYPTION_KEY="$(openssl rand -base64 32)" \
--from-literal=API_KEY_PEPPER="$(openssl rand -base64 32)" \
--from-literal=OIDC_CLIENT_SECRET='<from your identity provider>'Keep ENCRYPTION_KEY and API_KEY_PEPPER in your secrets store too: a database backup is useless without them. Every secret setting can also be read from a file, with <NAME>_FILE (see Configuration).
5. The platform's permissions
The platform talks to the Kubernetes API with its pod's service account token. In the agents' namespace it needs:
apiVersion: v1
kind: ServiceAccount
metadata: { name: pi-durable-platform, namespace: pi-durable-platform }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: pi-durable-platform-agents, namespace: pi-durable-agents }
rules:
- apiGroups: [""]
resources: [pods]
verbs: [get, list, create, delete]
- apiGroups: [""]
resources: [secrets]
verbs: [get, create, update, delete]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: pi-durable-platform-agents, namespace: pi-durable-agents }
roleRef: { apiGroup: rbac.authorization.k8s.io, kind: Role, name: pi-durable-platform-agents }
subjects:
- { kind: ServiceAccount, name: pi-durable-platform, namespace: pi-durable-platform }It needs nothing outside that namespace. The Agent Sandbox launcher needs other resources.
6. The Deployment and Service
apiVersion: apps/v1
kind: Deployment
metadata: { name: pi-durable-platform, namespace: pi-durable-platform }
spec:
replicas: 1
selector: { matchLabels: { app: pi-durable-platform } }
template:
metadata: { labels: { app: pi-durable-platform } }
spec:
serviceAccountName: pi-durable-platform
terminationGracePeriodSeconds: 75
containers:
- name: platform
image: ghcr.io/ncecere/pi-durable-platform:v0.1.1 # pin @sha256:… from the release
args: [serve]
envFrom: [{ secretRef: { name: pi-durable-platform } }]
env:
- { name: APP_URL, value: https://agents.example.com }
- { name: MIGRATE_ON_START, value: "true" }
- { name: OIDC_ISSUER, value: https://idp.example.com }
- { name: OIDC_CLIENT_ID, value: pi-durable-platform }
- { name: BOOTSTRAP_ADMIN_SUBJECT, value: "<your OIDC subject>" }
- { name: TRUSTED_PROXIES, value: 10.0.0.0/8 } # your ingress's addresses
- { name: AGENTS_LAUNCHER, value: kubernetes }
- { name: AGENTS_NAMESPACE, value: pi-durable-agents }
- { name: AGENTS_IMAGE, value: ghcr.io/ncecere/pi-durable-platform-runtime:v0.1.1 }
- { name: AGENTS_GATEWAY_ADDR, value: ":8099" }
- { name: AGENTS_GATEWAY_URL, value: http://pi-durable-platform.pi-durable-platform.svc.cluster.local:8099 }
ports:
- { name: http, containerPort: 8098 }
- { name: gateway, containerPort: 8099 }
readinessProbe: { httpGet: { path: /readyz, port: http } }
livenessProbe: { httpGet: { path: /healthz, port: http } }
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: [ALL] }
---
apiVersion: v1
kind: Service
metadata: { name: pi-durable-platform, namespace: pi-durable-platform }
spec:
selector: { app: pi-durable-platform }
ports:
- { name: http, port: 80, targetPort: http }
- { name: gateway, port: 8099, targetPort: gateway }Notes:
- One replica. v0.1.1 supports one platform replica. Rate limits, thread events and OAuth state go through PostgreSQL, but the idle reaper only knows the event streams open on its own replica, and several replicas haven't been tested.
AGENTS_GATEWAY_ADDRdefaults to127.0.0.1:8099, which agent pods can't reach: listen on:8099and setAGENTS_GATEWAY_URLto the address agents use, the Service's name and gateway port.- Migrations: with
MIGRATE_ON_START=truethe platform applies them at start. Otherwise runpi-durable-platform migrate(for example as a Job with the same image andargs: [migrate]) before rolling out; a platform whose database is behind refuses to start. See Upgrades and migrations. - Shutdown: on SIGTERM the platform fails readiness, waits
SHUTDOWN_DELAY(5 s), then gives requestsSHUTDOWN_TIMEOUT(60 s). MaketerminationGracePeriodSecondslonger than both. Open event streams end then; clients reconnect. - The image has no shell. Run checks with
kubectl exec deploy/pi-durable-platform -- /pi-durable-platform doctor.
7. Ingress
Route your HTTPS host to the Service's http port (8098) only. Never route the gateway port: it is for agents. The OIDC redirect address is https://agents.example.com/auth/callback. Set TRUSTED_PROXIES to the ingress's addresses so the platform sees clients' real addresses for rate limits and the audit log.
8. The agent pods
For each running agent the platform creates a Secret and a pod named agent-<id> in AGENTS_NAMESPACE, labelled app.kubernetes.io/managed-by: pi-durable-platform and pi-durable-platform/agent-id: <id>. The pod:
- runs the runtime image as user 10001, non-root, with a read-only root filesystem, all capabilities dropped, the
RuntimeDefaultseccomp profile, and a 64 MiB/tmp; - has no service account token and no service links;
- requests 50m CPU and 160 MiB memory, limited to 1 CPU and 512 MiB;
- gets its database URL and per-agent bearer token from its Secret, never from the pod spec;
- listens on port 4100 and accepts only its own token.
Optional settings: AGENTS_RUNTIME_CLASS runs the pods with a sandboxing RuntimeClass such as gVisor, and AGENTS_IMAGE_PULL_SECRET names a registry Secret in the agents' namespace. Hibernating deletes the pod and keeps the Secret; deleting the agent deletes both.
Then restrict the network: see Network isolation.
9. Check it
kubectl -n pi-durable-platform exec deploy/pi-durable-platform -- /pi-durable-platform doctorSign in as the bootstrap admin, create a team, deploy an agent and send it a message on its Test tab. A pod agent-… appears in the agents' namespace, and goes away after AGENTS_IDLE_TIMEOUT (10 minutes) without use.