PostgreSQL high availability for GitLab with Patroni and PgBouncer

How to build the database tier of a self-managed GitLab HA layout with Patroni, PgBouncer and a planned PostgreSQL version, and when a managed cloud database is the better fit.

On this page

PostgreSQL high availability for GitLab with Patroni and PgBouncer: what the database tier must do

If you are planning a self-managed GitLab for 3,000 users or more, the database tier has to keep serving requests when a node is lost. GitLab's reference architectures recommend PostgreSQL with Patroni for high availability at that size, and the layout only works if each component does its own job. PostgreSQL replication keeps a standby current, Patroni decides when that standby becomes the primary, and PgBouncer keeps the number of client connections under control while the cluster changes underneath it. Each piece is necessary, and none of them covers for the others.

PostgreSQL on its own does not detect a failed primary or promote a standby. Its high availability documentation supports hot standby servers and both synchronous and asynchronous replication, but the PostgreSQL failover documentation makes clear that detecting failure and notifying the standby needs external tools. Patroni is that external tool. It uses a distributed configuration store (etcd, Consul, ZooKeeper or Kubernetes) to hold leader state and exposes a REST API for health checks, cluster status, switchover and failover. Patroni is a template that you configure per deployment, so the configuration store, node addresses, replication mode and failover behavior are decisions your team owns, not defaults you can assume.

GitLab's reference architectures keep stateful services such as PostgreSQL and Redis out of Kubernetes. The reference architecture documentation says running them inside Kubernetes is not supported, and the Cloud Native Hybrid pattern places them on VMs or managed services. Before you commit to a design, confirm the exact Patroni, PostgreSQL and GitLab versions your deployment will run against the current GitLab reference architecture page, and confirm the PgBouncer placement and pool sizing with your GitLab documentation for that release. Those checks belong in the design review, not after the first failover test.

Self-managed Patroni cluster or a managed cloud database

Running Patroni and PgBouncer on virtual machines you control keeps the database inside your network and under your change process. Patroni manages leader election and failover through a distributed configuration store, so your team owns the cluster end to end, from the PostgreSQL build to the failover runbook.

  • Upgrades: Your team plans minor releases, which replace executables and require a restart, and major upgrades, which use pg_upgrade, pg_dumpall with restore, or logical replication.
  • Backups: Your team designs and tests point-in-time recovery and restore drills, since GitLab's own backup task does not replace database-level recovery.
  • Failover testing: Your team schedules controlled switchovers and failure tests through the Patroni REST API, and records the results.
  • On-call: Someone in your organization is paged when the leader, the configuration store or the connection pooler fails.
  • Cost driver: Staff time and VM capacity recur every month, and the effort rises with the number of clusters and the maturity of your automation.

Confirm with the GitLab reference architecture documentation which database layout your user count requires before you size these VMs.

Database tier options and when each fits

This table compares the database choices that shape a GitLab HA database tier: PostgreSQL major version, replication mode, Patroni's configuration store, PgBouncer pooling and whether to run failover yourself or use a managed service. Read each row against your recovery objectives and your team's operating capacity. The main takeaway is that PostgreSQL end-of-support dates set your upgrade clock, and any setting GitLab's documentation does not confirm for your release needs checking before the build starts.

DecisionOptionTrade-offWhen it fits
PostgreSQL 18Supported until November 14, 2030Longest support window. Confirm GitLab supports it for your release before choosing itNew builds that need the longest runway and can verify GitLab compatibility first
PostgreSQL 17Supported until November 8, 2029Shorter runway than 18 and one major version behind the newest releaseEstates that want a supported release now, with a planned move to 18 later
PostgreSQL 16Supported until November 9, 2028Earlier end of support than 17 and 18, so a dated upgrade is neededExisting 16 estates with an upgrade scheduled well before November 2028
PostgreSQL 15 and 1415 until November 11, 2027. 14 until November 12, 2026Short remaining runway. Version 14 ends on November 12, 2026Only as a stepping stone with a dated upgrade plan. Do not start new builds on these
Asynchronous replicationCommits do not wait for standbysRecent commits can be lost on failover, but writes stay fastestWhen losing the latest transactions is acceptable and write latency matters most
Synchronous replication with quorum commitCommits wait for a set number of standbysHigher write latency, and writes can stall if too few standbys respondWhen a failover must not lose acknowledged commits. Test the stall behavior first
Patroni distributed configuration storeetcd, Consul, ZooKeeper or the Kubernetes APIEach store is another cluster to run, secure and back upConsul suits teams that already run it, as in GitLab's reference HA design
PgBouncer pool mode and placementPooling layer between GitLab and PostgreSQLThe pool mode changes which session features work through pooled connectionsConfirm the pool mode and placement GitLab documents for your release before go-live
Self-run failover or managed cloud databasePatroni-driven failover you operate, or a provider-run servicePatroni keeps control in your hands and adds operating work. Managed services shift that work but limit some settingsManaged fits when your team cannot run database failover. Confirm GitLab supports the service first

Which PostgreSQL version GitLab deployments should plan on

Plan the database tier around a PostgreSQL major version that is still inside its support window for the whole life of the deployment. As of the supplied policy data, PostgreSQL 18 was first released on September 25, 2025 and is supported until November 14, 2030. Version 17 is supported until November 8, 2029, version 16 until November 9, 2028, version 15 until November 11, 2027 and version 14 until November 12, 2026. Versions 13 and earlier are unsupported. PostgreSQL ships a new major version about once a year and supports each one for five years, so a deployment that starts on the newest major version gets roughly a five-year runway before it must move. Before choosing, confirm which PostgreSQL versions the GitLab release you plan to install supports, since GitLab's own compatibility list governs the choice, not the PostgreSQL calendar alone. Check the PostgreSQL versioning policy for the authoritative dates.

Minor releases are the routine patching path. They never change the internal storage format, so applying one means replacing the PostgreSQL executables and restarting the service. In a Patroni cluster, plan the restart order so replicas are updated first and a switchover moves the primary to an already patched node, rather than restarting the leader blindly. Schedule minor updates within the same maintenance window as GitLab's own patch releases, and record which minor version each node runs so that Patroni, PgBouncer and the GitLab Linux package agree on the target.

Major upgrades are a separate project. The PostgreSQL upgrade documentation describes three methods: pg_dumpall with a restore, pg_upgrade, and logical replication. Dump and restore is simplest but takes the longest for large databases. pg_upgrade is faster and PostgreSQL 18 adds parallel jobs and a --swap option. Logical replication works across major versions and the documentation describes downtime of only several seconds at cutover, but it needs its own testing of replicated objects and sequences. Ask your team or partner to name the method, the rollback point and the GitLab version that supports both the old and new PostgreSQL major versions before the window is booked.

Setup sequence for Patroni and PgBouncer under GitLab

This sequence takes the database tier from a confirmed GitLab reference architecture to a tested Patroni-managed PostgreSQL cluster with PgBouncer in front of the primary, validated for failover and recovery before GitLab depends on it.

  1. Confirm the reference architecture and GitLab version

    Match your user count and API request rate to a GitLab reference architecture, and confirm the target GitLab version against the maintenance policy and the required upgrade stops before choosing any database components. The HA reference designs cover 3,000 users and above and include Patroni PostgreSQL, so confirm which layout your sizing selects and which PostgreSQL versions that GitLab release supports. Your architect or the GitLab documentation owner signs off on the chosen layout. Done when the architecture, GitLab version and supported PostgreSQL range are written into the design record.

  2. Choose the PostgreSQL version and distributed store

    Select a PostgreSQL major version that GitLab supports for your release and that is still inside PostgreSQL's support window, and confirm the distributed configuration store Patroni will use, such as etcd, Consul or ZooKeeper. Check the Patroni release notes for compatibility with your chosen PostgreSQL version and Python runtime. Record the choice and the reasoning, including how the store will be deployed and backed up. Done when a version matrix shows GitLab, PostgreSQL, Patroni and the store all agree.

  3. Build three or more database nodes across failure domains

    Provision at least three database nodes, placed in separate failure domains such as hosts, racks, hypervisors or availability zones, along with the configuration store nodes that Patroni needs for quorum. Confirm that the network paths between nodes, the store and the GitLab hosts are stable and that storage is sized and isolated for PostgreSQL. Your infrastructure team should verify placement against the failure domains they actually control. Done when each node can reach its peers and the store, and the placement is documented.

  4. Configure Patroni and synchronous replication

    Install PostgreSQL and Patroni on each node, then write a Patroni configuration per node that sets the scope, the store endpoints, replication settings and synchronous standby behavior. Choose a synchronous commit policy that matches your data-loss tolerance, and test that a standby is in sync before relying on it. Confirm the REST API responds on every node so health checks and monitoring can read cluster state. Done when Patroni reports one leader, the standbys are streaming and the REST API reports the expected roles.

  5. Place PgBouncer in front of the primary

    Deploy PgBouncer on hosts that can reach the current primary, and configure it to route client connections to the leader that Patroni reports. Set pool modes and connection limits deliberately, and make sure the routing layer follows leader changes after a failover rather than pinning to one node. Keep PgBouncer's own health checks tied to the database role. Done when a test client connects through PgBouncer, writes succeed and the pool points at the current leader.

  6. Point GitLab database settings at the pooler

    Update the GitLab database settings so the application connects through PgBouncer rather than directly to a PostgreSQL node, and confirm that the database name, user and TLS settings match what the pooler expects. Reconfigure GitLab and restart the services that depend on the database, following the documented procedure for your install method. Done when GitLab starts cleanly, its health checks pass and connections are visible through the pooler.

  7. Run switchover and failover tests

    Use Patroni's switchover to move the leader on purpose, then simulate an unplanned primary loss in a non-production copy of the cluster. During each test, watch whether PgBouncer re-routes, whether GitLab recovers without manual database changes and whether any writes were lost or duplicated. Record the time to recover and the observed errors. Done when both planned and unplanned tests complete with the results documented and the gaps assigned to owners.

  8. Validate backups and point-in-time recovery

    Set up continuous WAL archiving and base backups, then restore to a point in time on a separate environment and confirm the GitLab data matches what the restore target should contain. Check that backup jobs cover every node you may need to recover from and that restore steps are written down and rehearsed. Confirm the GitLab-side backup procedure as well, since the database backup alone does not restore GitLab. Done when a timed point-in-time restore succeeds and the recovery runbook has been tested by someone other than its author.

Security and operations for the Patroni database tier

A Patroni cluster is only as safe as the access paths around it: who can talk to PostgreSQL, who can call the Patroni API, how the distributed store is reached, and how a failover is kept from producing two primaries. The cards below cover those controls and the monitoring and recovery tests that show whether they work in your environment.

Client authentication with SCRAM

PostgreSQL's documented authentication methods include password, GSSAPI, SSPI, LDAP, RADIUS, certificate, PAM and OAuth. For GitLab, the practical choice is usually SCRAM-SHA-256 for password logins or certificates for service accounts, since PostgreSQL 18 deprecated MD5 password authentication in favor of SCRAM. Confirm that every pg_hba.conf line for the GitLab, PgBouncer and Patroni roles uses a method your security team has approved, and that no MD5 entries remain after the upgrade.

Patroni REST API access control

Patroni exposes a REST API for health checks, cluster status, switchover, failover, restarts and reinitialization. Anyone who can reach that API can move the primary, so restrict it to the monitoring systems and the other cluster members, and require authentication for write operations. Check the restapi section of the Patroni configuration on each node, and confirm the listen address is not reachable from the GitLab application tier.

Isolating the distributed configuration store

Patroni uses ZooKeeper, etcd, Consul or Kubernetes as its distributed configuration store, and the leader lock lives there. Whoever controls that store controls which node is primary. Place the store on a private segment reachable only by Patroni nodes, enable mutual TLS or client certificates where the store supports them, and restrict firewall rules to the exact peer ports. Confirm the store has its own quorum and backups.

Watchdog and split-brain prevention

Split brain occurs when an old primary keeps accepting writes after a new primary is elected. Patroni supports watchdog devices that reboot a node that loses contact with the store, and synchronous replication with quorum commit to limit data loss. Ask your team to test a network partition on a staging cluster, confirm the isolated primary demotes itself, and verify that the watchdog is armed on every node.

Backup and point-in-time recovery testing

Replication protects against node loss, not against a bad DROP TABLE or a corrupted deployment, which replicates to every standby. PostgreSQL's continuous archiving supports point-in-time recovery from a base backup plus archived WAL. Schedule restore tests into a separate environment, record the time to recover to a chosen timestamp, and check that GitLab can start against the restored database. Keep the archive in storage the database nodes cannot delete.

Replication lag and failover monitoring

Alert on replication lag for each standby, on the timeline changes that mark a failover, and on any node that stops reporting to the store. Patroni's REST API and PostgreSQL's statistics views expose the signals needed for these checks. Also track PgBouncer client wait times, because a stalled pool can look like an application outage. Confirm that alerts reach an on-call rota and that GitLab's own monitoring is wired to the same events.

Questions teams ask before building GitLab's database HA

What does a Patroni and PgBouncer build cost, and who pays for each part?

The software is free to use: Patroni is MIT licensed and PgBouncer is open source, so there is no license fee for either. The spend is labor and infrastructure. Your team or a partner pays for planning, building and testing the cluster, and your organization pays for the virtual machines or hardware, storage, network paths, backup targets and monitoring that the cluster runs on. If you use a managed cloud database instead, you pay the provider's service fees in place of most of the VM and operations work. A DeployOpen build is quoted as a fixed scope agreed in writing, so ask each quote to separate one-time build work from recurring items such as upgrades, testing and support retainers.

How long does a PostgreSQL HA build for GitLab take?

No timeline is supplied for this build, so confirm duration in a written scope rather than relying on a general estimate. The main drivers are the number of nodes and sites, whether a distributed configuration store such as etcd or Consul already exists, how many environments need to be built (staging and production usually differ), how much failover and restore testing is required, and whether your change process needs approvals or evidence. Access delays for virtual machines, network rules and backup targets often take as long as the build itself, so ask the partner to list those as dependencies.

What does the database tier need from our infrastructure team?

Plan for the database nodes, a witness or configuration store for leader election, network paths between the GitLab application nodes, PgBouncer and PostgreSQL, and separate storage for data and write-ahead logs. You also need a backup target with enough capacity and retention for point-in-time recovery, monitoring for replication lag and connections, and an on-call route that can act on a failover. The GitLab reference architectures describe the node layout for each size, so compare your plan against them. Confirm each item with your platform team before the scope is signed, because firewall rules and storage provisioning are usually the slowest steps.

Can our own team build and run this without a partner?

Yes, if your team already has solid PostgreSQL administration skills and has run Patroni or a similar failover tool before. The work includes configuring streaming replication, choosing synchronous or asynchronous commit behavior, setting up PgBouncer pooling, and rehearsing switchover and failover. GitLab's own documentation covers the required database settings, so start there and follow its reference architecture guidance. The areas teams most often underestimate are failover testing, restore drills and planned major upgrades, so budget time for those whatever you choose.

When is a managed cloud PostgreSQL service the better choice?

Choose a managed service when your organization already relies on one provider, when your team has little PostgreSQL operations experience, or when you want the provider to handle patching, backups and failover. Check that the service supports the PostgreSQL version GitLab requires, the extensions and settings GitLab needs, and that your data residency and encryption rules fit. Self-managed Patroni is the better fit when you must run in air-gapped or on-premises networks, need full control of the failover design, or cannot send data to a provider. Note that GitLab's reference architectures do not support running PostgreSQL inside Kubernetes, so a self-managed cluster would run on VMs.

What version of PostgreSQL should we target for GitLab?

Start from the PostgreSQL versions that are still supported and confirm the GitLab version compatibility in GitLab's installation and upgrade documentation before choosing. Supported releases as of this page are 18, 17, 16, 15 and 14, with 14 ending November 12, 2026 and 18 supported until November 14, 2030. Minor releases do not change the storage format and are applied by replacing executables and restarting, while major upgrades need pg_upgrade, a dump and restore, or logical replication. Plan the major upgrade as its own project, because GitLab upgrade stops also constrain the order in which you move components.

What breaks most often after the cluster goes live?

The common failures are replication lag under heavy write load, connection exhaustion when PgBouncer pool sizes do not match GitLab's connection needs, failover that works in a lab but not with real client reconnection, and backups that run but are never restored. Automatic failover also needs careful configuration, because PostgreSQL itself does not detect a failed primary, so the external tool's settings decide how quickly and safely a failover happens. Schedule regular switchover tests, restore drills and monitoring reviews, and ask any partner to include them in the handover.

When a managed cloud database is the better option for GitLab

A managed cloud PostgreSQL service is often the better choice when the team cannot staff PostgreSQL operations around the clock, or when the organization already runs a managed database platform with mature backup, patching and failover tooling. In that case the Patroni and PgBouncer design in this topic becomes unnecessary overhead. The decision starts with an honest count of who will handle failover drills, minor and major upgrades, backup restore tests and connection tuning after go-live, and whether those people exist on call. The second path is a provider-managed service that GitLab documents as compatible with its database requirements. Confirm this against the current GitLab reference architecture and installation documentation for your GitLab version, not against the provider's marketing pages. Check the PostgreSQL major version offered, which must be on the list of versions GitLab supports, and whether the provider exposes the extensions and parameters GitLab relies on. Ask the provider what it does during failover, how long client connections are interrupted, and whether it supports the replication topology that a Geo secondary requires, since GitLab Geo has its own database expectations.

The third path is accepting provider-managed failover in place of Patroni. You give up control over when failover happens, how the standby is chosen and when maintenance windows open. In return you avoid running a distributed configuration store and the Patroni REST API yourself. Data location is another trade-off: a managed service runs in the provider's region and accounts, which may conflict with sovereignty or air-gap requirements in the sector you operate in. Note also that managed services usually restrict superuser access, so review any GitLab procedure that expects direct database access. Before you choose, get written answers on compatibility with your GitLab version, supported extensions, Geo support, point-in-time recovery retention, upgrade notice periods and who is accountable for each recovery step. If the answers are unclear, the self-managed path in this topic is the safer default, because its failover, upgrades and backups stay under your control.

How DeployOpen scopes and delivers GitLab database HA

DeployOpen plans, builds and hands over the PostgreSQL database tier for self-managed GitLab inside infrastructure you control, including Patroni failover, PgBouncer pooling and tested recovery. Engagements are fixed-scope, with scope, deliverables and timelines agreed in writing before work starts.

How we scope

  • Confirm the GitLab reference architecture and user or request volume that the database tier must serve.
  • Record the PostgreSQL major version, extension needs, and whether the database stays on VMs or moves to a managed service.
  • Agree in writing the Patroni, etcd or Consul, PgBouncer, backup and failover test deliverables before work begins.

How we build

  • Build the Patroni cluster, distributed configuration store and PgBouncer layer on hosts or networks you designate.
  • Configure synchronous or asynchronous replication, PgBouncer pooling and GitLab connection settings, then test controlled switchover and failover.
  • Verify backups with a restore into a separate environment and record measured recovery results for your review.

How we hand over

  • Document node roles, configuration files, the Patroni REST API endpoints your team will call, and the runbooks for failover and rebuilds.
  • Walk your operators through a switchover, a failed node replacement and a restore drill using your own environment.
  • Deliver the written configuration, version inventory and test evidence so your team and auditors can trace each control.

How we support

  • Ongoing support, PostgreSQL minor updates and major upgrade planning are available as a retainer after handover.
  • Plan major version upgrades ahead of the supported end dates, with rollback steps agreed before the change window.
  • Schedule periodic failover and restore tests as part of a retainer, with results shared in writing.

Sources and further reading

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.