Avatar of Mahmoud Abdelwahab
Mahmoud Abdelwahab

Managed Postgres on Railway

Postgres had a big year on Railway! We shipped improvements across the entire database lifecycle. Going highly available, bouncing back from a bad migrations, tracking down slow queries, and more are now just a few clicks (or one prompt) away.

Here's how it all fits together, from your first DATABASE_URL to a production cluster, whether you're in the dashboard or handing the work to your agent.

Start with a single Postgres service

Add Postgres to your project

Deploy Postgres in one click and you've got a Postgres node with persistent storage running right next to your app. Railway maintains the Postgres image behind it, built on the official one, so patches and updates come to you (more on that below).

You can connect your app to your Postgres database without copying credentials around by using reference variables, which also makes rotating those credentials painless. We recently added the ability to regenerate credentials, which allows Railway to changes the password inside Postgres and updates the connection variables together, so the database and your apps stay in sync.

Connect your app with a reference variable

The database connection itself stays private by default. New Postgres services are only reachable over the environment's private network, which keeps them off the public internet and reduces the risk of known vulnerabilities being exploited.

When something outside Railway needs access, such as a BI tool or an app hosted elsewhere, you can add a public TCP proxy. For local work, the Railway CLI connects you to the database straight from your terminal (more on that later).

Vertical scaling, metrics and connection pooling

Postgres on Railway scales vertically up to your service's resource limits without any manual resizing, and because CPU and memory are billed on usage, you pay for what the database consumes rather than for a fixed instance size. You can follow that usage in your resource and disk metrics and set up monitors that alert you when CPU, memory, or disk usage crosses a threshold you set.

Once disk usage approaches its limit, you can increase the volume size without downtime, since Railway supports live volume resizing while Postgres keeps running.

Even with enough CPU, memory, and storage, a growing app can run into the Postgres connection limit, since every service and replica you run opens connections of its own. To deal with a high number of connections, you can add PgBouncer between your apps and the database so that all those clients share a smaller pool of Postgres connections.

Add PgBouncer from the Config tab

Railway handles the configuration, provisioning the pooler and updating your services' variable references to route traffic through it. From there, you can change the pool mode and size or add PgBouncer replicas based on demand, while a separate DATABASE_UNPOOLED_URL keeps a direct connection available for migrations and anything else that needs a dedicated session.

Scaling up gives a single Postgres node more room to grow, but your app still depends on that one node staying healthy, and high availability is how you remove that single point of failure.

From single node to a highly available Postgres cluster

Convert a single-node database to an HA cluster

Converting to high availability turns the Postgres service you already run into the cluster's primary and keeps its data in place.

You pick a cluster size and Railway takes a backup and stages the cluster so you can review the changes before deploying them, while the HA conversion flow provisions each component and connects them for you. After conversion, the cluster overview shows the health and role of each member and lets you change the cluster size, perform a coordinated switchover, or return to a standalone database.

The cluster pairs Postgres streaming replicas managed by Patroni with an etcd cluster for leader election and HAProxy for connection routing. Railway wires these together and updates your services' variable references to point at the cluster, and if PgBouncer already sits in front of your database, it keeps working on the cluster, so your apps keep the same connection URL.

When the primary becomes unavailable, Patroni promotes a replica and HAProxy routes new connections to it through the same application endpoint, so your apps reach the new primary without a configuration change. Once the failed node recovers, it can rejoin the cluster as a replica.

On the application side, connections that were open during a failover drop, so your app needs reconnect and retry handling to pick up where it left off. If your workload can send reads to replicas, the open-source HA template also exposes a read-only port on HAProxy that your application connects to explicitly, while the normal endpoint keeps sending both reads and writes to the primary.

Replicas protect you from a failed node, but they also copy every write as it happens, including the ones from a bad migration, so recovering from that kind of mistake depends on having a history of your data.

Backups and point-in-time recovery

Enable point-in-time recovery on an HA cluster

Backups capture your database on daily, weekly, and monthly schedules, or on demand before a risky change, so a bad migration always has a restore point to roll back to. You can combine schedules for regular coverage, and because backups are incremental and use copy-on-write storage, you don't pay again for data that hasn't changed between snapshots.

For changes that happen between those snapshots, point-in-time recovery lets you restore to a specific timestamp. When you enable PITR in Backups, Railway configures a storage bucket and continuous write-ahead log (WAL) archiving, and pgBackRest takes weekly full backups and daily differential backups, retaining roughly 4 weeks of history.

Enabling PITR restarts a standalone database, while on an HA cluster Railway rolls through the members with a brief interruption during switchover. Recovery coverage starts with the first completed base backup and archived WAL, so PITR covers only the period after you enable it.

When you restore to a timestamp from before a bad migration, Railway creates a separate service and replays WAL up to that point, while the source database stays online.

You can then inspect the recovered records and copy back the data you need, or move your application to the restored service. A restore from an HA cluster produces a standalone database, so you can enable HA and PITR on it to keep those protections after cutover, and the recovery guide covers the full workflow.

Railway also keeps deleted volumes for a 48-hour restoration window, including volumes you delete through the API, and emails you a restoration link.

Keeping your data recoverable is one part of running Postgres over time, and keeping the image itself updated and patched is the other, which Railway can handle for you.

Automatic image updates and security patches

Automatic image updates keep your Postgres image up to date on a schedule you choose. Railway checks the configured image for changes and redeploys the service during your maintenance window, so routine updates don't depend on you remembering to check the registry.

You can set the update policy and maintenance window, skip a version you don't want, and get a notification each time Railway applies an update, which gives you control over when new releases reach the database.

On top of scheduled updates, automatic CVE patching for Postgres handles security fixes. When Railway receives a CVE notification with a known mitigation, it patches Postgres on your behalf and sends you a notification and an email.

Those updates keep you on the same major version, since a new major version changes the database's on-disk format and usually means a careful, manual pg_upgrade run.

Major version upgrades

Run a major version upgrade with pre-upgrade checks

Railway's major upgrade flow runs the upgrade process for you from the Version section of the database's Config tab. After you select a target version, a compatibility report flags blockers and warnings, such as incompatible extensions, prepared transactions, and logical replication slots, so you can resolve them before anything changes.

Once the checks pass and you confirm, Railway takes a backup, stops the database, runs pg_upgrade, and redeploys on the selected version, then takes another backup once the database is serving again.

On an HA cluster, Railway upgrades the primary and rebuilds its replicas before automatic failover resumes. In either case, the database is unavailable while the upgrade runs, so you can start it at a low-traffic time to limit the impact on your users.

After the upgrade, a limited revert window lets you restore the pre-upgrade backup if validation uncovers a problem, discarding any writes made since. Logical replication slots don't carry over, so consumers that rely on them need new slots once the upgrade finishes.

Keeping Postgres healthy and current covers the database itself, and the other half of running it well is knowing what your app is asking it to do.

Query statistics

Turn on Query Statistics to find expensive queries

The Query Statistics panel, in the database's Stats tab, lists your most expensive Postgres queries with their call counts, returned rows, and execution timings. If pg_stat_statements isn't enabled yet, the panel can turn it on for you, which restarts the database.

Because it puts frequency beside timing, the panel also helps you decide which queries to look at first. A query that takes 20 milliseconds but runs millions of times can account for more database time than an occasional slow report, and spotting it gives you a starting point for adding a missing index, changing the query, or reducing how often your application runs it.

You can work through those patterns in the dashboard or hand the investigation to your coding agent.

Postgres from your terminal and your agent

Most of the workflows in this post also run outside the dashboard, so your agent can handle them too, starting with the Railway CLI, which includes commands to inspect availability, recovery coverage, and pool utilization.

railway postgres ha status --service postgres
railway postgres pitr status --service postgres
railway postgres pgbouncer status --service postgres

The same command groups let you convert to HA, scale nodes, change pool settings, and restore to a timestamp. Project, environment, and service selectors target the database you want, while --json provides output for scripts and agents, and the Postgres CLI reference covers the full command set.

For example, this command restores the database as it was 30 minutes ago into a new service, leaving the original untouched, provided the timestamp falls within its recovery coverage.

railway postgres pitr restore --service postgres --at 30m

For direct database access, railway connect postgres opens a shell, and railway connect postgres --tunnel-only lets you connect TablePlus, DBeaver, or pgAdmin through a private SSH tunnel.

Connecting your agent takes the Railway MCP server and our agent skills, which give it platform access and the workflows for investigating your database. railway setup agent configures it for you, or you can follow the agent setup guide, and once it's connected, you can try a prompt like this one.

Check my production Postgres database for connection pressure, slow queries, and tables that need attention.

Ask your agent to find your slowest query

The agent can inspect the service and use the database analysis workflow in Railway's agent skill to investigate connections, expensive queries, blocked transactions, cache misses, and vacuum health. It can correlate those findings with resource metrics and logs, then explain what needs attention, using pg_stat_statements for query analysis when it's enabled.

You can connect your agent to the Postgres service you're already running or deploy one to try it, and the Postgres docs cover each workflow in more detail.

Final thoughts

There's plenty more coming for Postgres on Railway, and your feedback helps decide what ships next. Share it on Central Station along with what you'd like to see, whether it's a missing feature, a rough edge you've hit, or a part of running your database you'd rather hand off to your agent.