Skip to content

Production

Whichever deployment shape you chose - Compose, ECS, or Kubernetes - the same operational contracts apply. This page collects them in one place rather than repeating them three times.

The Shape

Production is N stateless replicas behind a load balancer, sharing one Postgres.

Gateway state is mostly cache - resolved identities, permitted tool sets, checkout headers - plus a small amount of durable data: stored OAuth tokens, the MCP endpoint catalogue, and the audit log. All of it goes through Postgres.

Any replica reading after any other replica wrote sees the latest value. There is no replication lag to reason about, no conflict resolution, and no separate cache tier to operate. The same audit table is visible from every replica’s admin console.

The replicas themselves are stateless: nothing of value lives on their local disks, so you can kill any of them at any time.

Configuration Comes From the Platform

Every replica is started with the same three environment variables - TENANT, GATEWAY_POOL_TOKEN, DATABASE_URL - and fetches everything else from the gateway pool the token belongs to. There is no configuration file, no master secret, and no per-replica state to keep in step.

That has three consequences worth planning around.

The pool token decides the deployment’s identity. Every replica in one deployment must carry a token for the same pool: the pool is what supplies the encryption key that lets replicas read each other’s sessions, so a token from a different pool means a replica that appears healthy while sharing nothing.

Settings change without a deployment. A platform administrator editing the pool in the tenant portal changes the behaviour of every running replica within backendSyncIntervalSeconds - five minutes by default. Nothing you deploy pins a setting, and the console’s read-only Settings tab is where you confirm what a container is actually running. Settings changes are recorded in the audit trail under settings.refresh, which is the one place the two halves of that story meet.

Startup depends on the tenant. A replica that cannot reach the platform, or whose token has been revoked, exits non-zero rather than starting on guessed settings. That makes tenant reachability from the replicas’ subnets a production dependency: check egress, DNS, and NAT before blaming the image. A later re-fetch failing is survivable - the settings in force stay in force and the console flags them as stale.

There is no key to generate, distribute, or rotate. The encryption key protecting stored tokens and sessions is minted by the platform per pool, is never displayed to anyone, and arrives with the pool’s settings. Rotating the pool token does not invalidate sessions - the two are deliberately decoupled precisely so that it doesn’t.

The Database

The Gateway does not embed a database. DATABASE_URL is required and the container exits at startup without it.

Postgres 14 or later, from wherever you already get databases - RDS, Cloud SQL, Azure Database, Aiven, Neon, or your own cluster. The Gateway does not care which.

Schema migrations run automatically on every start, so upgrading the image needs no separate migration step and no maintenance window:

ScenarioWhat happens
Fresh databaseThe schema is created from scratch.
Database already currentA no-op fast path.
Several replicas starting at onceMigrations are coordinated; they do not race.

What Is Stored, and What Is Encrypted

The Gateway encrypts what requires confidentiality and stores audit data in plaintext so it stays queryable.

DataAt rest in Postgres
Stored OAuth tokens, session authorizations, identity cache, permitted-tools cache, checkout headersEncrypted with a key derived from the pool’s encryption key
Cache key namesPlaintext - names, not values
Audit eventsPlaintext

Audit events are deliberately not encrypted. By the time an event reaches Postgres, the same payload has already gone to stdout and, if configured, to your SIEM - both plaintext. Encrypting one copy while leaving the others readable would buy nothing and would cost the queryability that is the reason audit lives in Postgres at all.

So if someone exfiltrates a database dump, they can read the audit history - the same content your log pipeline already holds - and they cannot read any token or credential material.

If you need at-rest encryption everywhere, use Postgres TDE or your managed service’s disk encryption. Both are transparent to the Gateway.

Timeouts at the Edge

Raise the idle and response timeouts on the MCP path. A Britive checkout that requires human approval can outlive a default 60-second timeout, and a load balancer or CDN that cuts the connection at 60s will fail approval-gated tool calls. This is a timeout to configure, not a Gateway defect - the Gateway is still waiting when the connection is closed underneath it.

For the same reason, do not buffer responses on the MCP path: MCP holds a stream open for server-to-client messages, and buffering makes a working call look like a hang.

Session affinity is not required anywhere. Every replica shares one database, so any replica can serve any request.

Upgrades

Rolling restart is safe. The load balancer drains one replica, the others keep serving, and the restarted replica reconnects to Postgres and resumes.

  1. Pull or reference the new image tag.
  2. Restart replicas one at a time.
  3. Wait for GET /healthz to return 200 on each before moving to the next.

Pin a specific image tag rather than latest. With latest, what runs after a restart is whatever was most recently published, which is not a decision you made.

A single-replica deployment has a brief outage during restart - there is nowhere to fail over to. Schedule accordingly, or run two.

Backups

All durable state is in Postgres, so backing up the Gateway means backing up that database. Use your provider’s point-in-time recovery, or pg_dump on a schedule.

There is nothing to back up on the replicas themselves.

Housekeeping

A background maintenance worker runs on every replica and sweeps two things, coordinated with database advisory locks so only one replica performs each delete:

  • Cache expiry, always on. Expired cache rows are filtered out of reads immediately and physically removed by the sweep. Durable entries such as stored tokens are never touched.
  • Audit retention, when configured. Events older than audit.retentionDays are deleted in batches to keep lock holds short. See Retention.

The audit table is the only thing that grows without bound. Set a retention period deliberately rather than discovering the default later.

Production Checklist

A deployment is ready when all of these hold:

  • GET /healthz returns 200 on every replica, through the load balancer.
  • Startup logs contain no warning about a defaulted public base URL - if one is there, the pool’s Public base URL is unset and the deployment is advertising localhost to its own clients.
  • Every replica carries a pool token for the same gateway pool.
  • Signing in survives the restart of a replica other than the one that served it.
  • The console’s Settings tab shows the values you expect, with no staleness notice.
  • The admin console lists your backends as verified.
  • TLS terminates with a certificate clients trust, and the pool’s Public base URL matches the hostname clients use.
  • The MCP path’s idle timeout exceeds your longest expected approval window.
  • audit.retentionDays is set to a period you have chosen.
  • Audit events reach your SIEM, if you configured the webhook.
  • Database backups are running and have been restored at least once in a test.

Next Steps

Last updated on