For the complete documentation index, see llms.txt. This page is also available as Markdown.

Deploying the gateway

Deploy the self-hosted P0 AI Gateway and Auth Server from the P0 console, with the batteries-included Helm chart, or with the basic Helm chart.

The P0 AI Gateway is the self-hosted runtime enforcement layer for P0 Runtime Access for AI. You deploy it into your own Kubernetes cluster, where it sits in the data path between your AI agents and your MCP servers. Because it runs in your environment, your sensitive data and upstream credentials never leave your perimeter.

This guide covers how to install the self-hosted components. For how they fit together with the P0 Controller, see the architecture overview.

Prerequisites

  • An account with AWS, Google Cloud, or Azure, with billing enabled and permission to create a Kubernetes cluster. The gateway runs on your infrastructure, so P0 provisions nothing here.

  • An administrator account at the identity provider your people sign in with, such as Google Workspace, Okta, or Entra ID. You register an OAuth client there below.

  • A P0 organization. If you don't have one, see Set up P0 for your organization.

  • A dedicated Kubernetes cluster (EKS, GKE, or AKS). Some of what the chart installs applies to the whole cluster, so plan on a cluster that runs nothing else. See What the chart installs cluster-wide if you need to share one, and Cluster size for what the bundled components need.

  • A block-storage StorageClass for the bundled PostgreSQL and VictoriaLogs. A StorageClass is the cluster's recipe for handing disk to a pod that needs to keep data. For example, gp2 or gp3 on EKS (requires the EBS CSI driver add-on), standard-rwo on GKE, or managed-premium on AKS. On an EKS cluster in Auto Mode, none of these work until you create a storage class yourself. If your cluster has no storage class at all, see Evaluating without a storage class.

  • An OAuth 2.0 client (client ID and client secret) for user sign-in, registered at your identity provider. Any provider works that serves an OpenID Connect discovery document and supports standard Authorization Code with client_secret_post (for example, Google or Okta). The client must allow the openid, email, and profile scopes, which are the ones the gateway requests. On providers that have a consent screen, such as Google, configure it before you install: an unconfigured screen blocks sign-in, and the failure shows up the first time someone signs in rather than during the install.

  • kubectl, the Kubernetes command-line tool, configured against your cluster. You use it to read the gateway's address and to set your OIDC client secret after the install.

  • terraform (β‰₯ 1.7, with the hashicorp/helm provider β‰₯ 3.0). Terraform applies a configuration file describing what to install, so your gateway deployment is reproducible and reviewable. The Helm path uses helm (v3, with OCI support) instead, which installs the same packaged application from the command line.

  • Control over a public DNS record for the gateway's hostname, if you bring your own domain. With a P0-hosted domain, P0 creates the DNS record for you.

Deployment options

Option
What it is
When to use it

A guided install in the P0 console. You enter your environment's details, and P0 generates a Terraform snippet that deploys the batteries-included chart, registers the gateway, and (with a P0-hosted domain) creates its DNS record.

Standard installs. Recommended.

The umbrella chart the console installer deploys, run by hand. It deploys the gateway and Auth Server plus everything they depend on: Envoy Gateway, cert-manager (with Let's Encrypt), PostgreSQL, Valkey, VictoriaLogs, and an OpenTelemetry collector.

Installs that need values the console doesn't expose, or that must be driven entirely from your own pipeline.

Every path deploys the same workloads. The console installer differs in that it also registers the gateway with P0 as you go, so you don't configure the Gateway component separately afterward.

For every value you can set on either path, see the Helm chart values reference.

To move a running gateway to a newer chart version, see Upgrade the gateway.

Secrets the chart creates

The chart creates the app-secrets Secret for you, from a job that runs before anything that reads it.

Every key is generated. The OIDC client secret is the exception you must always set yourself, since only you have it: set your OIDC client secret gives the command. If you use your own database instead of the bundled PostgreSQL, replace the generated POSTGRESQL_PASSWORD with the real one the same way.

Key
Description

JWT_PRIVATE_KEY / JWT_PUBLIC_KEY

Key pair the Auth Server uses to sign and verify tokens. PEM block or base64-encoded PEM block.

KMS_LOCAL_ENCRYPTION_KEY

32-byte (64 hex character) key for local encryption.

REFRESH_TOKEN_SECRET

Base64-encoded secret (β‰₯32 bytes) for refresh tokens.

POSTGRESQL_PASSWORD

Password for the bundled PostgreSQL.

OIDC_CLIENT_SECRET

The client secret for your OAuth client at your identity provider. Starts as a placeholder, but sign-in fails until you set the real value.

The job runs again on every upgrade, filling in only missing keys. It checks existing keys and fails the install or upgrade if it finds malformed values. The job log names the key to fix or remove.

The same job also creates a second, separate braekhus-client-jwk Secret holding the EC P-384 keypair (BRAEKHUS_JWK_PRIVATE/BRAEKHUS_JWK_PUBLIC) for the gateway's outbound management tunnel. Registering the tunnel with P0 is required for every new gateway. Read back the public half and paste it into the console:

See Provide the management tunnel public key for where this goes in the console installer.

To manage the app-secrets Secret yourself, from External Secrets, Vault, or a sealed-secrets pipeline, set agentic-gateway.secretsJob.enabled: false in your values and create your namespace with app-secrets in it before installing.

What the chart installs cluster-wide

Most of what the chart deploys lives in its own namespace. Three things do not, and they are why the cluster should be dedicated:

  • Envoy Gateway v1.2.0, which routes outside traffic to the gateway, and its GatewayClass.

  • cert-manager v1.16.1, which obtains and renews the gateway's TLS certificate from Let's Encrypt.

  • 24 custom resource definitions that those two depend on, covering the Gateway API, Envoy, and cert-manager. Installing them needs cluster administrator rights.

The chart also creates cluster roles and two admission webhooks for those components.

A cluster that already runs cert-manager is the case to watch: a second copy conflicts with the one already there, and the conflict surfaces as certificates that never issue. Another ingress controller is usually fine. Envoy Gateway only serves the GatewayClass the chart creates, so a controller that answers to a different class runs alongside it. An existing Envoy Gateway is the exception, since both copies claim the same controller name.

If you must install onto a shared cluster, you can turn the bundled copies off and let the cluster's own components serve the gateway:

These flags only stop the chart installing its own copies. The cluster has to already run a cert-manager that can issue the chart's Certificate resources and a Gateway API controller that serves the GatewayClass named above, or certificates never issue and the gateway never gets an address. GatewayClass names are cluster-wide, so give each install its own, such as the Helm release name.

Set these in the module's values block for a console install, or in my-values.yaml for a Helm install. You then own keeping those components at versions the gateway works with.

Cluster size

The batteries-included chart runs twelve long-lived pods: the gateway and Auth Server, the braekhus management tunnel client, PostgreSQL, Valkey, VictoriaLogs, an OpenTelemetry collector, Envoy Gateway and its proxy, and cert-manager's three. Install and upgrade also run a few short-lived Jobs.

Two pods claim persistent volumes from the StorageClass above:

Volume
Default size

PostgreSQL

8Gi

VictoriaLogs

20Gi

VictoriaLogs holds the gateway's tool-call logs, so it is the volume that grows with use.

Most of these pods set no CPU or memory requests, so size the cluster by pod count rather than by CPU. A single node is enough to try the gateway, with one caveat on EKS: small instance types cap pods per node, and a t3.medium allows 17. The stack's eleven pods plus the cluster's own system pods can exceed that, and the extras then sit Pending with no resource pressure to explain it.

Evaluating without a storage class

Some clusters have no storage class at all, and no way to add one quickly. You can still stand the gateway up to try it, with both bundled datastores writing to scratch space on the node instead of to a volume.

In the console installer, choose No persistent storage (proof of concept only) for Gateway storage. If you are installing the chart directly, turn persistence off for both datastores in my-values.yaml:

VictoriaLogs drops its oldest logs once they fill 80% of its space, which victorialogs.storageSize sets and which defaults to 20Gi. It always keeps the two newest days, so if those outgrow that 80%, the pod is evicted and its logs are lost. Neither datastore reserves disk on the node, so leave the node room for both, or a full disk can evict the gateway too.

After the data is erased, PostgreSQL restarts empty. The gateway and the Auth Server keep reporting healthy, because their health checks do not query the database, so the first sign of trouble is usually a failed sign-in. Wait for the database, then restart both to rebuild the schema:

Once your cluster has a storage class, you can move the gateway onto volumes. In my-values.yaml, set persistence.enabled back to true and name the class in storageClass for both datastores. Kubernetes cannot move a running datastore from scratch space onto a volume, so the upgrade fails with a Forbidden: updates to statefulset spec error unless you delete both datastores first:

Then run the upgrade. If it already failed, delete the datastores and run it again. The gateway is down until the upgrade recreates them, and the database comes back empty, so run the two recovery commands above once it finishes.

EKS Auto Mode storage

EKS Auto Mode clusters need a storage class that you create yourself. Auto Mode ships none, so the chart's PostgreSQL and VictoriaLogs volumes stay unbound until you add one.

What makes this hard to spot is that the cluster looks ready. The gp2 class listed on an Auto Mode cluster comes from standard EKS cluster setup rather than from you, and Auto Mode can't provision against it. So kubectl get storageclass shows something plausible and the install proceeds. The pods then sit in Pending with their claims never bound, and nothing reports an error, because from Kubernetes' side nothing's gone wrong.

To check whether this applies to your cluster:

If the only entry is gp2, or the list is empty, create a storage class before installing. AWS's Create a storage class guide names it auto-ebs-sc:

The provisioner must be ebs.csi.eks.amazonaws.com. That's Auto Mode's built-in block storage, which isn't the same thing as the EBS CSI driver add-on. Don't install that add-on on an Auto Mode cluster.

If you would rather not change the cluster default, leave the annotation out. In either case you must name the class explicitly: enter auto-ebs-sc in the Kubernetes storage class field of the console installer, or set it in my-values.yaml if you're installing the chart directly. The chart does not fall back to the cluster default.

Stream timeouts for long-running calls

MCP tool calls and A2A agent calls stream their responses back through Envoy. The gateway doesn't send heartbeats on these streams, so a stream carries no traffic while a tool or agent is working on a reply. Envoy closes any stream that stays idle longer than its stream idle timeout, which is 5 minutes by default. The chart disables Envoy's request timeout (requestTimeout: "0s"), but that is a separate setting: the stream idle timeout still applies, and it cuts off any call that goes 5 minutes without sending anything back. The agent sees the call fail partway through, even though the upstream server may still be working.

Set streamIdleTimeout to much larger than the longest call you expect. If your slowest tool call or agent task can run for 20 minutes, set it to an hour rather than 25 minutes, so a slower-than-usual call still finishes:

Set this in the module's values block for a console install, or in my-values.yaml for a Helm install. The value takes a duration such as 30m or 1h. The chart applies it as an Envoy Gateway BackendTrafficPolicy only when the value is set, so it has no effect if you've turned off the bundled Envoy Gateway in favor of another Gateway API controller. In that case, raise the equivalent timeout in that controller instead.

A2A calls have idle timeouts of their own in the gateway, which reset each time the stream carries data. To let a long A2A call through, raise these alongside streamIdleTimeout:

Value
Default
Purpose

agenticGatewayServer.a2aProxyIdleTimeoutMs

60 seconds

Idle limit between the gateway and the A2A bridge.

agenticGatewayServer.a2aUpstreamIdleTimeoutMs

60 seconds

Idle limit between the A2A bridge and the upstream agent.

agenticGatewayServer.a2aAgentHandlerIdleTimeoutMs

5 minutes

How long the gateway keeps an idle upstream agent connection. Keep it equal to streamIdleTimeout.

These take milliseconds, and sit under agentic-gateway: in the batteries-included chart's values, next to gateway. MCP calls have no gateway-side timeout, so for MCP, streamIdleTimeout is the only limit.

User sign-in settings

Every path defaults to Google as the upstream identity provider. To use another provider, set these under agenticAuthServer, in the module's values block for a console install or in your values file for a Helm install:

Value
Purpose

oidcIssuer

Your provider's issuer URL. Defaults to https://accounts.google.com.

oidcRequireEmailVerified

Set to "false" for providers that omit email_verified (for example, Okta).

oidcPrompt

The prompt sent on the authorize leg. Set to "" to send none, so the IdP reuses an existing session instead of showing an account chooser.

oidcRedirectUrl

The OAuth callback URL registered at your provider. Must be the gateway URL. This is also the default, so leave it unset unless your provider needs it stated explicitly.

openIdDomain

Login allowlist: a regex matched against the whole id_token email claim, for example `[^@]*@(example[.]com

Next steps

Once the gateway is deployed, federate cloud credentials so it can obtain short-lived credentials for the agents it serves. For AWS, install AWS OIDC. For Google Cloud, install GCP Workload Identity Federation. Install the one that matches the servers you are about to configure, or both.

Last updated