Skip to main content

Configure networking and ingress

Getting AgentArea onto a hostname takes more than an Ingress resource. Three separate things have to agree: what the Ingress routes, what URL the backend advertises for itself, and what URL the browser is redirected to for authentication. Set only the first and you get a site that loads and cannot log in. This guide also covers egress restriction for the pods that run code the platform did not write — MCP server instances and agent sandboxes — which is a different problem with a different mechanism.

Prerequisites

  • A Kubernetes install of the chart, see Kubernetes
  • An ingress controller matching the ingress.className you intend to set
  • DNS records you control for the hostnames below
  • For egress restriction: a CNI that enforces NetworkPolicy

Steps

1. Enable ingress for the three public surfaces

The chart renders one Ingress each for the frontend, the backend, and Kratos. Each is created only when ingress.enabled is true and that host is non-empty — so an empty kratos.host silently produces no Kratos Ingress, and login breaks with no error at install time.
ingress.annotations and ingress.tls are shared across all three Ingresses. There is no per-host override.

2. Set the URLs the platform advertises

These are separate from routing. They are the URLs baked into responses, and they must be what a client can reach, not what a pod can reach.
global.api.publicUrl has a fallback chain: when empty, the chart derives https://<ingress.hosts.backend.host> if ingress is enabled, otherwise the internal ClusterIP URL. The derived value assumes https. If you terminate TLS elsewhere and serve plain HTTP, set publicUrl explicitly. kratos.urls.publicBrowser defaults to kratos.urls.public. Set it separately only when pods cannot resolve the public domain — a split-horizon DNS setup, or a Tailscale-style overlay where the in-cluster name and the browser-facing name differ. global.storage.publicEndpoint is empty by default, and the platform then falls back to the internal endpoint. That works only when the object store is itself browser-reachable, which in a cluster it usually is not. Leaving it empty is the single most common cause of file uploads failing in an otherwise working install.

3. Allow browser uploads through CORS

Presigned uploads go from the browser straight to the object store, bypassing the API, so the object store needs its own CORS rule. The chart applies one to the artifacts bucket during bootstrap:
An empty allowedOrigins skips the rule entirely rather than defaulting to something permissive.
The default is localhost. A session cookie scoped to localhost is not sent to app.example.com, so login appears to succeed and every subsequent request is anonymous. Kratos also has its own CORS allow-list under kratos.config.serve.public.cors.allowed_origins, which ships pointing at staging-0.agentarea.ai. Replace it with your origins.

5. Route MCP server instances

When mcpManager.backend is kubernetes and gateway_api is in mcpManager.features.enabled, the manager creates an HTTPRoute per MCP server instance, attached to a Gateway you provide.
Instances are then reachable at https://<domain>/mcp/<instance-name>. The chart does not install Gateway API CRDs or a Gateway; both must exist already.

6. Restrict egress from untrusted pods

MCP server instances run user-supplied images. The chart ships a NetworkPolicy, on by default, that denies them every cluster-internal and link-local range while allowing DNS and the public internet.
The 169.254.0.0/16 entry covers the cloud metadata endpoint at 169.254.169.254, which is the concrete thing this policy exists to block, along with kube-apiserver access and lateral movement to other tenants’ instance pods. Ingress is deliberately left open: inbound is fronted by the Service and gateway, and cross-pod reachability is already cut by every instance’s egress deny. Add your cluster’s pod and service CIDRs if they fall outside these ranges. Some providers use 100.64.0.0/10, which the defaults do not cover. Two limits worth stating plainly:
  • This is a no-op on a cluster whose CNI does not enforce NetworkPolicy. The chart cannot detect that, and nothing warns you. The policy object exists, and untrusted pods reach the metadata endpoint anyway.
  • This is not kernel isolation. Egress rules constrain the network; they do nothing about a container escape. That is what mcpManager.runtimeClass is for, and it defaults to "".
The policy selects pods by both app.kubernetes.io/managed-by: mcp-manager and app.kubernetes.io/component: mcp-server. The component constraint is deliberate: workflow sandboxes are also manager-created, and locked sandboxes must not inherit this public-egress allowance. They are governed by mcpManager.warmPool.lockedNetworkPolicy instead, which grants no public egress at all — add stable S3 endpoint CIDRs to s3EgressCIDRs if your object store is outside the cluster. The template rejects broad public routes there.

Verify

Confirm the Ingresses exist. Three, not one:
If any of the three hosts is missing from the output, its host value is empty in values.yaml. Confirm the backend advertises the right URL — this catches the derived-value fallback silently pointing at a ClusterIP name:
Check the endpoints answer from outside the cluster:
Then log in through the browser and confirm the session survives a page reload — that is the check that catches a wrong cookieDomain, which no curl will reveal. Confirm the egress policy is both present and enforced:
The request must time out. If it returns metadata, your CNI is not enforcing the policy and the protection you think you have does not exist.

Troubleshooting

The site loads but login redirects to localhost:4433. kratos.urls.public is unset, so the chart used the internal service URL. Set kratos.urls.public and kratos.urls.publicBrowser. Login succeeds, then every request is unauthenticated. kratos.session.cookieDomain is still localhost. Set it to your parent domain. Login fails with a CORS error in the browser console. Kratos rejects the origin. kratos.config.serve.public.cors.allowed_origins ships pointing at a staging domain; replace it. File uploads fail with a DNS error on an S3 hostname. global.storage.publicEndpoint is empty and the presigned URL points at the in-cluster object store. Set it, and set global.storage.cors.allowedOrigins. Uploads reach the object store and are rejected with a CORS error. The endpoint is right but the bucket has no CORS rule. allowedOrigins was empty at bootstrap, which skips applying one. MCP instances are created but never become reachable. No Gateway matching mcpManager.gateway, or Gateway API CRDs are not installed. The manager creates HTTPRoutes that nothing programs. Provider icons are broken in the UI. API_BASE_URL resolved to an internal address. Icons are served by the backend at that URL; set global.api.publicUrl. The egress NetworkPolicy exists and untrusted pods still reach the metadata endpoint. The CNI does not enforce NetworkPolicy. Nothing in the chart detects this. Switch to a CNI that enforces it, or accept that MCP instance pods have cluster-internal network access.