PostgreSQL can replicate, and it can be promoted. What it cannot do is decide, on its own and safely, which node should be the primary after the current one stops answering. That decision needs a third party that survives the failure and that every node agrees with. Patroni is that decision maker.
Its own documentation calls it “a template for high availability (HA) PostgreSQL solutions using
Python” and is explicit that it “is far from being a one-size-fits-all or plug-and-play replication
system”. Take that literally. Patroni is a Python daemon running beside a PostgreSQL instance it
starts, stops, configures and monitors. PostgreSQL still does the replication; Patroni writes
postgresql.conf, pg_hba.conf and the recovery settings, watches the local postmaster, and records
what it sees in a distributed configuration store shared by every node. Supported PostgreSQL versions
are 9.3 to 18.
Every node runs the same loop: read cluster state from the store, look at the local Postgres, act. One node holds a lease on a key that says “I am the leader”. The others do not, so they run as replicas and watch that key. That is the entire mechanism, and almost every operational question about Patroni reduces to a question about that lease.
The distributed configuration store
Patroni keeps cluster state in a DCS: etcd, Consul, ZooKeeper (directly or via Exhibitor), or the
Kubernetes API. A Raft implementation embedded in Patroni also exists but is marked deprecated in
the configuration reference; do not build on it. Under a namespace (default /service) and a scope
(the cluster name) the store holds the members, the leader key, the dynamic configuration, the sync
state, and the failsafe member list.
The store is not a convenience; it is where the safety comes from. Consensus stores answer “did this write succeed against a majority?”, and only a system that can answer that can distinguish “the primary is dead” from “I cannot currently see the primary”. A node cannot make that distinction locally, which is why a script that pings the primary and promotes a replica produces two primaries.
Patroni and DCS nodes are decoupled, so there is no minimum number of database nodes: a primary-and-one-standby cluster is documented as “perfectly fine”, with no redundancy during a failover until the failed node rejoins. The DCS is different: it “has to run with 3 or 5 nodes for proper consensus and fault tolerance”. One DCS cluster can hold state for many Patroni clusters via distinct namespace/scope pairs.
What happens when the DCS is unreachable
This is the behaviour most worth understanding before you deploy, because it looks like a bug the first time you see it.
A node is allowed to run Postgres as the primary only if it can update the leader lock in DCS. In case the update of the leader lock fails, Postgres is immediately demoted and started as read-only.
The reasoning is stated just as plainly: a lock update can fail because of network partitioning or because the DCS is down, “it is impossible to distinguish between these two from a single node, and therefore Patroni assumes the worst case - network partitioning.” If the network is partitioned, some other node may take the lock and promote. So the old primary demotes before the lock expires. You lose writes; you do not get two primaries. Patroni is choosing consistency, deliberately.
The documented escape hatch is failsafe_mode (default false), set through the dynamic
configuration in the DCS. With it enabled, a primary whose lock update failed for a reason other than
a version/value/index mismatch may keep running as primary if it can reach every known member via
POST /failsafe on the REST API. The member list lives in a permanent /failsafe key maintained by
the leader, and only members present in that key may win a leader race. Replicas treat an incoming
POST /failsafe as evidence the primary is alive and cache that for ttl seconds, so they do not
start an election even after the leader key has expired.
Why all members rather than a quorum? Because “the view on the quorum might be different from the perspective of DCS and Patroni”: nothing forces Patroni members to be distributed the way DCS members are, so a majority of Patroni nodes could sit on the losing side of a partition. The cost is that failsafe mode stops helping as soon as one member is unreachable: if a node is terminated while the DCS is down, the check fails on the next loop and Postgres is demoted until the DCS returns.
The leader lease, and the timing arithmetic
Four parameters decide how the cluster behaves in the seconds around a failure. They are dynamic
configuration, stored in the DCS and applied cluster-wide, so change them with
patronictl edit-config, not by editing YAML on each node.
| Parameter | Default | Minimum | What it controls |
|---|---|---|---|
ttl |
30 s | 20 | Lifetime of the leader lock. “Think of it as the length of time before initiation of the automatic failover process.” |
loop_wait |
10 s | 1 | How long the heartbeat loop sleeps between iterations |
retry_timeout |
10 s | 3 | Timeout for retries of DCS and PostgreSQL operations. “DCS or network issues shorter than this will not cause Patroni to demote the leader.” |
maximum_lag_on_failover |
not documented as a default; the shipped postgres0.yml sets 1048576 (1 MiB) |
- | Maximum bytes a follower may lag and still take part in the leader election |
primary_start_timeout |
300 s | - | How long a primary may spend recovering from failure before failover is triggered. 0 means fail over immediately once a crash is detected |
The documentation attaches a hard rule to the first three, as a warning:
loop_wait + 2 * retry_timeout <= ttl
The defaults sit exactly on the boundary: 10 + 2 × 10 = 30. That is not slack, it is the budget. The
leader must be able to lose one DCS attempt, retry for retry_timeout, sleep loop_wait, and still
renew before the lock expires. Break the inequality and you have built a cluster that can lose its
lock during ordinary DCS latency. The result is a demotion, or an election.
Each direction of tuning costs something specific. A shorter ttl detects failure sooner and
shortens the write outage, but tolerates less: a network blip, a paused VM or a DCS leader election
all start to read as a dead primary, and holding the inequality means shrinking loop_wait and
retry_timeout too, which raises DCS traffic and leaves less patience for a slow store. A longer
ttl inverts both. Cutting retry_timeout alone starts elections sooner after real trouble and
demotes a healthy primary during brief hiccups.
Two documented bounds belong in the runbook. Worst-case failover time for a primary failure is
loop_wait + primary_start_timeout + loop_wait, unless primary_start_timeout is zero, in which case
it is just loop_wait. And in asynchronous mode, data loss is worst-case bounded by
maximum_lag_on_failover bytes plus whatever was written in the last ttl seconds (loop_wait/2 in
the average case), because the primary’s WAL position is not sampled in real time. Typical
steady-state replication delay is described as well under a second.
By default the leader election ignores replica timelines; check_timeline: true prevents a node on a
different timeline from the former primary becoming leader. Per-node tags control eligibility:
nofailover: true (default false) or a failover_priority of zero or below keeps a node out of the
race, which is how you stop a reporting replica in another data centre from being promoted.
Failover, switchover and the old primary
Patroni distinguishes the two operations by cluster health, and patronictl enforces the
distinction only by convention.
switchover |
failover |
|
|---|---|---|
| Intended when | The cluster is healthy: there is a leader, and in synchronous mode a sync standby is available | The cluster is unhealthy: no leader, or no synchronous standby in a synchronous cluster |
--candidate |
Required | Required |
--leader / --primary |
Accepted, names the node to demote | Not applicable |
--scheduled |
Supported, takes a timestamp or now |
Not supported |
| Risk | Controlled demotion, then promotion | “Triggering a failover can cause data loss depending on how up-to-date the promoted replica is” |
failover also permits promoting an asynchronous node when synchronous mode is enabled: it is the
manual override for when synchronous mode has correctly refused to promote anything. Nothing prevents
failover on a healthy cluster; the documentation simply recommends switchover there.
When the old primary comes back, its timeline has diverged from the new one if it accepted any write
that was not replicated. postgresql.use_pg_rewind (default false) tells Patroni to run
pg_rewind on the former leader as it rejoins, rewinding it to the point of divergence so it can
stream from the new primary without being rebuilt. It only works if the cluster was initialised
with data page checksums (--data-checksums to initdb) and/or wal_log_hints = on. Decide at
bootstrap: turning checksums on later means rewriting the cluster.
Two settings decide what happens when rewind is not possible, and both default to false:
| Setting | Default | Behaviour when true |
|---|---|---|
remove_data_directory_on_rewind_failure |
false | Remove the data directory and recreate the replica. When false, Patroni tries to follow the new leader anyway |
remove_data_directory_on_diverged_timelines |
false | Remove and recreate when timelines have diverged and the former primary cannot stream from the new leader. Useful where pg_rewind cannot be used |
Leaving both at false is the conservative choice (nothing deletes your data directory), but it is
also how you end up with a node that reports Replica in patronictl list and never reaches
streaming. Watch for that after a failover.
Synchronous mode
Asynchronous replication is the default and it can lose committed transactions: anything not yet replicated to the promoted standby “remain[s] in a ‘forked timeline’ on the primary, and are effectively unrecoverable”.
synchronous_mode (off, on, or quorum) changes the contract. With it on, the leader manages
synchronous_standby_names itself, only the last known leader or one of the synchronous replicas may
join the leader race, and Patroni “will not promote a standby unless it is certain that the standby
contains all transactions that may have returned a successful commit status to client”. The price is
stated in the same breath: “the system may be unavailable for writes even though some servers are
available.”
There is a sharp edge here. With synchronous_mode: on alone, when no suitable standby is available
the primary still accepts writes. It stops guaranteeing they are replicated, and if it then
fails, no standby will be promoted. When a standby crashes, commits block until the next loop
iteration switches the primary to standalone mode: worst case ttl seconds, average loop_wait/2. A
clean shutdown or restart of a standby does not cause that interruption, because the standby signals
the primary to release it from synchronous duties first.
synchronous_mode_strict closes that gap, and is what to use when a commit must be durable on two
nodes. It prevents Patroni switching synchronous replication off when no synchronous candidate is
available. “As a downside, the primary is not be [sic] available for writes (unless the Postgres transaction
explicitly turns off synchronous_commit), blocking all client write requests until at least one
synchronous replica comes up.” Plainly: strict mode converts a durability risk into an availability
outage. That is the right trade for a ledger and the wrong one for a session store.
| Setting | Default | Effect |
|---|---|---|
synchronous_mode |
off |
on enables Patroni-managed synchronous replication; quorum uses quorum-based commit |
synchronous_mode_strict |
off | Never fall back to asynchronous; block writes instead |
synchronous_node_count |
1 | Number of synchronous standbys Patroni maintains; reduced automatically if higher than the number of eligible nodes |
maximum_lag_on_syncnode |
-1 | Bytes a sync standby may lag before being swapped for a healthy async one. 0 or below disables swapping |
primary_stop_timeout |
unset | Seconds to wait when stopping Postgres in synchronous mode before SIGKILL to the postmaster. Only effective with synchronous_mode enabled |
Use at least three data nodes with synchronous replication, so losing one host does not cost write
availability. Note the honest caveat: even synchronous_mode_strict can lose a transaction if the
backend is cancelled while waiting for the replication acknowledgement, because the change becomes
visible to other backends before it is replicated. Tag slow or distant standbys nosync: true so
they are never chosen as the synchronous one.
Watchdog and fencing
Demoting on lock-update failure is only a guarantee if Patroni is running well enough to demote. The documentation lists the cases where it is not: Patroni crashed, was killed, or was starved by load, a paused VM or other infrastructure trouble; or PostgreSQL is too slow to shut down. In any of those the lock expires, another node promotes, and the old primary is still accepting commits. That is split brain, and DCS correctness cannot prevent it from inside a process that is not being scheduled.
A watchdog device is the answer: a hardware or software timer that resets the machine if it stops
receiving a keepalive. Patroni activates the watchdog before promoting, and with
watchdog.mode: required a node refuses to become leader if activation fails. It is disabled again on
demotion, and while the cluster is paused. Only the Linux watchdog device interface is supported;
modprobe softdog plus chown postgres /dev/watchdog is enough for most uses, and soft_noboot=1
makes it log to dmesg instead of rebooting while you test.
| Setting | Default | Meaning |
|---|---|---|
watchdog.mode |
automatic in the shipped example |
off, automatic (use if available), required (refuse leadership without it) |
watchdog.device |
/dev/watchdog |
Device path |
watchdog.safety_margin |
5 s | Seconds between watchdog trigger and leader key expiry. -1 sets the timeout to ttl // 2 |
The arithmetic behind the default: with loop_wait: 10 and ttl: 30, the watchdog expires 5 seconds
before the lock, giving the HA loop at least ttl - safety_margin - loop_wait = 15 seconds to
complete before a forced reset, and at least ttl - safety_margin - loop_wait - retry_timeout = 5
seconds to terminate all client connections when the DCS is unavailable. The documented residual risk
is a Patroni process suspended at exactly the wrong moment, between the lock update and the keepalive;
safety_margin: -1 removes it at the cost of a much shorter watchdog timeout, which in turn argues
for a larger ttl and smaller loop_wait and retry_timeout.
For application-level fencing there is postgresql.pre_promote, a script run after the leader lock is
acquired and before promotion. Use it for anything outside PostgreSQL that must be released before
the new primary starts.
A three-node configuration
Nodes pg1 - pg3 on 10.0.2.11 - .13, a three-node etcd cluster on 10.0.3.11 - .13. This is
/etc/patroni/patroni.yml on pg1; on the other nodes change name, restapi and postgresql
addresses, and data_dir if it differs. The bootstrap section is read only when the cluster is
first initialised; afterwards use patronictl edit-config.
scope: appdb
namespace: /service/
name: pg1
restapi:
listen: 10.0.2.11:8008
connect_address: 10.0.2.11:8008
authentication:
username: patroni
password: rest-api-password
etcd3:
hosts:
- 10.0.3.11:2379
- 10.0.3.12:2379
- 10.0.3.13:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
primary_start_timeout: 300
synchronous_mode: true
synchronous_node_count: 1
failsafe_mode: true
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
max_connections: 300
wal_level: replica
hot_standby: "on"
max_wal_senders: 10
max_replication_slots: 10
wal_log_hints: "on"
wal_keep_size: 2GB
archive_mode: "on"
archive_timeout: 1800s
archive_command: 'test ! -f /var/lib/pgsql/wal_archive/%f && cp %p /var/lib/pgsql/wal_archive/%f'
pg_hba:
- local all all peer
- host all all 10.0.2.0/24 scram-sha-256
- host replication replicator 10.0.2.0/24 scram-sha-256
initdb:
- encoding: UTF8
- data-checksums
postgresql:
listen: 0.0.0.0:5432
connect_address: 10.0.2.11:5432
data_dir: /var/lib/pgsql/18/data
bin_dir: /usr/pgsql-18/bin
pgpass: /var/lib/pgsql/.pgpass_patroni
pg_ctl_timeout: 60
remove_data_directory_on_rewind_failure: false
remove_data_directory_on_diverged_timelines: false
authentication:
superuser:
username: postgres
password: superuser-password
replication:
username: replicator
password: replication-password
rewind:
username: rewind_user
password: rewind-password
parameters:
unix_socket_directories: /var/run/postgresql
watchdog:
mode: required
device: /dev/watchdog
safety_margin: 5
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
Notes on the choices. data-checksums at initdb time plus wal_log_hints: "on" is what makes
use_pg_rewind: true effective; neither can be added later without rebuilding. pg_ctl_timeout
defaults to 60 seconds. watchdog.mode: required means a node with no working /dev/watchdog will
not promote. That is correct for a cluster where split brain is worse than an outage, and it is worth
verifying before you rely on it, because a node that silently cannot promote is a node you do not have.
failsafe_mode: true requires every member to be running a current Patroni version. Validate before
starting:
patroni --validate-config /etc/patroni/patroni.yml
Diagnosing with patronictl
patronictl talks to both the REST API and the DCS, so it needs the ctl/restapi authentication
settings and the DCS section. On a cluster node, the daemon’s own configuration file will do.
patronictl -c /etc/patroni/patroni.yml list appdb
patronictl -c /etc/patroni/patroni.yml list appdb -e -t # extended, timestamps
patronictl -c /etc/patroni/patroni.yml list appdb -w 2 # refresh every 2s
patronictl -c /etc/patroni/patroni.yml topology appdb # tree view, shows cascading
patronictl -c /etc/patroni/patroni.yml history appdb
patronictl -c /etc/patroni/patroni.yml switchover appdb --leader pg1 --candidate pg2
patronictl -c /etc/patroni/patroni.yml failover appdb --candidate pg2
patronictl -c /etc/patroni/patroni.yml reinit appdb pg3 --wait
patronictl -c /etc/patroni/patroni.yml edit-config appdb -s ttl=45
list is where diagnosis starts. Its columns:
| Column | Meaning |
|---|---|
Cluster |
Cluster name; the system identifier is shown in the header in pretty format |
Member |
Node name, as set by name |
Host |
Where the member is |
Role |
Leader, Standby Leader (leader of a standby cluster), Sync Standby, or Replica |
State |
Postgres state: running, streaming (replica receiving WAL), in archive recovery, stopped, crashed, among others |
TL |
Current timeline |
Receive LSN / Receive Lag |
Last WAL location received and synced to disk, and the lag against its upstream in MB |
Replay LSN / Replay Lag |
Last WAL location replayed, and the lag in MB |
Pending restart |
* when a Postgres parameter change needs a restart to take effect |
Three readings matter more than the rest. A replica in state running rather than streaming is not
replicating. After a failover that usually means a rewind that did not happen. Divergent TL values
across members mean timelines have forked, which is what history is for: it lists each failover and
switchover with the timeline, LSN, reason taken from the Postgres .history file, and timestamp.
And a large Replay Lag with a small Receive Lag is a replica that is receiving WAL but not
applying it, typically blocked by a conflicting query.
reinit rebuilds a replica’s Postgres instance from scratch. It is the blunt fix for a node that
will not rejoin, and with no member names given it does nothing, which is a deliberate safety.
--from-leader takes the base backup from the leader directly instead of following the configured
replica creation method.
Before a major version upgrade or any manual promotion, pause the cluster:
patronictl -c /etc/patroni/patroni.yml pause appdb
patronictl -c /etc/patroni/patroni.yml resume appdb
While paused, Patroni stops changing PostgreSQL’s state: it keeps member keys and the leader lock
updated, does not start a stopped Postgres, does not stop Postgres when Patroni itself stops, and does
not promote anything if there is no leader lock. It warns about parallel primaries without demoting
one, and the watchdog is disabled. Check list before resume.
Where Patroni sits
Patroni decides which node is the primary. It does not tell clients where that node is. Each tool here solves exactly one problem:
| Component | Question it answers | Knows about PostgreSQL roles |
|---|---|---|
| Patroni | Which node should be the primary? | Yes, it decides |
| HAProxy | Which node should this connection go to? | Only by asking Patroni’s REST API |
| keepalived | Which host owns this IP address? | No |
| PgBouncer | How many backends does Postgres actually need? | No |
The conventional layout is Patroni for failover, HAProxy routing on Patroni’s health endpoints,
keepalived keeping the HAProxy address available, and PgBouncer for pooling. The REST API is the seam:
it publishes each node’s role over HTTP, which is how a proxy that cannot read the PostgreSQL wire
protocol still routes writes correctly. The endpoint table (/primary, /replica, /health,
/leader and the traps in choosing between them) is on the
HAProxy page. Read it before pointing any
health check at port 8008, because /health returns 200 on every healthy node regardless of role.
Put HAProxy in front of per-node PgBouncers, not a central PgBouncer in front of HAProxy, or a
failover leaves pooled server connections attached to a demoted primary. Note also
postgresql.proxy_address: it exists so a pooler beside Postgres can be advertised for service
discovery, not to route anything. For what Patroni is managing underneath - replication slots,
wal_level, checkpoints and vacuum - see PostgreSQL.
What changed, and in which release
| Release | Date | Change that affects this page |
|---|---|---|
| 3.3.0 | Apr 2024 | nostream tag; pending_restart_reason shown in patronictl list and /patroni; JSON logging |
| 4.0.0 | Aug 2024 | Breaking: role is primary not master in the DCS, REST API and callbacks; /metrics no longer reports patroni_master; patronictl no longer accepts --master. Quorum-based failover added; member_slots_ttl added. Upgrading is only reliable from 3.1.0 or newer |
| 4.1.1 | Apr 2026 | Thread pools introduced for Python 3.11+ compatibility; Kubernetes leader update retried within retry_timeout on a transient 403 instead of demoting immediately |
| 4.1.4 | Jul 2026 | Watchdog no longer stopped until client backends have exited; monitoring-query statement timeouts fall back to the cached role rather than demoting the primary |
| 4.1.5 | Aug 2026 | Compatibility with PostgreSQL 14.24, 15.19, 16.15, 17.11, 18.6; --validate-config accepts synchronous_mode: quorum |
The 4.0.0 terminology change is the one that breaks on upgrade, because it reaches outside Patroni:
monitoring alerting on patroni_master, Kubernetes selectors matching role=master, and callback
scripts branching on role=master all stop working. On Kubernetes, kubernetes.leader_label_value: master preserves the old label while you migrate.
If you run Patroni on Python 3.11 or newer with vm.overcommit_memory=2 (itself recommended for
PostgreSQL), read the warning at the top of Patroni’s documentation index. A CPython bug can leave the
REST API unresponsive while Patroni looks healthy and PostgreSQL keeps running, which behind HAProxy
health checks means a primary no proxy will route to. Releases 4.1.1+ and 4.0.8+ reduce the exposure by
starting threads early.
Diagnostics
The primary tool is patronictl list:
patronictl -c /etc/patroni/patroni.yml list
This shows the cluster topology: which node is Leader, which are Replicas, lag in bytes or seconds, and the timeline. If no node shows as Leader, the cluster is in a degraded state.
No leader after a restart. Check whether the DCS (etcd/Consul/ZooKeeper) is reachable. Patroni
cannot promote without updating the leader key. Run etcdctl endpoint health or equivalent. If the
DCS quorum is lost, Patroni deliberately refuses to promote, which is correct behaviour, not a bug.
Primary demoted unexpectedly. Check the Patroni log for INFO: Lock owner: ... and
WARNING: Loop time exceeded. Common causes: DCS latency spike (network or disk on the etcd node),
ttl set too low relative to actual round-trip times, or a garbage-collection pause in a JVM-based
DCS client. Increase retry_timeout and ttl if the demotion was transient.
Replica not streaming. Run patronictl list and check the Lag column. Then on the replica:
patronictl -c /etc/patroni/patroni.yml show-config
# Confirm replication slots are in use
psql -c "SELECT * FROM pg_stat_replication;" -h <primary>
If the slot exists but WAL is not shipping, check max_wal_senders and whether pg_hba.conf
allows replication connections from the replica IP.
Failover hangs. If patronictl failover or switchover stalls, check whether the DCS leader
key is being updated. A switchover requires the old primary to voluntarily step down; if it is
unreachable (or its Patroni process is dead), use patronictl failover (not switchover). If
failover still does not proceed, check whether failsafe_mode is active and the DCS is degraded.
Timeline divergence after failover. After an unclean failover, the old primary may have a
different timeline. Patroni handles this via pg_rewind if configured (use_pg_rewind: true). If
rewind fails, the node must be re-basebackuped:
patronictl -c /etc/patroni/patroni.yml reinit <cluster> <node>
Pause mode confusion. patronictl pause disables automatic failover but does not stop
PostgreSQL. Use it for maintenance (major upgrades, manual recovery). Remember to patronictl resume
afterwards, or the cluster has no automatic protection.
Where Patroni is the wrong tool
- Read/write splitting or load balancing. Patroni publishes roles; it does not proxy connections.
Use HAProxy against
/primaryand/replica, or Pgpool-II if you need per-statement routing. - Connection pooling. Patroni does not reduce backend count. That is PgBouncer.
- Backups. Patroni can call a backup tool as a replica-creation or bootstrap method, but it takes
no backups and offers no point-in-time recovery. A cluster that fails over but cannot restore is not
protected against a bad
DELETE. - Moving a virtual IP. Patroni has no VRRP. If clients connect to an address instead of through a proxy, you still need keepalived or a managed load balancer.
- Deployments without a properly sized DCS. The store must run on 3 or 5 nodes. A single-node etcd makes the consensus store a single point of failure, and every DCS restart demotes the primary.
- Multi-primary or write scaling. One node accepts writes. Distributed writes mean Citus (which Patroni can manage) or a different architecture, not a tuning exercise.
- Manual, staged operations. During a major version upgrade or corruption recovery the automation works against you. Use paused mode; do not fight the HA loop.
Patroni release support
3 supportedThree branches currently receive coordinated releases.
| Release | Latest | Released | End of life | Status |
|---|---|---|---|---|
4.1 | 4.1.5 | - | not published | Supported |
4.0 | 4.0.11 | - | not published | Supported |
3.3 | 3.3.11 | - | not published | Supported |
Data as of 2026-10-01 from github.com. Refresh with node scripts/fetch-versions.mjs.
PostgreSQL release support
5 supported| Release | Latest | Released | End of life | Status |
|---|---|---|---|---|
18 | 18.6 | Sept 2025 | Nov 2030 | Supported |
17 | 17.11 | Sept 2024 | Nov 2029 | Supported |
16 | 16.15 | Sept 2023 | Nov 2028 | Supported |
15 | 15.19 | Oct 2022 | Nov 2027 | Supported |
14 | 14.24 | Sept 2021 | Nov 2026 | Supported |
13 | 13.23 | Sept 2020 | Nov 2025 | End of life |
12 | 12.22 | Oct 2019 | Nov 2024 | End of life |
11 | 11.22 | Oct 2018 | Nov 2023 | End of life |
Data as of 2026-10-01 from endoflife.date. Refresh with node scripts/fetch-versions.mjs.
Where Patroni sits
How this component relates to the rest of the stack. Items without a link are not covered here yet.