> For the complete documentation index, see [llms.txt](https://docs.p0.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.p0.dev/getting-started/deploying-the-p0-mcp-gateway.md).

# Deploying the P0 AI Gateway

Architecture and deployment of the self-hosted P0 AI Gateway and OAuth server using the batteries-included Helm chart or the Terraform module.

The P0 AI Gateway is the self-hosted runtime enforcement layer for the [P0 AuthZ Control Plane for Agents](/readme/agentic-control-plane.md). 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 the architecture of the self-hosted components and how to install them.

## Architecture

Agentic authorization spans three runtime planes: two you self-host, and the P0 AuthZ Control Plane™ for Agents, which P0 delivers as SaaS.

```mermaid
flowchart LR
  A["AI agent<br/>(MCP client)"]

  subgraph self["Your environment (self-hosted)"]
    direction TB
    GW["P0 AI Gateway"]
    OS["P0 OAuth Server"]
    UP["Upstream MCP servers"]
  end

  subgraph saas["P0 SaaS"]
    CTRL["AuthZ Control Plane"]
  end

  IDP["Your IdP<br/>(Okta, Entra ID, Google)"]
  RES["Apps & cloud resources"]
  SIEM["Logging sink / SIEM<br/>(Splunk, Elastic, Grafana, …)"]

  A -->|"OAuth token (user + agent)"| GW
  A -.->|"authenticate"| OS
  OS <-->|"federate"| IDP
  GW <-->|"policy sync, approvals"| CTRL
  GW -->|"token exchange, WIF credentials"| OS
  GW -->|"proxied tool calls"| UP
  GW -->|"audit"| SIEM
  UP -->|"access"| RES
  CTRL -->|"session identity and access provisioning"| RES
```

**P0 AI Gateway** (self-hosted). The runtime enforcement point for agent traffic. It proxies MCP tool calls to your upstream MCP servers, verifies the identity token on every call, evaluates policy, and ships audit activity to your logging sink or SIEM. For upstream servers that require just-in-time credentials, it launches an isolated, per-session container for each grant and injects short-lived credentials into it. The gateway periodically syncs MCP server definitions and tool catalogs from the AuthZ Control Plane, and calls the AuthZ Control Plane to drive the access request → approval → grant → revoke lifecycle.

**P0 OAuth Server** (self-hosted). The authorization server and credential broker. It federates with your existing IdP to authenticate the user, issues signed tokens that bind the user and agent identities together, and brokers downstream cloud credentials. When the gateway needs to reach a cloud resource, the OAuth Server mints short-lived Workload Identity Federation (WIF) credentials for that session (for example, exchanging a signed OIDC token with GCP STS or AWS `AssumeRoleWithWebIdentity`), so cloud credentials are minted per session rather than stored.

**P0 AuthZ Control Plane™ for Agents** (SaaS). The authorization and policy layer. It holds your agentic access policies, pushes server definitions to the gateway, decides whether each tool call is allowed, coordinates approvals, and provisions session identity and access in your upstream apps and cloud resources for deeper enforcement. The AuthZ Control Plane is not deployed by these charts. The self-hosted components reach it over HTTPS at your tenant URL.

## Prerequisites

* A Kubernetes cluster (EKS, GKE, or AKS).
* A block-storage `StorageClass` for the bundled PostgreSQL and VictoriaLogs. For example, `gp2` on EKS (requires the EBS CSI driver add-on), `standard-rwo` on GKE, or `managed-premium` on AKS.
* An OAuth 2.0 client (client ID and client secret) for user sign-in, registered at your identity provider. Any provider that serves an OpenID Connect discovery document works (for example, Google or Okta).
* A P0 SaaS tenant: you need your tenant's P0 URL and audience.
* Control over a public DNS record for the gateway's hostname (required for TLS certificate issuance).
* `helm` (v3, with OCI support) and `kubectl` configured against your cluster.

## Deployment options

P0 publishes three artifacts. Most deployments should use the **batteries-included Helm chart**.

| Option                                                                                                                             | What it is                                                                                                                                                                                                          | When to use it                                                                                                |
| ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **Batteries-included Helm chart**: [`p0security/agentic-gateway-stack`](https://hub.docker.com/r/p0security/agentic-gateway-stack) | An umbrella chart that deploys the gateway and OAuth server **plus** everything they depend on: Envoy Gateway, cert-manager (with Let's Encrypt), PostgreSQL, Valkey, VictoriaLogs, and an OpenTelemetry collector. | Standard installs, and any cluster that doesn't already provide these dependencies. **Recommended.**          |
| **Basic Helm chart**: [`p0security/agentic-gateway`](https://hub.docker.com/r/p0security/agentic-gateway)                          | Deploys only the gateway and OAuth server and their wiring. It does **not** include the Gateway API controller, cert-manager/TLS, PostgreSQL, Valkey, or a log sink. You bring those yourself.                      | Clusters that already manage those dependencies (for example, a platform team's shared Postgres and ingress). |
| **Terraform module**                                                                                                               | A thin wrapper that installs the batteries-included chart via a `helm_release`.                                                                                                                                     | Teams that manage infrastructure as code with Terraform.                                                      |

The rest of this guide uses the batteries-included chart.

## Install with the batteries-included Helm chart

### 1. Create the `app-secrets` secret

The chart doesn't create secrets for you. The `app-secrets` Secret must exist in the target namespace before you install. It holds the signing keys and credentials the services need:

| Key                                  | Description                                                                                                                                                                              |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `JWT_PRIVATE_KEY` / `JWT_PUBLIC_KEY` | Key pair the OAuth server uses to sign and verify tokens. Store each as **base64-encoded PEM**: the OAuth server base64-decodes both values at startup, so a raw PEM breaks the install. |
| `KMS_LOCAL_ENCRYPTION_KEY`           | 32-byte (64 hex character) key for local encryption.                                                                                                                                     |
| `REFRESH_TOKEN_SECRET`               | Base64-encoded secret (≥32 bytes) for refresh tokens.                                                                                                                                    |
| `OIDC_CLIENT_SECRET`                 | The client secret for your OAuth client at your identity provider.                                                                                                                       |
| `POSTGRESQL_PASSWORD`                | Password for the bundled PostgreSQL.                                                                                                                                                     |

Generate the signing key pair and random secrets, then create the Secret in your namespace:

```bash
# Create the namespace (the Secret must exist before you install)
kubectl create namespace p0-agentic-gateway

# RSA signing key pair for the OAuth server
openssl genrsa -out jwt-private.pem 2048
openssl rsa -in jwt-private.pem -pubout -out jwt-public.pem

# Create app-secrets with the generated keys, random secrets, and your OIDC client secret
kubectl -n p0-agentic-gateway create secret generic app-secrets \
  --from-literal=JWT_PRIVATE_KEY="$(openssl base64 -A -in jwt-private.pem)" \
  --from-literal=JWT_PUBLIC_KEY="$(openssl base64 -A -in jwt-public.pem)" \
  --from-literal=KMS_LOCAL_ENCRYPTION_KEY="$(openssl rand -hex 32)" \
  --from-literal=REFRESH_TOKEN_SECRET="$(openssl rand -base64 32)" \
  --from-literal=OIDC_CLIENT_SECRET="<your-oidc-client-secret>" \
  --from-literal=POSTGRESQL_PASSWORD="$(openssl rand -base64 24)"
```

### 2. Create your values file

{% hint style="info" %}
User sign-in federates to your identity provider over OpenID Connect. Google is the default, so the example below sets only `oidcClientId`; for any other provider, set `agenticAuthServer.oidcIssuer` to that provider's issuer URL.
{% endhint %}

Create a `my-values.yaml` with the values required for your environment:

```yaml
letsEncrypt:
  email: platform@example.com   # for TLS certificate registration
  env: staging                  # switch to "prod" once staging certs succeed

postgresql:
  storageClass: gp2             # your cluster's block-storage class

victorialogs:
  storageClass: gp2             # required: the bundled log sink is enabled by default

agentic-gateway:
  imageRegistry: p0security     # override only if self-building images

  gateway:
    className: agentic-gateway-stack # unique per release; use the release name from the `helm install` command in step 3
    host: gateway.example.com   # public hostname for the gateway
    tlsSecretName: gateway-tls

  agenticAuthServer:
    oidcClientId: <your-client-id>   # OAuth 2.0 client ID from your identity provider
    # oidcIssuer: https://idp.example.com  # your provider's issuer URL; defaults to https://accounts.google.com
    # oidcRequireEmailVerified: "true"     # for providers that omit email_verified, mark the value "false" (eg. okta)
    # oidcPrompt: "select_account"         # `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: ""                  # OAuth callback URL registered at your provider. Must be the gateway URL, https://{gateway.host} — the gateway routes only that path to the OAuth server. 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, e.g. "[^@]*@(example[.]com|other[.]test)". Empty admits every account the provider verifies
    p0Url: https://api.p0.app/o/<tenant>
    p0Audience: https://api.p0.app/o/<tenant>
    gatewayIss: https://gateway.example.com

  agenticGatewayServer:
    agenticAuthServer: http://agentic-auth-server:52701     # in-cluster URL
    p0Url: https://api.p0.app/o/<tenant>
    p0Audience: https://api.p0.app/o/<tenant>              # equal to p0Url
    gatewayIss: https://gateway.example.com
    managementAudience: https://gateway.example.com/manage # gateway address + "/manage" path
    manageGcpIssuers: https://accounts.google.com          # accept Google-issued identity tokens on /manage; required for the P0 AuthZ Control Plane to reach the gateway
    manageAllowedEmails: customer-p0-gcp-sa@p0-prod.iam.gserviceaccount.com # P0 service account allowed to call the gateway's /manage endpoint
```

If your cluster already provides any of the bundled dependencies, disable the corresponding subchart (`cert-manager.enabled`, `envoy-gateway.enabled`, `postgresql.enabled`, `valkey.enabled`, `victorialogs.enabled`) and point the services at your managed services instead.

### 3. Install the chart

```bash
helm install agentic-gateway-stack \
  oci://registry-1.docker.io/p0security/agentic-gateway-stack \
  --version <chart-version> \
  --namespace p0-agentic-gateway \
  -f my-values.yaml
```

{% hint style="warning" %}
Don't pass `--wait`. cert-manager can't issue the TLS certificate until you add the DNS record in the next step, so `--wait` would block and eventually time out.
{% endhint %}

### 4. Add the DNS record

Find the load balancer address assigned to the gateway, then create a public DNS record for your gateway host that points at it:

```bash
kubectl -n p0-agentic-gateway get gateway agentic-gateway \
  -o jsonpath='{.status.addresses[0].value}'
```

cert-manager retries certificate issuance automatically; the certificate becomes `Ready` once DNS resolves.

### 5. Verify

Confirm the pods are running:

```bash
kubectl -n p0-agentic-gateway get pods
```

Confirm DNS resolves and the certificate is `Ready` (this may take several minutes):

```bash
kubectl -n p0-agentic-gateway get certificate
```

Desired output:

```
NAME          READY   SECRET        AGE
gateway-tls   True    gateway-tls   15m
```

Once staging certificates issue successfully, promote to production certificates:

```bash
helm upgrade agentic-gateway-stack \
  oci://registry-1.docker.io/p0security/agentic-gateway-stack \
  --namespace p0-agentic-gateway --reuse-values \
  --set letsEncrypt.env=prod
```

The `prod` certificate is required for testing the backends. The `gateway-tls` certificate is reissued by cert-manager. Wait for it to be `Ready` again.

Confirm both backends respond through the gateway host over public DNS. Replace `<gateway-host>` with your gateway's public hostname:

```bash
# OAuth server (exact-match route) - expect 200
curl -sS -o /dev/null -w '%{http_code}\n' https://<gateway-host>/.well-known/oauth-authorization-server

# Gateway (catch-all route) - expect 200
curl -sS -o /dev/null -w '%{http_code}\n' https://<gateway-host>/health

# HTTP is redirected to HTTPS - expect 301
curl -sS -o /dev/null -w '%{http_code}\n' http://<gateway-host>/health
```

{% hint style="info" %}
\ On macOS, if \`dig\` resolves the gateway host but \`curl\` still fails, your machine may have cached an earlier \`NXDOMAIN\` response from before the DNS record existed.

Flush the local DNS cache, then retry:

`sudo dscacheutil -flushcache`

`sudo killall -HUP mDNSResponder`\\
{% endhint %}

## Install with the Terraform module

{% hint style="info" %}
To register the gateway with Terraform, use the [`p0_agentic_gateway` resource](https://registry.terraform.io/providers/p0-security/p0/latest/docs/resources/agentic_gateway) in the P0 Terraform provider.
{% endhint %}

The Terraform module wraps the batteries-included chart in a single `helm_release`. It requires Terraform ≥ 1.7 and the `hashicorp/helm` provider ≥ 3.0.

```hcl
module "agentic_gateway_stack" {
  source  = "p0-security/p0-oauthed-mcp/kubernetes"
  version = "<module-version>"

  release_name     = "agentic-gateway-stack"
  namespace        = "p0-agentic-gateway"
  create_namespace = true

  # Same YAML as my-values.yaml above, passed through to the chart.
  values = [file("${path.module}/my-values.yaml")]
}
```

The module passes your values straight through to the chart, so the prerequisites are the same: you still create the `app-secrets` Secret and add the DNS record yourself. Each module version pins a specific chart version. Check the module's compatibility matrix.

## Next steps

Once you deploy the gateway, register it with P0 and configure the MCP servers it fronts:

* [Agentic Gateway integration](/integrations/resource-integrations/agentic-gateway.md): register the gateway and configure upstream MCP servers.
