High Availability Templates
Railway can convert a single-service database deployed from your template into a replicated cluster, and convert it back. You enable this by publishing two templates and declaring how they relate: the standalone template your users deploy first, and a high availability (HA) companion template describing the cluster it becomes.
Everything the platform needs to run a conversion is declared in template metadata. Adding support for a new engine version, changing how many nodes a user may pick, or wiring a new coordination variable is a template update, not a platform change.
The two-template model
| Field | Set on | Meaning |
|---|---|---|
haTemplateCode | The standalone template's root service | The code of the HA template this service converts into |
revertsToTemplateCode | The HA template's root service | The code of the standalone template a cluster reverts back to |
haConversionConfig | The HA template's root service | Everything about the conversion: node selectors, supported versions, labels |
haConversionConfig lives on the companion, not on the standalone. The HA
template is the authority on what its own cluster supports, so a standalone
template only ever names its companion and stays otherwise unaware of the
cluster's shape.
Both templates must be published for conversion to appear. A standalone
template naming an haTemplateCode that does not resolve to a published
template offers users nothing.
Cluster roles
Every service in the HA template declares a clusterRole, which tells the
platform what the service is for:
| Role | Purpose |
|---|---|
root | The primary data node, and the service users connect to and manage |
replica | Additional data nodes replicating from the root |
internal | Coordination services that are not data nodes, such as a consensus store |
edge | The entry point in front of the data nodes, such as a proxy or load balancer |
The root is special: it is the service that already exists when a user converts, so the conversion adopts it in place rather than creating a new one. Its data, volume, connection string, and service ID survive the conversion.
Declaring the conversion
haConversionConfig describes what a user may choose when converting:
{
"description": "Run three data nodes with automatic failover.",
"replica": {
"label": "Data nodes",
"nodeLabel": "Postgres",
"description": "Nodes replicating from the primary.",
"options": [2, 3],
"defaultValue": 2
},
"internal": {
"label": "Coordination nodes",
"nodeLabel": "etcd",
"options": [3],
"defaultValue": 3
},
"supportedImageMajorVersions": [16, 17]
}Each role selector accepts:
label— the heading for the selector, usually pluralnodeLabel— the singular noun for one node of this role, used as the per-node type label in cluster viewsdescription— optional helper text under the selectoroptions— the node counts a user may pick for this roledefaultValue— the initial selection, defaulting to the first entry inoptions
Omit a role's selector entirely to hide it. A cluster whose coordination node
count is not a user's choice should simply not declare an internal selector.
Supported engine versions
supportedImageMajorVersions lists the image majors your HA template actually
publishes data-node images for. It is a gate, not a hint: conversion requires
the existing service's image major to appear in this list, and the conversion
pins the cluster's data-node images to that major.
This is also why an image whose major cannot be determined is refused. A tag
like :latest or a named tag carries no major to pin data nodes to, so the
conversion has nothing to match and declines rather than guessing.
Shipping support for a new major means adding it to this list and publishing the matching images. No platform release is involved.
Pinning to a minor version
Set pinToMinorVersion to true only when your HA image repository publishes
minor alias tags alongside major ones (for example a :8.2 tag next to :8).
When set, and when the adopted service's tag declares a minor, the conversion
pins every service sharing that image repository to the major.minor tag.
Use it when your engine's replication is not backward-compatible across minors. The published tag's existence is the compatibility proof: if the minor is not published yet, the pull fails loudly instead of producing a replica that cannot read the primary's data. Leave it unset when replication is minor-agnostic, and major-only pinning applies.
Wiring coordination variables
Clusters change shape after conversion: a user can add or remove nodes. For the
platform to re-stamp coordination variables when that happens, declare
clusterWiring on the HA template's root service, naming the variables your
images read rather than having the platform hardcode them:
| Field | What it names |
|---|---|
internalNodeNameVariable | The variable on each coordination service holding that node's own identity |
coordinatorHostsVariable | The variable on root and replicas holding the coordinator host list |
coordinatorPort | The port appended to each coordinator host |
replicaNodeNameVariable | The variable on each replica holding that replica's own identity |
dataNodesVariable | The variable on the edge service holding the data-node endpoint list |
Entry formats accept two substitutions: {host} becomes the node's private
domain reference, and {rootName} becomes the cluster root's actual service
name.
Never hardcode a template service name in these formats. clusterWiring is read
back long after conversion, by which point a user may have renamed the root
service, and a hardcoded name would resolve to nothing. Always use {rootName}.
Checklist
Before publishing, confirm that:
- Both templates are published, and each names the other
- The HA template's root service declares
haConversionConfig - Every HA service declares a
clusterRole supportedImageMajorVersionslists every major you publish data-node images for- Node count
optionsare values your engine actually supports as a quorum clusterWiringformats use{rootName}, never a literal service name- Converting and reverting both work on a service holding real data