Deploy Postgres HA Read Replica
Highly available PostgreSQL with automated failover and a read replica.
pgaf-replica
Just deployed
/data
pgaf-primary
Just deployed
/data
pgaf-monitor
Just deployed
/data
๐ 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.
> The short version: one custom image, three services (monitor, primary, > replica). 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. All traffic stays on Railway's private network.
Deploy and Host Postgres HA Read Replica
Deploying this template provisions a three-service PostgreSQL high-availability formation in your Railway project:
| Component | Type | Source / Image | Persistent Volume | Public URL | Description |
|---|---|---|---|---|---|
pgaf-monitor | Service | wotonews/postgres-ha:v0.1.0 | Yes (/data) | No | pg_auto_failover monitor โ the witness that arbitrates failover. |
pgaf-primary | Service | wotonews/postgres-ha:v0.1.0 | Yes (/data) | No | The primary Postgres node โ accepts all writes and reads. |
pgaf-replica | Service | wotonews/postgres-ha:v0.1.0 | Yes (/data) | No | The standby node โ hot streaming replica, promoted on failover. |
About Hosting Postgres HA Read Replica on Railway
All three services run the same image and self-organize: roles are derived
from Railway-injected environment variables (RAILWAY_SERVICE_NAME โ monitor
vs node, RAILWAY_PRIVATE_DOMAIN โ node identity). The first Postgres node
to join the formation becomes the primary; the second clones it with
pg_basebackup and streams WAL continuously.
- 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: it's plain PostgreSQL 16 โ dump,
pg_dump, and walk away whenever you like.
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 pattern for app connections is the multi-host connection string:postgresql://postgres@pgaf-primary.railway.internal:5432,postgres@pgaf-replica.railway.internal:5432/postgres?target_session_attrs=read-write -
Old primary comes back โ pg_auto_failover demotes and re-clones it 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 on Railway. The template ships no required variables โ deployers click Deploy and everything self-wires from Railway-injected environment variables:
| 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 authentication uses pg_auto_failover's
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. Private-network access only.
- To add password auth, set
--auth scram-sha-256behavior by forking the image and managingPGUSER/passwords via pg_autoctl configuration.
๐ Setup & Deployment Guide
Deploy from the marketplace
- Click Deploy on the template card โ Railway provisions the three services with their volumes and variables.
- Wait for all three deployments to report success (~1โ2 minutes; the replica clones the primary on first boot).
- Connect your app services to
pgaf-primary.railway.internal:5432(writes) andpgaf-replica.railway.internal:5432(reads).
Verify the formation
From any of the three services (railway ssh -s pgaf-monitor):
pg_autoctl show state
# expect: one PRIMARY node and one SECONDARY node in state "primary" / "secondary"
Local development
git clone && cd postgres-ha
cp .env.example .env
๐งช End-to-End (E2E) Testing
bash e2e/run-tests.sh
Validates directory structure, marketplace publish limits, the three-service manifest, role variable wiring, and entrypoint/Dockerfile syntax. CI runs the same suite on every push.
๐ Operational notes
- Cost: three always-on services + volumes. The monitor is tiny; consider a small plan for it.
- Backups still matter: HA is not backup. Use
pg_dumpagainst the replica to avoid load on the primary. - Upgrades: pin/ 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.1.5pgaf-primary
wotonews/postgres-ha:v0.1.5pgaf-monitor
wotonews/postgres-ha:v0.1.5