Deploy Postgres HA [Oct '26]
Self-healing PostgreSQL 18: automated failover + read replica. [Oct '26]
pgaf-replica
Just deployed
/data
pgaf-primary
Just deployed
/data
pgadmin
Just deployed
/var/lib/pgadmin
pgaf-monitor
Just deployed
/data
๐ข Actively maintained โ last updated Oct '26.
๐ Postgres HA Read Replica - Premium Railway Template
A highly available PostgreSQL cluster with automated failover and a streaming read replica, powered by pg_auto_failover and deployed as a single Docker image across three Railway services โ plus a pgAdmin web UI with both nodes pre-registered. One custom image, four services: monitor, primary, replica, pgAdmin. The monitor watches the primary; if it dies, the replica is promoted automatically and the old primary rejoins as a standby when it comes back. Database traffic stays on Railway's private network.
> ๐ Actively maintained โ latest update 2026-10-28: PostgreSQL 18 + pg_auto_failover 2.2; private-network app connections fixed. History: CHANGELOG.md.
Deploy and Host Postgres HA Read Replica
Deploying this template provisions a three-service PostgreSQL high-availability formation plus a pgAdmin UI service in your Railway project:
| Component | Type | Source / Image | Persistent Volume | Public URL | Description |
|---|---|---|---|---|---|
pgaf-monitor | Service | wotonews/postgres-ha:v0.3.0 | Yes (/data) | No | pg_auto_failover monitor โ the witness that arbitrates failover. |
pgaf-primary | Service | wotonews/postgres-ha:v0.3.0 | Yes (/data) | No | The primary Postgres node โ accepts all writes and reads. |
pgaf-replica | Service | wotonews/postgres-ha:v0.3.0 | Yes (/data) | No | The standby node โ hot streaming replica, promoted on failover. |
pgadmin | Service | wotonews/pgadmin-ha:v9.18-2 | Yes (/var/lib/pgadmin) | Generate one | Browser UI with both cluster nodes pre-registered (primary (writes) / replica (reads)). |
pgAdmin: one-click cluster insight
Generate a public domain on the pgadmin service and open it (credentials
below). Both nodes appear in the server tree labeled by role โ browse schemas,
run SQL, watch sessions. The cluster uses private-network trust, so pgAdmin
connects without a database password; role labels follow each host's current
role and swap after failover.
Login credentials
pgAdmin web UI (the only service meant to be public):
| URL | the pgadmin service's public domain (https://) |
| Username / Email | admin@template.local (fixed placeholder โ not a real inbox) |
| Password | the generated PGADMIN_DEFAULT_PASSWORD variable โ pgadmin service โ Variables tab |
The database nodes have no login screen and no password by design:
pgaf-primary / pgaf-replica / pgaf-monitor are private-network only
(trust authentication inside Railway's private network โ nothing is exposed
to the internet). Connect your app services with
postgresql://pgaf-primary.railway.internal:5432/postgres (writes) or
postgresql://pgaf-replica.railway.internal:5432/postgres (reads).
> The pgAdmin password is generated fresh at deploy time via ${{secret(20)}}
> and applies on first boot only; change it inside pgAdmin afterwards.
About Hosting Postgres HA Read Replica on Railway
The three cluster services run the same image and self-organize: roles derive
from Railway-injected env (RAILWAY_SERVICE_NAME โ monitor vs node,
RAILWAY_PRIVATE_DOMAIN โ node identity). The first node to join becomes
primary; the second clones it via pg_basebackup and streams WAL.
- Automated failover: the monitor health-checks the primary every second; on failure it promotes the standby โ no human intervention, no DNS tricks.
- Automatic rejoin: when the old primary returns (e.g. after a Railway
redeploy), it is demoted and re-cloned as a standby. No split-brain, no
manual
pg_rewind. - Private networking: nodes talk to each other over
.railway.internalhostnames on Railway's private network. Nothing is exposed to the public internet by default.
Why Deploy Postgres HA Read Replica?
- Sleep-at-night durability: a dead primary costs you seconds of availability, not your database.
- Read scaling: point reporting/analytics traffic at
pgaf-replica.railway.internalto keep the primary free for writes. - Railway-native: one image, three services, standard volumes โ scale the
replica tier by duplicating the
pgaf-replicaservice. - No lock-in: plain PostgreSQL 18 โ
pg_dumpand walk away.
Common Use Cases
- Production Postgres for APIs and web apps that cannot tolerate database downtime.
- Offloading heavy read/reporting queries to a hot standby.
- Zero-downtime maintenance windows (fail over, patch, fail back).
- A resilient Postgres backend for queues, analytics, or stateful workers.
Dependencies for Deploying Postgres HA Read Replica
Deployment Dependencies
- A Railway account (the template deploys three services + three volumes).
- Your application services must live in the same Railway project and environment to reach the cluster over the private network.
- No public port is exposed. To connect from inside Railway:
- Writes:
postgresql://postgres@pgaf-primary.railway.internal:5432/postgres - Reads:
postgresql://postgres@pgaf-replica.railway.internal:5432/postgres
- Writes:
๐ Architecture & Components
graph TD
App[Your App Services<br>same Railway project] -->|writes| Primary[pgaf-primary]
App -->|reads| Replica[pgaf-replica]
Monitor[pgaf-monitor] <-->|health checks + failover arbitration| Primary
Monitor <-->|health checks + promotion| Replica
Primary -->|WAL streaming| Replica
Primary --- V1[(volume /data)]
Replica --- V2[(volume /data)]
Monitor --- V3[(volume /data)]
How failover works
-
Monitor misses health checks from the primary (default: ~10s of failures).
-
Monitor promotes the standby โ it becomes the new primary.
-
Clients reading
pgaf-primary.railway.internalshould reconnect topgaf-replica.railway.internal. The recommended app pattern is the multi-host connection string (transparent failover):postgresql://postgres@pgaf-primary.railway.internal:5432,postgres@pgaf-replica.railway.internal:5432/postgres?target_session_attrs=read-write -
Old primary comes back โ demoted and re-cloned as the new standby. Roles converge without manual action.
Scaling out
Duplicate the pgaf-replica service (same image, same variables) to add more
standbys. Each new node clones the current primary and joins the formation.
โ๏ธ Configuration
Zero-configuration cluster. The pg_auto_failover services ship no variables at all โ everything self-wires from Railway-injected environment variables. The only template variable is the generated pgAdmin password:
| Service | Variable | Default | Description |
|---|---|---|---|
pgadmin | PGADMIN_DEFAULT_PASSWORD | generated (${{secret(20)}}) | Login password for admin@template.local. |
Cluster derivation (injected, not configurable through the card):
| Derived from | Used for |
|---|---|
RAILWAY_SERVICE_NAME | Role: the pgaf-monitor service runs the monitor; every other service joins as a Postgres node. |
RAILWAY_PRIVATE_DOMAIN | This node's private DNS name (NODE_HOST). |
Optional overrides (for renamed services or local Docker runs):
| Variable | Default | Description |
|---|---|---|
NODE_ROLE | derived | Force the role: monitor, primary, or secondary (primary/secondary behave identically โ the formation assigns real roles). |
NODE_HOST | derived | Override this node's DNS name. |
MONITOR_SERVICE | pgaf-monitor | Which service name runs the monitor (also derives MONITOR_HOST). |
MONITOR_HOST | pgaf-monitor.railway.internal | Override the monitor's DNS name. |
PGDATA | /data/pgdata | Postgres data directory (on the volume). |
PGPORT | 5432 | Postgres port. |
WAIT_TIMEOUT | 300 | Seconds a node waits for the monitor before exiting (restart policy retries). |
> Renaming services? Set NODE_ROLE=monitor on the monitor and
> MONITOR_HOST= on the nodes โ otherwise keep names as-is.
๐ Security model
- Inter-node auth is pg_auto_failover
trustmode, scoped to Railway's private network โ unreachable from the public internet. - Do not attach a public TCP domain to these services: that would bypass network isolation.
- To add password auth, fork the image and manage
PGUSER/passwords via pg_autoctl configuration (--auth scram-sha-256).
๐ Setup & Deployment Guide
- Click Deploy on the template card โ Railway provisions the services with their volumes and variables.
- Wait for all deployments to report success (~1โ2 minutes; the replica clones the primary on first boot).
- Connect apps to
pgaf-primary.railway.internal:5432(writes) andpgaf-replica.railway.internal:5432(reads). - Optional โ verify the formation (
railway ssh -s pgaf-monitor):pg_autoctl show stateshould list one nodeprimaryand onesecondary.
Local development: git clone && cd postgres-ha && cp .env.example .env.
๐งช End-to-End (E2E) Testing
bash e2e/run-tests.sh
Validates structure, marketplace limits, the manifest, volume wiring, and Dockerfile/entrypoint contracts; with a Docker daemon it also builds the cluster image and proves a three-node formation converges, streams, and promotes the replica after a hard primary kill. CI runs the suite on every push.
๐ Operational notes
- Cost: three always-on services + volumes; the monitor fits a small plan.
- Backups still matter: HA is not backup.
pg_dumpagainst the replica avoids load on the primary. - Upgrades: bump the image tag in
railway.json, redeploy services one at a time โ pg_auto_failover fails over during primary redeploys automatically.
Template Content
pgaf-replica
wotonews/postgres-ha:v0.3.0pgaf-primary
wotonews/postgres-ha:v0.3.0pgadmin
wotonews/pgadmin-ha:v9.18-2pgaf-monitor
wotonews/postgres-ha:v0.3.0