Pi Durable Platformdocs

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
done

2. 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-agents

4. 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_ADDR defaults to 127.0.0.1:8099, which agent pods can't reach: listen on :8099 and set AGENTS_GATEWAY_URL to the address agents use, the Service's name and gateway port.
  • Migrations: with MIGRATE_ON_START=true the platform applies them at start. Otherwise run pi-durable-platform migrate (for example as a Job with the same image and args: [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 requests SHUTDOWN_TIMEOUT (60 s). Make terminationGracePeriodSeconds longer 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 RuntimeDefault seccomp 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 doctor

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

On this page