Skip to main content

Railway Put Postgres HA, PITR, and PgBouncer in the Terminal — and Gave Agents the Keys

10 min readDora NodaDora Noda
Share
On this page

Three commands now stand between you and a production-grade Postgres on Railway: check the HA cluster, restore to 30 minutes ago, add transaction pooling. No dashboard clicks, no hand-rolled API scripts. And the --json flag on every one of them is the real announcement — Railway just conceded that the database lifecycle is an API your agents call, not a console your humans click.

A reader opened this to get a concrete answer to one question: what do Railway's new terminal-first database operations actually do, how do the same operations look when you run them declaratively on infrastructure you own, and whose API should your agents be calling? Here is that comparison, operation by operation.

Your database has a command line. Your agent noticed.

Railway's September 4, 2026 changelog shipped railway postgres in CLI 5.33 with three subcommand families covering the unglamorous middle of running a database: high availability, point-in-time recovery, and connection pooling. The headline examples fit in a tweet:

bash
railway postgres ha status --service postgres
railway postgres pitr restore --service postgres --at 30m
railway postgres pgbouncer add --service postgres --pool-mode transaction

Previously these workflows meant returning to the dashboard or writing your own API scripts. Now humans and agents get dedicated commands with project, environment, and service targeting, plus --json output built for automation. The same changelog flipped railway mcp to default to Railway's hosted MCP server at mcp.railway.com, authenticated with the existing CLI login — so the agent calling those database operations may not even be running your shell commands; it may be calling tools through the Model Context Protocol.

That pairing — a CLI that emits JSON plus a hosted MCP endpoint that speaks the same operations — is the shape of where managed database operations are going. The question for teams on owned infrastructure is what the equivalent surface looks like when there is no vendor CLI to install.

What actually shipped in the terminal

Each of the three subcommand families covers a full lifecycle, not just a status readout.

railway postgres ha converts a standalone Postgres service into an HA cluster, shows cluster health, scales cluster nodes, switches the primary, and reverts a cluster back to standalone. The follow-up releases are instructive about how seriously Railway is taking the state machine here: CLI 5.47.1 added revert and scaling fixes with staged member changes that never delete the acting primary, plus a --remove-orphans flag — and a later fix ensured ha revert never sweeps the PgBouncer pooler attached to the cluster's root. HA switchovers briefly interrupt connections; this is a real failover with real semantics, not a label change.

railway postgres pitr manages point-in-time recovery: it checks WAL archive coverage and restores to a timestamp. Under the hood, Railway's Postgres image archives every WAL segment to a private storage bucket with pgBackRest and takes rolling base backups (weekly full, daily incremental). The restore semantics matter: PITR restores create a separate service, preserving the source database. You get the recovered data as a new service next to the original, not an in-place rewind. It also manages volume backups and their schedules.

railway postgres pgbouncer adds connection pooling to a service, adjusts pool settings, scales PgBouncer replicas, and shows live pool utilization. Railway automatically rewrites variable references within the project so services that used the Postgres variables now point at the pooler — connection strings hardcoded outside Railway still need updating by hand.

Two cross-cutting details deserve attention. First, every command supports --json, and the operations compose: railway postgres history shows a persistent local ops trail so you can reconstruct which sequence of operations produced a misconfigured database — the CLI authors explicitly noted that server-side command counters were not enough for that debugging story. Second, the MySQL and Redis CLIs got HA commands in 5.46, but PITR and PgBouncer remain Postgres-only. The Postgres surface is where the investment is going.

The same three operations, declared instead of typed

On a Cluster API fleet running CloudNativePG (CNPG) — the CNCF-graduated Postgres operator — these same three operations exist as declarative objects rather than imperative commands. Here is each Railway op translated.

HA: instances plus automated failover, not a convert command. Where Railway converts a standalone service with railway postgres ha and switches primaries on demand, a CNPG Cluster declares its topology up front:

yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: postgres
spec:
  instances: 3
  storage:
    size: 20Gi
  postgresql:
    parameters:
      max_connections: "200"

Streaming replication, automated failover, and switchover are operator-reconciled state, and kubectl cnpg promote / kubectl cnpg failover perform the manual role changes that Railway's ha switchover does. The failure-domain thinking is yours to supply — anti-affinity rules across nodes or zones replace the availability posture Railway picks for you — but the primitive (declare the cluster, let a controller hold it there) is the same one running your nodes.

PITR: continuous archiving to object storage plus a ScheduledBackup, not a restore flag. Railway's pgBackRest-to-bucket pipeline has a direct CNPG analogue: continuous WAL archiving to S3-compatible object storage with scheduled base backups, declared on the cluster. Recovery to a point in time is a bootstrap stanza on a new cluster object — notably the same separate-from-source semantics Railway chose:

yaml
spec:
  bootstrap:
    recovery:
      source: postgres
      recoveryTarget:
        targetTime: "2026-09-22 10:30:00+00"

One migration note for 2026-era clusters: the in-tree barmanObjectStore backup field is deprecated (slated for removal), and current CNPG wires object-store backups through the Barman Cloud plugin with ObjectStore and ScheduledBackup companions. If your manifests still carry the old field, the upgrade runbook includes moving them.

Pooling: a Pooler CR, not an add command. CNPG's Pooler object deploys and configures PgBouncer (typically 3 instances for HA) with pool mode and sizing as spec fields:

yaml
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
  name: postgres-pooler
spec:
  cluster:
    name: postgres
  instances: 3
  pgbouncer:
    poolMode: transaction
    parameters:
      max_client_conn: "1000"
      default_pool_size: "25"

poolMode: transaction is the declarative spelling of Railway's --pool-mode transaction. Live utilization comes from the pooler's metrics endpoint rather than a CLI status command — same signal, different surface.

The honest summary: nothing Railway shipped in the terminal is capability you cannot express on owned infrastructure. The difference is who operates the control loop — Railway's backend behind a CLI, or an operator reconciling YAML on your machines.

Head to head: one table, three operations

OperationRailway CLICloudNativePG on your fleet
Enable HArailway postgres ha converts standalone to clusterinstances: 3 on the Cluster; operator builds replicas
Check cluster healthha status (+ --json)kubectl cnpg status (+ -o json); Prometheus metrics
Failover / switchoverha switchover (brief connection interruption)Automatic failover; kubectl cnpg promote/failover for manual
Revert to standaloneha revert (keeps pooler; --remove-orphans)Scale instances to 1 via GitOps
PITR coverage checkpitr status (archive + volume backups)ScheduledBackup status; object-store listing
Restore to timestamppitr restore --at 30mnew separate service, source preservedbootstrap.recovery with targetTimenew cluster, source preserved
Add poolingpgbouncer add --pool-mode transaction (rewrites project variables)Pooler CR; update connection strings via GitOps/secret rotation
Pool utilizationpgbouncer status live viewPooler metrics endpoint + dashboards
Audit trailrailway postgres history local ops trailGit history of manifests + Kubernetes events
Agent surface--json output; hosted MCP at mcp.railway.comkubectl -o json; MCP servers over kubeconfig; GitOps PRs

Three tradeoffs in that table deserve more than a row. First, restore semantics agree: both sides restore into something new rather than rewinding in place. That convergence is worth noting — the industry has collectively decided that a recovery that preserves the broken original is the safe default, and both a managed CLI and a declarative operator landed there independently.

Second, the CLI only drives Railway's substrate. Every railway postgres command is an API call against Railway's control plane; the YAML on the right runs on any Kubernetes with the operator installed — Hetzner, your basement rack, another cloud. Portability lives on the declarative side, and it always will, because the artifact is the desired state rather than a transcript of actions.

Third, speed-to-operational favors the CLI by a wide margin. Converting a standalone database to HA is one typed command with Railway handling member orchestration, staged changes, and the never-delete-the-primary invariant. The CNPG equivalent requires the operator installed, object storage wired, anti-affinity thought through, and monitoring scraped. That gap is real labor, and dismissing it is how self-hosting advocacy loses credibility. The counterweight is that the CNPG labor is paid once per fleet and versioned in Git; the CLI convenience is rented per project, per month, on one vendor's terms.

The --json is the story: whose API do your agents call?

Step back from Postgres specifically and look at what Railway actually shipped: a database lifecycle oriented around machine callers. --json on every command. A persistent ops trail an agent can read to understand what happened before it arrived. Skills published in railwayapp/railway-skills that teach agents which command family owns which engine (Postgres gets pitr, ha, pgbouncer, history; MySQL and Redis get ha and history only, with a minimum-version gate of 5.47.1 for HA mutations). And a hosted MCP server so agents do not even need the CLI installed — railway mcp proxies to mcp.railway.com under the existing login.

This is the concession that matters: the database lifecycle is becoming an API agents call, and Railway just made that official. The remaining question is whose API.

On an owned fleet, the agent surface already exists — it just looks different. kubectl -o json is the --json equivalent with three decades of ecosystem behind it. Git history plus Kubernetes events is the ops trail. And the GitOps PR is arguably a better agent primitive than a CLI invocation: the agent proposes desired state as YAML, CI validates it, a human approves the diff, and the operator converges. Railway's own railway variable edit flow — open dotenv in an editor, review the diff, confirm — is converging toward the same review-the-change shape from the imperative side.

What owned fleets mostly lack is the packaging: nobody has published the railway-skills equivalent that teaches an agent "this fleet runs CNPG; HA means editing instances; restores mean authoring a recovery bootstrap; here is the object-store layout." That is documentation and MCP-server work, not operator work — the control plane already speaks JSON. The team that writes those skills for the CNCF stack closes most of the agent-operability gap in a weekend.

So the agent question resolves to a substrate question, same as ever. If your databases live on Railway, your agents should call Railway's API — it is purpose-built, versioned, and documented for them. If your databases live on machines you own, your agents should call Kubernetes' API, through the operator's objects, with Git as the audit trail. Either way, stop designing database operations for humans clicking dashboards. The next operator to page through your runbook at 3am may not be human, and the runbook that survives is the one that is already an API.

What to steal either way

Whichever substrate you run, Railway's September 4 release sets a bar worth copying point by point. Emit machine-readable output from every database operation — --json should be table stakes, not a feature. Keep an ops trail an oncoming operator (human or agent) can read to reconstruct how the current state came to be. Restore into new resources and preserve the source; never rewind in place. And publish the agent instructions — skills, MCP tools, validated command patterns — instead of letting each agent rediscover your runbook by trial and error.

Managed CLIs will keep getting better at driving their own substrate. The durable advantage of owned infrastructure was never that typing YAML is more fun than typing commands — it is that the desired state is portable, versioned, and reviewable. Meet the managed world on machine-readability, keep the declarative core, and your fleet's database story reads the same to a human at noon and an agent at 3am.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Related articles

Give your agents a chain backend

Autonomous agents hit RPC endpoints very differently than people do. See what bex router handles on their behalf.

Read the agents guide