When people start deploying PostgreSQL on Kubernetes, they usually focus on StatefulSets, PVCs and backup strategy. It is only when they start thinking about real failures that a much harder question surfaces: what actually happens if the primary node crashes? Who decides which node is promoted to primary, and how does that happen safely without causing split-brain? That is the point at which Patroni becomes worth understanding properly — not just at the level of “how to use it”, but at the level of “why it works”.
In this article, we will walk through how Patroni implements high availability for PostgreSQL on Kubernetes, in a way that is easy to visualize and stays close to what actually happens under the hood. The goal is not just to get Patroni running - it is to help you understand core concepts such as the leader lock, the health check loop, leader races, and failsafe mode.
Issue
Picture this: at 2 a.m. the primary node of a PostgreSQL cluster crashes. Applications start reporting connection errors and alerts pile up. The question is: who — or what — elects a new primary node before users notice anything is wrong?
PostgreSQL has no built-in HA mechanism. If the primary node crashes, the entire cluster becomes read-only or stops working altogether until a DBA intervenes manually. On Kubernetes this gets even more complicated: pods can be rescheduled at any time, IPs change, and a DBA is not always available to react within the first few critical seconds.
This is exactly the problem Patroni was built to solve. Patroni is a high-availability (HA) solution for PostgreSQL across a range of runtime environments, using automatic failover to keep a PostgreSQL cluster running even when the primary node fails. This article explains how Patroni works internally on Kubernetes: from periodic health checks and the leader lock mechanism through to how race conditions are avoided.
Summary
After reading this article, you will understand:
- How Patroni structures its health check loop to keep the cluster operating normally.
- The leader race: what qualifies a replica for promotion, and the order of election.
- How Patroni prevents split-brain using the Kubernetes resourceVersion.
- How failsafe mode works, and its limitations when the DCS fails.
- Strengths, limitations, and when to choose Patroni (or not).
Glossary of terms
PostgreSQL cluster:A group of nodes deployed as 1 primary and N replicas.

A PostgreSQL cluster architecture with 1 primary and N replicas
Race condition:A condition that occurs when several processes try to change the same shared resource at once, producing unintended results.
resourceVersion:Kubernetes uses resourceVersion to implement optimistic concurrency. When a resource is updated, Kubernetes checks whether the resourceVersion in the request matches the resourceVersion currently stored.
- If they match, the update is accepted and resourceVersion is set to the new value.
- If they do not match, Kubernetes rejects the update and returns HTTP 409.
For example: suppose a Service resource currently has resourceVersion: 308. We update that Service but send a resourceVersion other than 308 (perhaps because someone else updated it first). The result: the request returns HTTP 409 and the update fails.
Patroni architecture and how it works
1. Overview of the model
Patroni uses a DCS (Distributed Configuration Store) as the source of truth for cluster metadata. Patroni supports several types of DCS: etcd, Consul, ZooKeeper, and Kubernetes ConfigMaps or Endpoints.
Patroni is deployed on every PostgreSQL node, so each node runs its own Patroni instance. The Patroni cluster mirrors the topology of the PostgreSQL cluster: one Primary and N Replicas. The Patroni Primary holds a leader lock stored in the DCS, and the Patroni Replicas watch that lock.
The role of each Patroni instance is determined via the DCS, which directly decides the role of its corresponding PostgreSQL instance: whichever Patroni instance holds the leader lock has its PostgreSQL instance promoted to Primary; the remaining Patroni instances run in the Replica role, and their corresponding PostgreSQL instances are Replicas.

Patroni deployment model
2. Health check loop
Patroni supports two mechanisms for storing the leader lock on Kubernetes: Endpoints and ConfigMaps. This article focuses on how a Kubernetes Endpoint acts as the DCS. When a Kubernetes Endpoint is used as the DCS, the leader lock is stored as follows:
$ kubectl get ep postgres-cluster -o yaml
apiVersion: v1
kind: Endpoints
metadata:
annotations:
leader: postgres-cluster-0
renewTime: "2026-05-10T09:03:36.094563+00:00"
ttl: "30"
name: postgres-cluster
resourceVersion: "49818310"
...
The leader lock is in fact an Endpoints resource on Kubernetes, in which the renewTime annotation records the moment the Patroni Primary last updated the lock. The leader lock carries a TTL (time to live).
Patroni checks cluster state on a cycle (10 seconds by default, set by the loop_wait parameter). In each cycle, Patroni performs the following in order:
- Retrieves information from DCS.
- Check leader lock status:
- If the lock is still valid: the Primary refreshes the lock and the Replicas take no action on it.
- If the lock expires (or does not exist), the replicas all compete for a new lock — this is the leader race.
Example:The lock is renewed at 11:30:00 with a 30-second TTL. If it has still not been renewed by 11:30:30, the replicas begin a leader race.
3. Automatic failover
When the leader lock expires:
- Each Patroni replica checks whether it is eligible to become the new leader, based on three conditions:
- Lag:Replication lag does not exceed maximum_lag_on_failover.
- Timeline:The node is on a WAL timeline compatible with the cluster.
- Quorum (if synchronous mode is enabled): the node must be in the synchronous standby list, meaning it had received and written all WAL from the primary before the primary crashed.
- The first Patroni replica to acquire the lock is promoted to Patroni primary. At that point Patroni calls pg_promote() to promote the corresponding PostgreSQL instance. The remaining nodes recognize the new leader and switch to watching that node's lock.
In parallel, if the former Patroni primary recovers from the failure and detects that a new leader already exists, Patroni automatically performs a demote: it stops PostgreSQL, runs pg_rewind to align the WAL timeline with the new primary (if enabled), and then restarts the node in standby mode.
How does Patroni prevent a leader race between Patroni replicas?
Patroni uses the Kubernetes resourceVersion mechanism to ensure that no race condition occurs during leader election. Each node sends a lock update request carrying the Endpoint's current resourceVersion:
- If the resourceVersion matches, the request succeeds and the node becomes the new leader.
- If the resourceVersion does not match, Kubernetes returns HTTP 409 (StatusConflict) and the node cannot become leader.
Case 1 – multiple nodes contending for the lock:The current resourceVersion is 100. Two replicas simultaneously send a request to update the endpoint with resourceVersion = 100. The request that arrives first succeeds and that node is promoted to primary. The remaining requests receive HTTP 409 because resourceVersion has changed.

Sequence diagram for case 1
Case 2 – Slow node:resourceVersion starts at 100. Node A updates successfully and becomes the leader, and resourceVersion increases to 101. Node B is lagging; when it performs its operation it reads the latest state with resourceVersion = 101, recognizes that a valid leader already exists, and continues to run as a Replica.

Sequence diagram for case 2
Case 3 – renewal request is delayed:If the Primary's renewal request is delayed long enough for the lock to expire, a leader race is triggered and a new node is elected, changing the resourceVersion. When the Primary's old request finally arrives, Kubernetes rejects it with HTTP 409 because the resourceVersion no longer matches. The old Primary realizes it has lost the lock and automatically demotes itself to Replica.

Sequence diagram for case 3
4. Failsafe mode
Failsafe mode is the second layer of protection when the DCS is unavailable. When it is enabled, the primary can retain its role provided it can still reach every other node in the cluster through Patroni's REST API.

Sequence diagram showing how failsafe mode works in Patroni
In this state, however, failover is no longer available. If the Primary fails while Failsafe is active, no Replica is promoted to take its place. The entire cluster becomes read-only.
What to consider when deploying PostgreSQL high availability with Patroni on Kubernetes
Strengths:
- Integrates natively with Kubernetes, using Endpoints as the DCS without a separate etcd.
- Fully automatic leader election.
- Supports a range of HA options: failsafe mode, synchronous replication and custom bootstrap hooks.
- Large community, extensive documentation.
- Supports multiple DCS backends: etcd, Consul, ZooKeeper and Kubernetes Endpoints.
Limitations:
- Dependence on the DCS: if the DCS fails, the cluster can fall into a read-only state — unless failsafe mode is enabled.
- Operationally more complex than managed services (RDS, CloudSQL, AlloyDB).
- You need to understand the ttl, loop_wait and maximum_lag_on_failover parameters to configure them correctly per workload.
Conclusion
If Patroni was previously just a name that kept appearing in PostgreSQL HA diagrams, we hope this article has made how it works considerably clearer. Rather than seeing Patroni only as an “automatic failover” tool, you should now have a clearer picture of how it monitors cluster state, elects a new leader when something fails, and works with Kubernetes to avoid dangerous situations such as split-brain. With that internal behavior understood, you can approach a Patroni deployment with more confidence — and judge more accurately whether it is the right fit for your Kubernetes environment.
Fundamentally, Patroni solves high availability for PostgreSQL by continuously maintaining a leader lock in the DCS and automatically triggering an election when that lock expires. The core mechanisms work together across three layers:
- Health check loopso that cluster state is checked continuously. The Primary renews the lock, while the Replicas watch the lock to detect a failure early.
- Leader raceoccurs when the lock expires. The Replicas then compete to become the new Primary based on conditions such as replication lag, WAL timeline and quorum. At the same time, Patroni uses the Kubernetes resourceVersion to ensure that only one node can acquire the lock, which prevents split-brain.
- Failsafe Modeacts as the fallback layer when the DCS fails. In this state the primary can keep operating as long as it can still reach the entire cluster, but in exchange the system loses the ability to fail over automatically.
More broadly, Patroni suits systems that need a high degree of control and want to run PostgreSQL on Kubernetes themselves. For workloads that prioritize operational simplicity, managed services remain the option more worth considering.