Back to blog

// OSSeva Blog

Migration

Patroni DCS: Moving a Cluster from ZooKeeper to etcd

Randall McClure8 min read

The short answer

Patroni keeps a PostgreSQL cluster's leader lock, member status and dynamic configuration in a distributed configuration store (DCS). The supported DCS sections are etcd (v2 API), etcd3 (v3 API), Consul, ZooKeeper, Exhibitor and Kubernetes. Patroni's built-in Raft is deprecated since Patroni 3.0.0 (January 2023). The latest release is Patroni 4.1.5, from 12 August 2026. Moving a Patroni cluster from ZooKeeper to etcd is a controlled maintenance operation, not a database migration: the Postgres data stays where it is. The Patroni docs do not publish a dedicated "change DCS" runbook, so the procedure below is built only from behaviour the docs describe, and we mark the one step that is our precaution.

How Patroni uses the DCS

Patroni runs next to PostgreSQL on every node and manages high availability for the cluster: one node is the primary, and the others run as replicas using streaming replication. The DCS is the source of truth. A node may run PostgreSQL as the primary only while it can update the leader lock in the DCS. The lock has a time-to-live (ttl, default 30 seconds). If the leader cannot renew it, the lock expires and the replicas race for it, which is how automatic failover works. Patroni also stores the dynamic configuration there, the settings applied on all cluster nodes, which you change with patronictl edit-config or the REST API.

That design is why the DCS matters so much:

  • If you lose the DCS cluster, every Patroni cluster using it goes read-only, unless DCS Failsafe Mode is enabled.
  • If the DCS loses its majority, it becomes unresponsive and Patroni demotes the current primary to avoid split-brain.
  • DCS or network issues shorter than retry_timeout (default 10 seconds) do not cause a demotion.

Patroni's FAQ says the separate DCS cluster is the price of making split-brain less likely. One DCS can serve several Patroni clusters, as long as each uses its own namespace and cluster name.

The Patroni DCS options

SectionBackendNotes from the docs
etcd3etcd, v3 APIRecommended over etcd. Uses etcd's gRPC gateway, so TLS common name authentication is not possible.
etcdetcd, v2 APIThe FAQ notes v2 is disabled by default from etcd v3.4 and removed in v3.6.
consulConsul local agentHost or URL of the agent; the ACL token needs write on the service, key and session prefixes.
zookeeperZooKeeper ensembleA list of hosts; SSL needs kazoo 2.6.0 or later; optional ACLs and auth data.
exhibitorZooKeeper via ExhibitorThe host list updates as the ensemble changes.
kubernetesKubernetes APILeader election through ConfigMaps, or Endpoints with use_endpoints.
raftBuilt-in pysyncobj RaftDeprecated since 3.0.0 but still documented.

Why teams move off ZooKeeper, and off Raft

ZooKeeper is a sound DCS, but the ensemble under Patroni is often shared with Kafka, Hadoop or Solr and is frequently an end-of-life version. ZooKeeper 3.4 to 3.7 get no community fixes, and CVE-2023-44981 has no fix for 3.4, 3.5 or 3.6. etcd is the more common choice for new PostgreSQL HA builds. If you move, land on a supported etcd: v3.5, v3.6 and v3.7 are supported, and v3.4 reached end of life with v3.4.45 on 1 June 2026. Teams on the deprecated Raft option have the same reason to move, since the docs no longer recommend it.

What the docs say about pointing Patroni at a new DCS

Three documented behaviours make a DCS move possible:

  1. A new DCS is recoverable. The FAQ covers a new DCS cluster with different endpoints: update the DCS endpoints in each node's Patroni configuration. Patroni then recreates the status information from the current state of the cluster, and recreates the dynamic configuration from patroni.dynamic.json, a backup file in each member's Postgres data directory.
  2. Pause mode protects Postgres. patronictl pause puts the cluster in maintenance mode and disables automatic failover. While paused, stopping Patroni does not stop the Postgres instance it manages.
  3. Some changes need an agent restart. Local configuration changes are applied with a reload, and environment configuration only at startup. The FAQ points to patronictl pause to avoid a failover while restarting agents.
  4. Environment variables win. Values set through environment variables take precedence over the configuration file. If a node sets PATRONI_ZOOKEEPER_HOSTS, as container images often do, editing the YAML is not enough. The etcd3 equivalents use the same names with ETCD3 in place of ETCD.
  5. bootstrap.dcs does not carry over. Values under bootstrap.dcs are only used when bootstrapping a fresh cluster. On an existing cluster, the dynamic configuration comes back from patroni.dynamic.json.

One limit is explicit: keys written with etcd's v2 API are not visible through v3, so you cannot switch from etcd to etcd3 just by editing the config file.

Step by step: ZooKeeper to etcd

  1. Build the new etcd cluster on a supported release, and use the etcd3 section, not etcd.
  2. Check the Patroni cluster is healthy. patronictl list should show one leader and every replica streaming with low lag. Check for environment variables that configure the DCS. Confirm patroni.dynamic.json exists in each data directory, and save patronictl show-config output.
  3. Pause the cluster with patronictl pause.
  4. Edit each node's Patroni configuration file. Remove the zookeeper section and add etcd3 with the new hosts. Keep scope and namespace unchanged.
  5. Restart the Patroni agents. We restart the primary's agent first, so the first member to write to the empty store is the one already running as primary. That ordering is our precaution, not a documented requirement.
  6. Verify. Run patronictl list against etcd and check every member, the leader and the timeline. Compare patronictl show-config with the saved copy. The pause flag lives in the DCS, so check whether the cluster still shows as paused; if it does not, pause it again before continuing.
  7. Repoint everything else that reads the DCS: patronictl configs on admin hosts, and any tools that watch the leader key directly.
  8. Resume with patronictl resume, then test a planned switchover.
  9. Retire the old data. Remove the Patroni znodes from ZooKeeper only after the cluster has run cleanly on etcd. Rolling back is the same procedure in reverse.
# before
zookeeper:
  hosts: ['zk1:2181', 'zk2:2181', 'zk3:2181']

# after
etcd3:
  hosts: etcd1:2379,etcd2:2379,etcd3:2379

Standby clusters need their own pass. A Patroni standby cluster replicates from another cluster and, per the FAQ, may use a completely independent DCS cluster, so you can move a disaster-recovery site to etcd before or after the primary site.

The same steps apply to a move from Raft, Consul or Exhibitor. Moving to Kubernetes is different, because it normally goes with moving PostgreSQL itself onto Kubernetes.

Checks before you start

  • Timing settings must satisfy loop_wait + 2 * retry_timeout <= ttl; the default value for each is 10, 10 and 30 seconds. Do not tighten them during the move.
  • Rehearse on a staging Patroni cluster with the same Patroni and PostgreSQL versions.
  • If Consul is a candidate, note that Consul 1.17 and later are under the BSL; see Consul end of life.

Where OSSeva fits

Patroni support from OSSeva covers the PostgreSQL clusters, the Patroni layer and the DCS move. For ensembles that stay on ZooKeeper, OSSeva ships patched, signed builds for ZooKeeper 3.4, 3.5, 3.6 and 3.7 today, supports 3.8 and 3.9, and provides VEX attestation for auditors. Start with Patch, or book a discovery call. The Patroni and ZooKeeper page shows how to check which DCS a cluster uses, and etcd extended support covers the other side of the move.

Frequently asked questions

What is the DCS in Patroni?

The distributed configuration store: the key-value store that holds the leader lock, member status and dynamic configuration for a Patroni cluster.

Which DCS should I use with Patroni?

etcd through the etcd3 section is the most common choice. Consul, ZooKeeper and Kubernetes are fully supported. Avoid the deprecated Raft option for new clusters.

Is Patroni's built-in Raft still supported?

It was deprecated in Patroni 3.0.0 (January 2023). It is still documented, but not recommended.

Can I switch from etcd to etcd3 by editing the config?

No. Keys written through the v2 API are not visible through v3, so treat it as a DCS move.

What happens to PostgreSQL if the DCS goes down?

Patroni cannot renew the leader lock, so the primary is demoted and the cluster runs read-only, unless DCS Failsafe Mode is enabled and the primary can reach all members.

Can one DCS serve several Patroni clusters?

Yes. Each Patroni cluster is stored under its own namespace and cluster name, so one etcd or ZooKeeper cluster can hold many, as long as those do not clash.

Can I temporarily disable automatic failover?

Yes. patronictl pause puts the cluster in maintenance mode, and Patroni does not promote a replica node automatically until you run patronictl resume.

What is the latest version of Patroni?

Patroni 4.1.5, released on 12 August 2026.

Tags

PatroniPostgreSQLetcdZooKeeperHigh Availability

Ready to get your open source under control?

Talk to an OSSeva engineer about CVE coverage, compliance, and migration support for your stack.