Self-hosted service
patroni/patroni avatar
patroni/patroni

Patroni: PostgreSQL High Availability Driven by etcd, Consul, ZooKeeper or Kubernetes

A template for PostgreSQL High Availability with Etcd, Consul, ZooKeeper, or Kubernetes

8,754 stars1,029 forksPythonMIT

At a glance

What is it?
Patroni is a Python template for building PostgreSQL HA clusters on top of an existing distributed configuration store. It is a strong fit for teams that already run etcd, Consul, ZooKeeper or Kubernetes, and a poor fit for anyone who wants a single binary with no DCS to operate.
Who is it for?
Adopt Patroni if you already operate etcd, Consul, ZooKeeper or Kubernetes and need leader election plus automatic failover for PostgreSQL 9.3 through 18. Do not adopt it if you want a single self-contained binary, or if you cannot run a second distributed system alongside your database.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Patroni actually solves, and who it is written for

PostgreSQL streaming replication gives you copies of a database. It does not give you an answer to the question every HA setup eventually asks: which node is allowed to accept writes right now, and what happens when that node stops answering. Patroni is a Python template for building that answer. The README describes it as "a Python template for building PostgreSQL high availability (HA) clusters", and the setup.py description calls it a "PostgreSQL High-Available orchestrator and CLI". Those two words, template and orchestrator, are the honest framing. Patroni does not ship a database. It manages one.

The audience is narrow and specific. You need to be running PostgreSQL 9.3 to 18 (the README states that range), you need a distributed configuration store that all nodes can reach, and you need to be comfortable operating that store independently. The supported DCS options are ZooKeeper, etcd, Consul and Kubernetes. If none of those are already part of your infrastructure, Patroni asks you to add one before it does anything useful. That is the central trade-off of the project and the reason some teams look elsewhere.

How Patroni decides who is primary

The mechanism is leader election through the DCS, not through PostgreSQL. Every Patroni node runs a process that competes for a leader lock stored in etcd, Consul, ZooKeeper or the Kubernetes API. The node holding the lock is the one Patroni promotes to primary; the others follow it. Because the lock lives outside PostgreSQL, a node that has lost network access to the DCS cannot keep claiming leadership, which is the failure mode that plain streaming replication handles badly.

The repository layout reflects this split. There is a patroni.py entry point for the node agent, a separate patronictl.py for the command line, and a patroni_raft_controller.py for a Raft-based controller, which the setup.py extras list as the raft group backed by pysyncobj and cryptography. The DCS backends are optional dependencies rather than hard requirements: setup.py maps etcd and etcd3 to python-etcd, consul to py-consul, zookeeper and exhibitor to kazoo, and kubernetes to an empty list, meaning the Kubernetes client is expected from elsewhere. That structure tells you the project is deliberately modular about where the lock lives.

The rest of the stack is conventional. haproxy.cfg sits at the repository root because Patroni deployments commonly route client traffic through HAProxy, which reads the health endpoints Patroni exposes and sends writes to whichever node holds the lock. Patroni also integrates with the Citus extension since version 3.0, per the README, and runs natively on Kubernetes.

Installing Patroni and running a first cluster

The README's macOS pre-requirements use Homebrew, and the pip path is the same on Linux. The important detail is that Patroni's DCS clients are extras, not defaults, so a bare install leaves you without a way to talk to etcd. Install the dependency group that matches your DCS:

bash
pip install patroni[dependencies]

That form is what the README shows. For a specific combination it gives this example, which pulls in the psycopg 3 driver, the etcd3 client and the AWS callbacks:

bash
pip install patroni[psycopg3,etcd3,aws]

The available extras listed in the README include etcd, etcd3, consul, zookeeper, exhibitor, kubernetes, raft, aws, systemd, all, psycopg3, psycopg2 and psycopg2-binary. On a Debian or RHEL system the README also offers the distribution packages for the driver:

bash
sudo apt-get install python3-psycopg2
sudo yum install python3-psycopg2

The README notes that external tools used by bootstrap or replica creation scripts, WAL-G for instance, must be installed separately. Nothing in the pip extras covers them.

For a first run, the README describes starting a minimal cluster from different terminals, using etcd with the v2 API enabled and two node configuration files:

bash
> etcd --data-dir=data/etcd --enable-v2=true
> ./patroni.py postgres0.yml
> ./patroni.py postgres1.yml

After that the README says to verify cluster behavior and experiment with the YAML configuration files, and to add more postgres*.yml files to scale the cluster. There is no documented single command that brings up a production cluster; the repository's docker-compose.yml is described as a demo, requiring a locally built patroni image (`docker build -t patroni .`) before `docker-compose up -d` starts a three-node PostgreSQL cluster with a three-node etcd cluster and one HAProxy node.

The Python 3.11 memory failure mode is the sharpest documented limitation

The README devotes a full subsection to a failure that is easy to misdiagnose. On systems with strict memory limits, specifically with vm.overcommit_memory=2, which the README itself calls recommended for PostgreSQL, and Python 3.11 or newer, Patroni can appear healthy while its REST API stops responding. PostgreSQL keeps running. The operating system still reports Patroni listening on the REST API port. Logs look normal, apart from possibly a single "Exception ignored in thread started by" line and a MemoryError, and the kernel log may show "not enough memory for the allocation".

The cause is a Python 3.11+ issue where starting a new thread can hang indefinitely when free memory is short. Patroni releases 4.1.1+ and 4.0.8+ reduce the impact by starting required threads early, before memory pressure builds, but the README does not claim the underlying Python issue is fixed. It offers mitigations instead: MALLOC_ARENA_MAX=1 to reduce glibc virtual memory per thread, PG_MALLOC_ARENA_MAX= to reset that value for PostgreSQL processes Patroni starts, and the thread_stack_size, thread_pool_size and restapi.thread_pool_size parameters. The README states thread_stack_size defaults to 512kB, thread_pool_size defaults to 5 (sufficient for three-node clusters), and restapi.thread_pool_size defaults to 5. It also notes that REST API requests involving SQL queries are effectively serialized because a single database connection is used, so raising restapi.thread_pool_size does not parallelize them.

This is worth reading as a design statement, not just a bug note. A health-check endpoint that hangs while the process looks alive is exactly the signal an HA system must never send, and the mitigation depends on glibc, Linux and environment variables rather than on Patroni alone.

When Patroni is the wrong tool

If you do not already run a distributed configuration store, Patroni adds one to your critical path. An etcd, Consul or ZooKeeper outage is now a Patroni outage, and you are operating two distributed systems instead of one. Teams that want failover without a second cluster to keep alive should look at a different shape of solution.

The same applies to scope. Patroni is an orchestrator, not a connection pooler, and the README does not present it as one. If your problem is too many client connections, that is a separate layer. And if you expect Patroni to provision PostgreSQL or install WAL-G, the README explicitly pushes that back onto you. The project assumes a working PostgreSQL installation and a working backup or replica creation toolchain that you have already built.

Patroni compared with repmgr and CloudNativePG

repmgr keeps its cluster state inside PostgreSQL itself, using a metadata schema and a witness node for quorum decisions. Patroni keeps that state in an external DCS. The practical difference is what fails independently: repmgr has no second system to operate, but it also has no external arbiter that can overrule a partitioned primary, which is why it relies on a witness. Patroni's external lock gives a cleaner split-brain boundary at the cost of running etcd, Consul or ZooKeeper.

CloudNativePG takes the opposite approach from Patroni's Kubernetes mode. Patroni runs natively on Kubernetes and uses the Kubernetes API as its DCS, but it remains a process you deploy and configure. CloudNativePG is built as a Kubernetes operator, where the cluster definition is a custom resource and the operator owns the lifecycle. If Kubernetes is your only platform and you want the cluster managed the Kubernetes way, the operator model fits more naturally than a Python agent reading the API. If you need the same Patroni configuration to run on bare metal and on Kubernetes, the agent model is the one that travels.

pgpool and pgbouncer are not alternatives here. They sit in the traffic path, and Patroni deployments often place HAProxy in front of the database for the same routing reason. Comparing Patroni to a pooler conflates the election problem with the connection problem.

Licence, maintenance and upgrade cost

Patroni is MIT licensed, per the repository's LICENSE file and the setup.py LICENSE string, "The MIT License". MIT is permissive: it allows modification and redistribution with the licence and copyright notice retained. It provides no patent grant and no warranty, which is the standard trade-off of permissive licensing rather than a Patroni-specific issue. The repository also carries a MAINTAINERS file and CODEOWNERS, and setup.py lists two named maintainers with contact addresses. This is not legal advice; check how MIT interacts with your own distribution obligations.

Maintenance looks current. The last push to master was on 2026-09-17, and the most recent releases are v4.1.5 and v4.0.11, both dated 2026-08-12, with v4.1.4 on 2026-07-07. The parallel 4.1.x and 4.0.x lines matter for upgrades: the README's memory-issue mitigations arrived in 4.1.1+ and 4.0.8+, so anyone on an older branch inherits the problem without the early thread startup. Upgrade cost is mostly dependency cost. The extras in setup.py are version-constrained (py-consul pinned below 1.6.0 on some Python versions, python-etcd below 0.5, ydiff below 1.5 with two excluded releases), so a Python runtime upgrade can force a dependency review before Patroni itself changes.

Editorial conclusion

Adopt Patroni if you already operate etcd, Consul, ZooKeeper or Kubernetes and need leader election plus automatic failover for PostgreSQL 9.3 through 18. Do not adopt it if you want a single self-contained binary, or if you cannot run a second distributed system alongside your database. Before committing, verify which DCS you will run, whether your Python version triggers the documented 3.11+ memory issue under strict limits, and whether the extra dependency group you need is actually installed, since pip install patroni alone does not pull in the DCS client.

Frequently asked questions

What is Patroni in PostgreSQL?

Patroni is a Python template for building PostgreSQL high availability clusters, described in setup.py as a PostgreSQL high-available orchestrator and CLI. It coordinates leader election and failover using an external distributed configuration store such as etcd, Consul, ZooKeeper or Kubernetes.

How do I install Patroni?

The README shows installing it with pip and an optional dependency group, for example pip install patroni[dependencies] or pip install patroni[psycopg3,etcd3,aws]. The DCS clients are extras, so the group you pick must match your configuration store, and external tools used by bootstrap or replica creation scripts must be installed separately.

How do I set up a Patroni cluster?

The README describes starting a minimal cluster from different terminals by running etcd with --data-dir=data/etcd --enable-v2=true and then starting ./patroni.py with separate postgres*.yml configuration files. Adding more postgres*.yml files is how the README says to scale the cluster.

How does Patroni compare with repmgr?

Patroni stores cluster state in an external DCS such as etcd, Consul, ZooKeeper or Kubernetes, which means an additional distributed system sits in the failover path. repmgr is not covered in the Patroni README, so the comparison here is limited to Patroni's own design choice of an external lock rather than in-database state.

Official sources

  1. Issues
  2. License: MIT
  3. patroni/patroni on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/patroni-patroni.svg)](https://hysenlabs.com/projects/patroni-patroni)