Extensions in HA Clusters
Installing PostgreSQL extensions in a Patroni-managed HA cluster is
fundamentally different from a single-node instance. This page explains why,
and walks through the pg ha extension command subtree that handles it.
Why not pg extension install?
The single-node flow (pg extension install <instance> <ext>) works by:
- Building a
-extderived image with Pigsty packages - Stopping and recreating the container from the new image
- Editing
postgresql.confto setshared_preload_libraries - Running
CREATE EXTENSIONinside the container
In a Patroni cluster, steps 2-4 all break:
- Patroni is PID 1. Recreating a container takes that node offline. If it’s the leader, Patroni triggers a failover — the cluster reshuffles while you’re mid-install.
- Patroni regenerates
postgresql.confevery loop from the DCS. Any direct edit to the file is overwritten within seconds.shared_preload_librariesmust be set viapatronictl edit-config(which writes to the DCS). CREATE EXTENSIONmust run on the leader, which may be on a remote host — not inside any local container.
pg ha extension orchestrates all of this correctly.
How It Works
The install flow follows a specific order to avoid the pitfalls above:
The critical ordering is resume before edit-config: a paused Patroni cluster
does not apply edit-config changes. If you edit-config while paused, the
shared_preload_libraries update is silently lost.
Builtin-only fast path
If all requested extensions are builtin (contrib extensions like hstore,
uuid-ossp that ship with PostgreSQL), steps 2-4 are skipped entirely — no
image build, no container recreate, no pause/resume. The flow goes straight to
edit-config + CREATE EXTENSION.
Commands
Install
Builds the -ext image, pauses the cluster, and recreates local member containers.
- Single-host cluster (all members local): automatically runs apply (resume + edit-config + CREATE EXTENSION)
- Cross-host cluster: only performs pause + recreate, cluster stays paused. Run install on each host separately, then run
pg ha extension applyon any host to complete the installation.
Extensions are passed as a comma-separated list:
Flags:
| Flag | Default | Description |
|---|---|---|
--database |
postgres |
Target database for CREATE EXTENSION |
--auto-restart |
false |
Skip confirmation for the rolling restart triggered by edit-config |
⚠
--databaseaffects all extensions. When you specify--database, theapplyphase runsCREATE EXTENSION IF NOT EXISTSfor all installed extensions (not just the newly added ones) in the target database. For example, if the cluster has[pg_cron, pg_stat_statements, pgvector]installed, runningpg ha extension install app hstore --database mydbwill create all four extensions inmydb. The extension.sofiles are already in the image and won’t be reinstalled — only the SQL objects (functions, types, etc.) are registered in the target database.If you only want to enable an existing extension in a specific database, there’s no need to reinstall. Simply connect to that database with psql and run
CREATE EXTENSION IF NOT EXISTSdirectly.
Remove
Runs DROP EXTENSION on the leader, then updates shared_preload_libraries
via patronictl edit-config (triggers a rolling restart). Does not rebuild
the image or recreate containers — the -ext image only grows; disk reclamation
is rare and manual.
Extensions are passed as a comma-separated list:
List
Shows three views of the cluster’s extensions:
- Config (pg.yaml): the
extensionslist stored inpg.yaml - DCS (preload): the
shared_preload_librariesvalue frompatronictl show-config - Leader (installed): actual extensions from
pg_extensionon the leader
Apply
Manually triggers the second half of the install flow: resume the cluster,
run patronictl edit-config, and execute CREATE EXTENSION on the leader.
Used in cross-host clusters — after running install on each host (each
builds the image and recreates its local members), run apply once on any host
to complete the DCS update and extension creation.
⚠
--databaseaffects all installed extensions.applyrunsCREATE EXTENSION IF NOT EXISTSfor all extensions in the cluster in the specified database, not just the newly added ones.
Cross-Host Workflow
In a cross-host cluster, each host manages only its own members. The extension installation workflow splits into two phases with global pause/resume — the cluster is paused once and resumed once, not per-host:
Phase 1 — per host (sequentially): Run pg ha extension install on each host.
Each host:
- Builds the
-extimage locally (merging DCS’s existing preload list to include all packages) - Pauses the cluster (idempotent — second host won’t fail on “already paused”)
- Recreates its own local members from the new image (replicas first, leader last)
- Saves config — cluster stays paused, no resume
Recommended order: start from replica hosts. If the host with the leader runs
installfirst, recreating the leader triggers a failover (leader moves to another host). Starting from replica hosts keeps the leader in place until the very end, minimizing failover-related data sync overhead.
Phase 2 — once, on any host: Run pg ha extension apply to:
- Resume the cluster (idempotent — tolerates “not paused”)
- Update
shared_preload_librariesviapatronictl edit-config - Wait for the rolling restart
- Run
CREATE EXTENSIONon the leader
Note: After
install, the cluster is in paused state (auto-failover disabled). If you forget to runapply, runpg ha extension applyor manuallypatronictl resume <scope>to re-enable failover.
For single-host clusters (all members local), install automatically runs
the apply step — no separate command needed.
About leader drift: Leader drift cannot be completely avoided at this time. When the container on the host where the leader resides is recreated, the Patroni process stops. After the leader key in the DCS expires (TTL), even if the cluster is paused, other replicas will still detect the expired key and trigger a new election. The
replicas-firstrecreation order ensures that the host with the leader is the last one to runinstall, but it cannot prevent the leader drift itself. This is an inherent limitation of Patroni + container recreation.
The extensions Config Field
Extensions are tracked at the cluster level in pg.yaml, under the Patroni
cluster config:
This list is the source of truth for what pg ha extension list reports and
what apply installs. Both install and remove update it automatically.
shared_preload_libraries Ordering
Some extensions must appear at position 0 in shared_preload_libraries
(PostgreSQL fatals if they’re not first). pgcli tracks this as a catalog
attribute (PreloadFirst) — currently set on:
citus— distributed PostgreSQL, must be loaded before anything elsetimescaledb— time-series engine, same constraint
Additional extensions may require companion DCS parameters:
pg_cronneedscron.database_name
pg ha extension handles all of this automatically:
- Extensions with
PreloadFirstare always placed at the start of the CSV, regardless of input order - When
pg_cronis present,cron.database_nameis set to the--databasevalue (defaultpostgres) - When
pg_cronis removed,cron.database_nameis cleared
Examples
Install pg_stat_statements and pg_cron
This builds an -ext image, recreates all members (with pause/resume), sets
shared_preload_libraries=pg_stat_statements,pg_cron and
cron.database_name=mydb in the DCS, then creates both extensions in mydb.
Install Citus (must be first in preload)
Even though citus is listed second, the preload CSV is generated as
citus,pg_stat_statements — extensions with PreloadFirst are always
placed at position 0.
Remove an extension
Drops pg_cron from the leader, removes it from shared_preload_libraries,
and clears cron.database_name. Triggers a rolling restart.
Check what’s installed
Limitations
- Image rebuild is additive. Removing an extension does not rebuild the
-extimage or shrink it. To reclaim disk, manually prune old images withpodman image prune. - No per-member extension list. Extensions are cluster-wide — all members
share the same
-extimage and the sameshared_preload_libraries. CREATE EXTENSIONtargets one database. PostgreSQL extensions are per-database. To install in multiple databases, re-run with--databasepointing at each one.- Cross-host requires manual coordination. Each host must run
installbeforeapplyis run once. pgcli does not SSH into remote hosts.
See Also
- Patroni HA — cluster setup, commands, and architecture
- Extensions — single-node extension management and the Pigsty catalog
- Patroni Dynamic Configuration — DCS parameters reference