β οΈ This is under development. All code within this repository is released under analpha
tag. Please do not use these versions unless advised by the Railway team.
- Replication support via repmgr
- Automatic SSL cert generation and renewal
There is no Railway template for this yet. In its current iteration, this is meant to replace the base image of an existing Postgres service deployed on Railway. Doing so will transparently upgrade the Postgres service to support replication, and allow you to create read replicas of the primary.
The setup steps here are manual. We're working on baking this into the product in the future.
Before proceeding:
- Please ensure you have backups enabled
- If you have requested a volume size increase for your Postgres service in the past, please let us know so we can ensure your read replica has the same volume size
- Your primary Postgres service will be re-deployed. This will incur some downtime (<1 minute) while the new deployment is being created
On the Postgres service you wish to designate as the primary:
-
Change the image under
Settings -> Source -> Source Image
. Use the image corresponding to the current version you are on:Current Source Image... Change Source Image To... ghcr.io/railwayapp-templates/postgres-ssl:15 ghcr.io/railwayapp-templates/postgres:pg15.12-alpha ghcr.io/railwayapp-templates/postgres-ssl:16 ghcr.io/railwayapp-templates/postgres:pg16.8-alpha ghcr.io/railwayapp-templates/postgres-ssl:17 ghcr.io/railwayapp-templates/postgres:pg17.4-alpha -
Make the following changes to your service variables:
Action Variable Value + Add RAILWAY_PG_INSTANCE_TYPE PRIMARY + Add REPMGR_USER_PWD Strong password (tip: use CMD+K -> Generate 32 character secret on Railway dashboard to generate a strong value that you can paste) Your primary's service variables should resemble this diff:
DATABASE_PUBLIC_URL="postgresql://${{PGUSER}}:${{POSTGRES_PASSWORD}}@${{RAILWAY_TCP_PROXY_DOMAIN}}:${{RAILWAY_TCP_PROXY_PORT}}/${{PGDATABASE}}" DATABASE_URL="postgresql://${{PGUSER}}:${{POSTGRES_PASSWORD}}@${{RAILWAY_PRIVATE_DOMAIN}}:5432/${{PGDATABASE}}" PGDATA="/var/lib/postgresql/data/pgdata" PGDATABASE="${{POSTGRES_DB}}" PGHOST="${{RAILWAY_PRIVATE_DOMAIN}}" PGPASSWORD="${{POSTGRES_PASSWORD}}" PGPORT="5432" PGUSER="${{POSTGRES_USER}}" POSTGRES_DB="railway" POSTGRES_PASSWORD="redacted" POSTGRES_USER="postgres" RAILWAY_DEPLOYMENT_DRAINING_SECONDS="60" SSL_CERT_DAYS="820" + RAILWAY_PG_INSTANCE_TYPE="PRIMARY" + REPMGR_USER_PWD="$SOME_STRONG_PASSWORD"
-
Click "Apply Changes". This will re-deploy your Postgres service with the new image and configure it as the primary. Once the new deployment is Active, follow the steps below to create read replicas
If you're using a Custom Start Command to pass in extra options to Postgres,
change wrapper.sh
to start.sh
like so:
- wrapper.sh postgres --port=5432 -c 'max_connections=100' -c ...
+ start.sh postgres --port=5432 -c 'max_connections=100' -c ...
If your custom start command contains any of the config below (e.g. `-c wal_level='replica', etc.), please remove them as they are already configured in the image:
wal_level
wal_log_hints
max_replication_slots
max_wal_senders
hot_standby
archive_mode
archive_command
-
Duplicate the primary service from above (right click service ->
Duplicate
) -
Adjust the duplicated service's variables:
Action Variable Value + Add OUR_NODE_ID A unique integer β₯2. The primary node is always node1
and subsequent nodes must be numbered starting from2
. If this is your first read replica, set it to2
, otherwise increment it by 1 for each additional read replica you create.+ Add PRIMARY_REPMGR_USER_PWD Set to the value of REPMGR_USER_PWD
on the primary instance using Reference Variables+ Add PRIMARY_PGHOST Set to the PGHOST of the primary instance using Reference Variables + Add PRIMARY_PGPORT Set to the PGPORT of the primary instance using Reference Variables ~ Change RAILWAY_PG_INSTANCE_TYPE READREPLICA ~ Change DATABASE_URL Replace ${{POSTGRES_PASSWORD}}
inDATABASE_URL
with${{PGPASSWORD}}
~ Change DATABASE_PUBLIC_URL Replace ${{POSTGRES_PASSWORD}}
inDATABASE_URL
with${{PGPASSWORD}}
~ Change PGPASSWORD Set to PGPASSWORD of the primary instance using Reference Variables - Remove POSTGRES_PASSWORD - Your read replica's service variables should resemble this diff:
+ DATABASE_PUBLIC_URL="postgresql://${{PGUSER}}:${{PGPASSWORD}}@${{RAILWAY_TCP_PROXY_DOMAIN}}:${{RAILWAY_TCP_PROXY_PORT}}/${{PGDATABASE}}" - DATABASE_PUBLIC_URL="postgresql://${{PGUSER}}:${{POSTGRES_PASSWORD}}@${{RAILWAY_TCP_PROXY_DOMAIN}}:${{RAILWAY_TCP_PROXY_PORT}}/${{PGDATABASE}}" + DATABASE_URL="postgresql://${{PGUSER}}:${{PGPASSWORD}}@${{RAILWAY_PRIVATE_DOMAIN}}:5432/${{PGDATABASE}}" - DATABASE_URL="postgresql://${{PGUSER}}:${{POSTGRES_PASSWORD}}@${{RAILWAY_PRIVATE_DOMAIN}}:5432/${{PGDATABASE}}" + OUR_NODE_ID="2" PGDATA="/var/lib/postgresql/data/pgdata" PGDATABASE="${{POSTGRES_DB}}" PGHOST="${{RAILWAY_PRIVATE_DOMAIN}}" - PGPASSWORD="${{POSTGRES_PASSWORD}}" + PGPASSWORD="${{Postgres-Primary.PGPASSWORD}}" PGPORT="5432" PGUSER="${{POSTGRES_USER}}" POSTGRES_DB="railway" POSTGRES_USER="postgres" - POSTGRES_PASSWORD="redacted" + PRIMARY_PGHOST="${{Postgres-Primary.PGHOST}}" + PRIMARY_PGPORT="${{Postgres-Primary.PGPORT}}" + PRIMARY_REPMGR_USER_PWD="${{Postgres-Primary.REPMGR_USER_PWD}}" - REPMGR_USER_PWD="redacted" RAILWAY_DEPLOYMENT_DRAINING_SECONDS="60" + RAILWAY_PG_INSTANCE_TYPE="READREPLICA" - RAILWAY_PG_INSTANCE_TYPE="PRIMARY" SSL_CERT_DAYS="820"
-
Click "Apply Changes". This will deploy your read replica. The initial setup may take some time depending on the size of your primary instance. You can monitor the logs of the read replica to see when it is ready
To create more read replicas, repeat the steps above on Creating Read Replicas.
- Failovers are not supported at this time
- Read replicas share the same credentials as the primary instance. You can connect to it using the same credentials as the primary instance
- Read replicas are strictly read-only. You cannot write to them. Attempting
to do so will result in a Postgres error
ERROR: cannot execute INSERT in a read-only transaction
- If you are using a connection pooler such as pgbouncer, consider setting a separate instance for your read replica as well, or use a replication-aware connection pooler such as pgpool
- If your ORM supports it, you can use the read replica's connection string directly for read-only statements (e.g. Prisma)
Replication is set up using repmgr. All repmgr commands
are available, however promotion (standby promote
etc.) will not work at
this time.
- Set up Railway CLI (https://docs.railway.com/guides/cli]
- Right click the service you wish to run repmgr commands on
- Click "Copy SSH Command" (https://docs.railway.com/reference/cli-api#ssh)
- Paste into a Terminal
This will open a shell into your service's container. From there, you can run:
# Use the Postgres user:
su -m postgres
# Make repmgr use the repmgr user:
export PGPASSWORD="${REPMGR_USER_PWD}"
repmgr -U repmgr -f /var/lib/postgresql/data/repmgr/repmgr.conf cluster show
# To run other repmgr commands:
repmgr -U repmgr -f /var/lib/postgresql/data/repmgr/repmgr.conf <REPMGR_COMMAND>
(You may notice a bash: /root/.bashrc: Permission denied
error after
running su -m postgres
. This is expected and can be safely ignored.)
π Initial release (π head=e6d147 @ railwayapp-templates/postgres-ssl
)
- Adds support for setting up replication via repmgr
- Postgres versions are now pinned to their respective latest minor versions
and
bookworm
deb images SSL_CERT_DAYS
is deprecated. This value will be ignored if provided. All server certificates will default to 730 days validity- Root CA certificate expiry is now separated from server certificate expiry
- Adds
DEBUG_MODE
environment variable. When set, the container willsleep infinity;
so it remains shell-accessible instead of starting Postgres - Require
RAILWAY_VOLUME_NAME
andRAILWAY_VOLUME_MOUNT_PATH
environment variables to be set to avoid deployments without a volume on Railway