This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Configuration

Choose the appropriate instance and cluster types based on your requirements to configure PostgreSQL database clusters that meet your needs.

Pigsty is a “configuration-driven” PostgreSQL platform: all behaviors come from the combination of inventory files in ~/pigsty/conf/*.yml and PGSQL parameters. Once you’ve written the configuration, you can replicate a customized cluster with instances, users, databases, access control, extensions, and tuning policies in just a few minutes.


Configuration Entry

  1. Prepare Inventory: Copy a pigsty/conf/*.yml template or write an Ansible Inventory from scratch, placing cluster groups (all.children.<cls>.hosts) and global variables (all.vars) in the same file.
  2. Define Parameters: Override the required PGSQL parameters in the vars block. The override order from global → cluster → host determines the final value.
  3. Apply Configuration: Run ./configure -c <conf> or bin/pgsql-add <cls> and other playbooks to apply the configuration. Pigsty will generate the configuration files needed for Patroni/pgbouncer/pgbackrest based on the parameters.

Pigsty’s default demo inventory conf/pgsql.yml is a minimal example: one pg-meta cluster, global pg_version: 18, and a few business user and database definitions. You can expand with more clusters from this base.


Focus Areas & Documentation Index

Pigsty’s PostgreSQL configuration can be organized from the following dimensions. Subsequent documentation will explain “how to configure” each:

  • Cluster & Instances: Define instance topology (standalone, primary-replica, standby cluster, delayed cluster, Citus, etc.) through pg_cluster / pg_role / pg_seq / pg_upstream.
  • Kernel Version: Select the core version, flavor, and tuning templates using pg_version, pg_mode, pg_packages, pg_extensions, pg_conf, and other parameters.
  • Users/Roles: Declare system roles, business accounts, password policies, and connection pool attributes in pg_default_roles and pg_users.
  • Database Objects: Create databases as needed using pg_databases, baseline, schemas, extensions, pool_* fields and automatically integrate with pgbouncer/Grafana.
  • Access Control (HBA): Maintain host-based authentication policies using pg_default_hba_rules and pg_hba_rules to ensure access boundaries for different roles/networks.
  • Privilege Model (ACL): Converge object privileges through pg_default_privileges, pg_default_roles, pg_revoke_public parameters, providing an out-of-the-box layered role system.

After understanding these parameters, you can write declarative inventory manifests as “configuration as infrastructure” for any business requirement. Pigsty will handle execution and ensure idempotency.


A Typical Example

The following snippet shows how to control instance topology, kernel version, extensions, users, and databases in the same configuration file:

all:
  children:
    pg-analytics:
      hosts:
        10.10.10.11: { pg_seq: 1, pg_role: primary }
        10.10.10.12: { pg_seq: 2, pg_role: replica, pg_offline_query: true }
      vars:
        pg_cluster: pg-analytics
        pg_conf: olap.yml
        pg_extensions: [ postgis, timescaledb, pgvector ]
        pg_databases:
          - { name: bi, owner: dbuser_bi, schemas: [mart], extensions: [timescaledb], pool_mode: session }
        pg_users:
          - { name: dbuser_bi, password: DBUser.BI, roles: [dbrole_admin], pgbouncer: true }
  vars:
    pg_version: 17
    pg_packages: [ pgsql-main pgsql-common ]
    pg_hba_rules:
      - { user: dbuser_bi, db: bi, addr: intra, auth: ssl, title: 'BI only allows intranet SSL access' }
  • The pg-analytics cluster contains one primary and one offline replica.
  • Global settings specify pg_version: 17 with a set of extension examples and load olap.yml tuning.
  • Declare business objects in pg_databases and pg_users, automatically generating schema/extension and connection pool entries.
  • Additional pg_hba_rules restrict access sources and authentication methods.

Modify and apply this inventory to get a customized PostgreSQL cluster without manual configuration.

1 - Cluster & Instances

Choose the appropriate instance and cluster types based on your requirements to configure PostgreSQL database clusters that meet your needs.

Choose the appropriate instance and cluster types based on your requirements to configure PostgreSQL database clusters that meet your needs.

You can define different types of instances and clusters. Here are several common PostgreSQL instance/cluster types in Pigsty:

  • Primary: Define a single instance cluster.
  • Replica: Define a basic HA cluster with one primary and one replica.
  • Offline: Define an instance dedicated to OLAP/ETL/interactive queries
  • Sync Standby: Enable synchronous commit to ensure no data loss.
  • Quorum Commit: Use quorum sync commit for a higher consistency level.
  • Standby Cluster: Clone an existing cluster and follow it
  • Delayed Cluster: Clone an existing cluster for emergency data recovery
  • Citus Cluster: Define a Citus distributed database cluster

Primary

We start with the simplest case: a single instance cluster consisting of one primary:

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
  vars:
    pg_cluster: pg-test

This configuration is concise and self-describing, consisting only of identity parameters. Note that the Ansible Group name should match pg_cluster.

Use the following command to create this cluster:

bin/pgsql-add pg-test

For demos, development testing, hosting temporary requirements, or performing non-critical analytical tasks, a single database instance may not be a big problem. However, such a single-node cluster has no high availability. When hardware failures occur, you’ll need to use PITR or other recovery methods to ensure the cluster’s RTO/RPO. For this reason, you may consider adding several read-only replicas to the cluster.


Replica

To add a read-only replica instance, you can add a new node to pg-test and set its pg_role to replica.

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica }  # <--- newly added replica
  vars:
    pg_cluster: pg-test

If the entire cluster doesn’t exist, you can directly create the complete cluster. If the cluster primary has already been initialized, you can add a replica to the existing cluster:

bin/pgsql-add pg-test               # initialize the entire cluster at once
bin/pgsql-add pg-test 10.10.10.12   # add replica to existing cluster

When the cluster primary fails, the read-only instance (Replica) can take over the primary’s work with the help of the high availability system. Additionally, read-only instances can be used to execute read-only queries: many businesses have far more read requests than write requests, and most read-only query loads can be handled by replica instances.


Offline

Offline instances are dedicated read-only replicas specifically for serving slow queries, ETL, OLAP traffic, and interactive queries. Slow queries/long transactions have adverse effects on the performance and stability of online business, so it’s best to isolate them from online business.

To add an offline instance, assign it a new instance and set pg_role to offline.

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica }
    10.10.10.13: { pg_seq: 3, pg_role: offline }  # <--- newly added offline replica
  vars:
    pg_cluster: pg-test

Dedicated offline instances work similarly to common replica instances, but they serve as backup servers in the pg-test-replica service. That is, only when all replica instances are down will the offline and primary instances provide this read-only service.

In many cases, database resources are limited, and using a separate server as an offline instance is not economical. As a compromise, you can select an existing replica instance and mark it with the pg_offline_query flag to indicate it can handle “offline queries”. In this case, this read-only replica will handle both online read-only requests and offline queries. You can use pg_default_hba_rules and pg_hba_rules for additional access control on offline instances.


Sync Standby

When Sync Standby is enabled, PostgreSQL will select one replica as the sync standby, with all other replicas as candidates. The primary database will wait for the standby instance to flush to disk before confirming commits. The standby instance always has the latest data with no replication lag, and primary-standby switchover to the sync standby will have no data loss.

PostgreSQL uses asynchronous streaming replication by default, which may have small replication lag (on the order of 10KB/10ms). When the primary fails, there may be a small data loss window (which can be controlled using pg_rpo), but this is acceptable for most scenarios.

However, in some critical scenarios (e.g., financial transactions), data loss is completely unacceptable, or read replication lag is unacceptable. In such cases, you can use synchronous commit to solve this problem. To enable sync standby mode, you can simply use the crit.yml template in pg_conf.

pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica }
    10.10.10.13: { pg_seq: 3, pg_role: replica }
  vars:
    pg_cluster: pg-test
    pg_conf: crit.yml   # <--- use crit template

To enable sync standby on an existing cluster, configure the cluster and enable synchronous_mode:

$ pg edit-config pg-test    # run as admin user on admin node
+++
-synchronous_mode: false    # <--- old value
+synchronous_mode: true     # <--- new value
 synchronous_mode_strict: false

Apply these changes? [y/N]: y

In this case, the PostgreSQL configuration parameter synchronous_standby_names is automatically managed by Patroni. One replica will be elected as the sync standby, and its application_name will be written to the PostgreSQL primary configuration file and applied.


Quorum Commit

Quorum Commit provides more powerful control than sync standby: especially when you have multiple replicas, you can set criteria for successful commits, achieving higher/lower consistency levels (and trade-offs with availability).

If you want at least two replicas to confirm commits, you can adjust the synchronous_node_count parameter through Patroni cluster configuration and apply it:

synchronous_mode: true          # ensure synchronous commit is enabled
synchronous_node_count: 2       # specify "at least" how many replicas must successfully commit

If you want to use more sync replicas, modify the synchronous_node_count value. When the cluster size changes, you should ensure this configuration is still valid to avoid service unavailability.

In this case, the PostgreSQL configuration parameter synchronous_standby_names is automatically managed by Patroni.

synchronous_standby_names = '2 ("pg-test-3","pg-test-2")'
Example: Using multiple sync standbys
$ pg edit-config pg-test
---
+synchronous_node_count: 2

Apply these changes? [y/N]: y

After applying the configuration, two sync standbys appear.

+ Cluster: pg-test (7080814403632534854) +---------+----+-----------+-----------------+
| Member    | Host        | Role         | State   | TL | Lag in MB | Tags            |
+-----------+-------------+--------------+---------+----+-----------+-----------------+
| pg-test-1 | 10.10.10.10 | Leader       | running |  1 |           | clonefrom: true |
| pg-test-2 | 10.10.10.11 | Sync Standby | running |  1 |         0 | clonefrom: true |
| pg-test-3 | 10.10.10.12 | Sync Standby | running |  1 |         0 | clonefrom: true |
+-----------+-------------+--------------+---------+----+-----------+-----------------+

Another scenario is using any n replicas to confirm commits. In this case, the configuration is slightly different. For example, if we only need any one replica to confirm commits:

synchronous_mode: quorum        # use quorum commit
postgresql:
  parameters:                   # modify PostgreSQL's configuration parameter synchronous_standby_names, using `ANY n ()` syntax
    synchronous_standby_names: 'ANY 1 (*)'  # you can specify a specific replica list or use * to wildcard all replicas.
Example: Enable ANY quorum commit
$ pg edit-config pg-test

+    synchronous_standby_names: 'ANY 1 (*)' # in ANY mode, this parameter is needed
- synchronous_node_count: 2  # in ANY mode, this parameter is not needed

Apply these changes? [y/N]: y

After applying, the configuration takes effect, and all standbys become regular replicas in Patroni. However, in pg_stat_replication, you can see sync_state becomes quorum.


Standby Cluster

You can clone an existing cluster and create a standby cluster for data migration, horizontal splitting, multi-region deployment, or disaster recovery.

Under normal circumstances, the standby cluster will follow the upstream cluster and keep content synchronized. You can promote the standby cluster to become a truly independent cluster.

The standby cluster definition is basically the same as a normal cluster definition, except that the pg_upstream parameter is additionally defined on the primary. The primary of the standby cluster is called the Standby Leader.

For example, below defines a pg-test cluster and its standby cluster pg-test2. The configuration inventory might look like this:

# pg-test is the original cluster
pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
  vars: { pg_cluster: pg-test }

# pg-test2 is the standby cluster of pg-test
pg-test2:
  hosts:
    10.10.10.12: { pg_seq: 1, pg_role: primary , pg_upstream: 10.10.10.11 } # <--- pg_upstream defined here
    10.10.10.13: { pg_seq: 2, pg_role: replica }
  vars: { pg_cluster: pg-test2 }

The primary node pg-test2-1 of the pg-test2 cluster will be a downstream replica of pg-test and serve as the Standby Leader in the pg-test2 cluster.

Just ensure the pg_upstream parameter is configured on the standby cluster’s primary node to automatically pull backups from the original upstream.

bin/pgsql-add pg-test     # create original cluster
bin/pgsql-add pg-test2    # create standby cluster
Example: Change replication upstream

If necessary (e.g., upstream primary-standby switchover/failover), you can change the standby cluster’s replication upstream through cluster configuration.

To do this, simply change standby_cluster.host to the new upstream IP address and apply.

$ pg edit-config pg-test2

 standby_cluster:
   create_replica_methods:
   - basebackup
-  host: 10.10.10.13     # <--- old upstream
+  host: 10.10.10.12     # <--- new upstream
   port: 5432

 Apply these changes? [y/N]: y
Example: Promote standby cluster

You can promote the standby cluster to an independent cluster at any time, so the cluster can independently handle write requests and diverge from the original cluster.

To do this, you must configure the cluster and completely erase the standby_cluster section, then apply.

$ pg edit-config pg-test2
-standby_cluster:
-  create_replica_methods:
-  - basebackup
-  host: 10.10.10.11
-  port: 5432

Apply these changes? [y/N]: y
Example: Cascade replication

If you specify pg_upstream on a replica instead of the primary, you can configure cascade replication for the cluster.

When configuring cascade replication, you must use the IP address of an instance in the cluster as the parameter value, otherwise initialization will fail. The replica performs streaming replication from a specific instance rather than the primary.

The instance acting as a WAL relay is called a Bridge Instance. Using a bridge instance can share the burden of sending WAL from the primary. When you have dozens of replicas, using bridge instance cascade replication is a good idea.

pg-test:
  hosts: # pg-test-1 ---> pg-test-2 ---> pg-test-3
    10.10.10.11: { pg_seq: 1, pg_role: primary }
    10.10.10.12: { pg_seq: 2, pg_role: replica } # <--- bridge instance
    10.10.10.13: { pg_seq: 3, pg_role: replica, pg_upstream: 10.10.10.12 }
    # ^--- replicate from pg-test-2 (bridge) instead of pg-test-1 (primary)
  vars: { pg_cluster: pg-test }

Delayed Cluster

A Delayed Cluster is a special type of standby cluster used to quickly recover “accidentally deleted” data.

For example, if you want a cluster named pg-testdelay whose data content is the same as the pg-test cluster from one hour ago:

# pg-test is the original cluster
pg-test:
  hosts:
    10.10.10.11: { pg_seq: 1, pg_role: primary }
  vars: { pg_cluster: pg-test }

# pg-testdelay is the delayed cluster of pg-test
pg-testdelay:
  hosts:
    10.10.10.12: { pg_seq: 1, pg_role: primary , pg_upstream: 10.10.10.11, pg_delay: 1d }
    10.10.10.13: { pg_seq: 2, pg_role: replica }
  vars: { pg_cluster: pg-testdelay }

You can also configure a “replication delay” on an existing standby cluster.

$ pg edit-config pg-testdelay
 standby_cluster:
   create_replica_methods:
   - basebackup
   host: 10.10.10.11
   port: 5432
+  recovery_min_apply_delay: 1h    # <--- add delay duration here, e.g. 1 hour

Apply these changes? [y/N]: y

When some tuples and tables are accidentally deleted, you can modify this parameter to advance this delayed cluster to an appropriate point in time, read data from it, and quickly fix the original cluster.

Delayed clusters require additional resources, but are much faster than PITR and have much less impact on the system. For very critical clusters, consider setting up delayed clusters.


Citus Cluster

Pigsty natively supports Citus. You can refer to files/pigsty/citus.yml and prod.yml as examples.

To define a Citus cluster, you need to specify the following parameters:

  • pg_mode must be set to citus, not the default pgsql
  • The shard name pg_shard and shard number pg_group must be defined on each shard cluster
  • pg_primary_db must be defined to specify the database managed by Patroni.
  • If you want to use pg_dbsu postgres instead of the default pg_admin_username to execute admin commands, then pg_dbsu_password must be set to a non-empty plaintext password

Additionally, extra hba rules are needed to allow SSL access from localhost and other data nodes. As shown below:

all:
  children:
    pg-citus0: # citus shard 0
      hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
      vars: { pg_cluster: pg-citus0 , pg_group: 0 }
    pg-citus1: # citus shard 1
      hosts: { 10.10.10.11: { pg_seq: 1, pg_role: primary } }
      vars: { pg_cluster: pg-citus1 , pg_group: 1 }
    pg-citus2: # citus shard 2
      hosts: { 10.10.10.12: { pg_seq: 1, pg_role: primary } }
      vars: { pg_cluster: pg-citus2 , pg_group: 2 }
    pg-citus3: # citus shard 3
      hosts:
        10.10.10.13: { pg_seq: 1, pg_role: primary }
        10.10.10.14: { pg_seq: 2, pg_role: replica }
      vars: { pg_cluster: pg-citus3 , pg_group: 3 }
  vars:                               # global parameters for all Citus clusters
    pg_mode: citus                    # pgsql cluster mode must be set to: citus
    pg_shard: pg-citus                # citus horizontal shard name: pg-citus
    pg_primary_db: meta               # citus database name: meta
    pg_dbsu_password: DBUser.Postgres # if using dbsu, need to configure a password for it
    pg_users: [ { name: dbuser_meta ,password: DBUser.Meta ,pgbouncer: true ,roles: [ dbrole_admin ] } ]
    pg_databases: [ { name: meta ,extensions: [ { name: citus }, { name: postgis }, { name: timescaledb } ] } ]
    pg_hba_rules:
      - { user: 'all' ,db: all  ,addr: 127.0.0.1/32 ,auth: ssl ,title: 'all user ssl access from localhost' }
      - { user: 'all' ,db: all  ,addr: intra        ,auth: ssl ,title: 'all user ssl access from intranet'  }

On the coordinator node, you can create distributed tables and reference tables and query them from any data node. Starting from 11.2, any Citus database node can act as a coordinator.

SELECT create_distributed_table('pgbench_accounts', 'aid'); SELECT truncate_local_data_after_distributing_table($$public.pgbench_accounts$$);
SELECT create_reference_table('pgbench_branches')         ; SELECT truncate_local_data_after_distributing_table($$public.pgbench_branches$$);
SELECT create_reference_table('pgbench_history')          ; SELECT truncate_local_data_after_distributing_table($$public.pgbench_history$$);
SELECT create_reference_table('pgbench_tellers')          ; SELECT truncate_local_data_after_distributing_table($$public.pgbench_tellers$$);

2 - Kernel Version

How to choose the appropriate PostgreSQL kernel and major version.

Choosing a “kernel” in Pigsty means determining the PostgreSQL major version, mode/distribution, packages to install, and tuning templates to load.

Pigsty supports PostgreSQL from version 10 onwards. The current version packages core software for versions 13-18 by default and provides a complete extension set for 17/18. The following content shows how to make these choices through configuration files.


Major Version and Packages

  • pg_version: Specify the PostgreSQL major version (default 18). Pigsty will automatically map to the correct package name prefix based on the version.
  • pg_packages: Define the core package set to install, supports using package aliases (default pgsql-main pgsql-common, includes kernel + patroni/pgbouncer/pgbackrest and other common tools).
  • pg_extensions: List of additional extension packages to install, also supports aliases; defaults to empty meaning only core dependencies are installed.
all:
  vars:
    pg_version: 17
    pg_packages: [ pgsql-main pgsql-common ]
    pg_extensions: [ postgis, timescaledb, pgvector, pgml ]

Effect: Ansible will pull packages corresponding to pg_version=17 during installation, pre-install extensions to the system, and database initialization scripts can then directly CREATE EXTENSION.

Extension support varies across versions in Pigsty’s offline repository: 12/13 only provide core and tier-1 extensions, while 15/17/18 cover all extensions. If an extension is not pre-packaged, it can be added via repo_packages_extra.


Kernel Mode (pg_mode)

pg_mode controls the kernel “flavor” to deploy. Default pgsql indicates standard PostgreSQL. Pigsty currently supports the following modes:

ModeScenario
pgsqlStandard PostgreSQL, HA + replication
citusCitus distributed cluster, requires additional pg_shard / pg_group
gpsqlGreenplum / MatrixDB
mssqlBabelfish for PostgreSQL
mysqlOpenGauss/HaloDB compatible with MySQL protocol
polarAlibaba PolarDB (based on pg polar distribution)
ivoryIvorySQL (Oracle-compatible syntax)
orioleOrioleDB storage engine
oraclePostgreSQL + ora compatibility (pg_mode: oracle)

After selecting a mode, Pigsty will automatically load corresponding templates, dependency packages, and Patroni configurations. For example, deploying Citus:

all:
  children:
    pg-citus0:
      hosts: { 10.10.10.11: { pg_seq: 1, pg_role: primary } }
      vars: { pg_cluster: pg-citus0, pg_group: 0 }
    pg-citus1:
      hosts: { 10.10.10.12: { pg_seq: 1, pg_role: primary } }
      vars: { pg_cluster: pg-citus1, pg_group: 1 }
  vars:
    pg_mode: citus
    pg_shard: pg-citus
    patroni_citus_db: meta

Effect: All members will install Citus-related packages, Patroni writes to etcd in shard mode, and automatically CREATE EXTENSION citus in the meta database.


Extensions and Pre-installed Objects

Besides system packages, you can control components automatically loaded after database startup through the following parameters:

  • pg_libs: List to write to shared_preload_libraries. For example: pg_libs: 'timescaledb, pg_stat_statements, auto_explain'.
  • pg_default_extensions / pg_default_schemas: Control schemas and extensions pre-created in template1 and postgres by initialization scripts.
  • pg_parameters: Append ALTER SYSTEM SET for all instances (written to postgresql.auto.conf).

Example: Enable TimescaleDB, pgvector and customize some system parameters.

pg-analytics:
  vars:
    pg_cluster: pg-analytics
    pg_libs: 'timescaledb, pg_stat_statements, pgml'
    pg_default_extensions:
      - { name: timescaledb }
      - { name: pgvector }
    pg_parameters:
      timescaledb.max_background_workers: 8
      shared_preload_libraries: "'timescaledb,pg_stat_statements,pgml'"

Effect: During initialization, template1 creates extensions, Patroni’s postgresql.conf injects corresponding parameters, and all business databases inherit these settings.


Tuning Template (pg_conf)

pg_conf points to Patroni templates in roles/pgsql/templates/*.yml. Pigsty includes four built-in general templates:

TemplateApplicable Scenario
oltp.ymlDefault template, for 4–128 core TP workload
olap.ymlOptimized for analytical scenarios
crit.ymlEmphasizes sync commit/minimal latency, suitable for zero-loss scenarios like finance
tiny.ymlLightweight machines / edge scenarios / resource-constrained environments

You can directly replace the template or customize a YAML file in templates/, then specify it in cluster vars.

pg-ledger:
  hosts: { 10.10.10.21: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-ledger
    pg_conf: crit.yml
    pg_parameters:
      synchronous_commit: 'remote_apply'
      max_wal_senders: 16
      wal_keep_size: '2GB'

Effect: Copy crit.yml as Patroni configuration, overlay pg_parameters written to postgresql.auto.conf, making instances run immediately in synchronous commit mode.


Combined Instance: A Complete Example

pg-rag:
  hosts:
    10.10.10.31: { pg_seq: 1, pg_role: primary }
    10.10.10.32: { pg_seq: 2, pg_role: replica }
  vars:
    pg_cluster: pg-rag
    pg_version: 18
    pg_mode: pgsql
    pg_conf: olap.yml
    pg_packages: [ pgsql-main pgsql-common ]
    pg_extensions: [ pgvector, pgml, postgis ]
    pg_libs: 'pg_stat_statements, pgvector, pgml'
    pg_parameters:
      max_parallel_workers: 8
      shared_buffers: '32GB'
  • First primary + one replica, using olap.yml tuning.
  • Install PG18 + RAG common extensions, automatically load pgvector/pgml at system level.
  • Patroni/pgbouncer/pgbackrest generated by Pigsty, no manual intervention needed.

Replace the above parameters according to business needs to complete all kernel-level customization.

3 - Package Alias

Pigsty provides a package alias translation mechanism that shields the differences in binary package details across operating systems, making installation easier.

PostgreSQL package naming conventions vary significantly across different operating systems:

  • EL systems (RHEL/Rocky/Alma/…) use formats like pgvector_17, postgis36_17*
  • Debian/Ubuntu systems use formats like postgresql-17-pgvector, postgresql-17-postgis-3

This difference adds cognitive burden to users: you need to remember different package name rules for different systems, and handle the embedding of PostgreSQL version numbers.

Package Alias

Pigsty solves this problem through the Package Alias mechanism: you only need to use unified aliases, and Pigsty will handle all the details:

# Using aliases - simple, unified, cross-platform
pg_extensions: [ postgis, pgvector, timescaledb ]

# Equivalent to actual package names on EL9 + PG17
pg_extensions: [ postgis36_17*, pgvector_17*, timescaledb-tsl_17* ]

# Equivalent to actual package names on Ubuntu 24 + PG17
pg_extensions: [ postgresql-17-postgis-3, postgresql-17-pgvector, postgresql-17-timescaledb-tsl ]

Alias Translation

Aliases can also group a set of packages as a whole. For example, Pigsty’s default installed packages - the default value of pg_packages is:

pg_packages:                      # pg packages to be installed, alias can be used
  - pgsql-main pgsql-common

Pigsty will query the current operating system alias list (assuming el10.x86_64) and translate it to PGSQL kernel, extensions, and toolkits:

pgsql-main:    "postgresql$v postgresql$v-server postgresql$v-libs postgresql$v-contrib postgresql$v-plperl postgresql$v-plpython3 postgresql$v-pltcl postgresql$v-llvmjit pg_repack_$v* wal2json_$v* pgvector_$v*"
pgsql-common:  "patroni patroni-etcd pgbouncer pgbackrest pg_exporter pgbackrest_exporter vip-manager"

Next, Pigsty further translates pgsql-main using the currently specified PG major version (assuming pg_version = 18):

pg18-main:   "postgresql18 postgresql18-server postgresql18-libs postgresql18-contrib postgresql18-plperl postgresql18-plpython3 postgresql18-pltcl postgresql18-llvmjit pg_repack_18* wal2json_18* pgvector_18*"

Through this approach, Pigsty shields the complexity of packages, allowing users to simply specify the functional components they want.


Which Variables Can Use Aliases?

You can use package aliases in the following four parameters, and the aliases will be automatically converted to actual package names according to the translation process:


Alias List

You can find the alias mapping files for each operating system and architecture in the roles/node_id/vars/ directory of the Pigsty project source code:


How It Works

Alias Translation Process

User config alias --> Detect OS -->  Find alias mapping table ---> Replace $v placeholder ---> Install actual packages
     ↓                 ↓                   ↓                                   ↓
  postgis          el9.x86_64         postgis36_$v*                   postgis36_17*
  postgis          u24.x86_64         postgresql-$v-postgis-3         postgresql-17-postgis-3

Version Placeholder

Pigsty’s alias system uses $v as a placeholder for the PostgreSQL version number. When you specify a PostgreSQL version using pg_version, all $v in aliases will be replaced with the actual version number.

For example, when pg_version: 17:

Alias Definition (EL)Expanded Result
postgresql$v*postgresql17*
pgvector_$v*pgvector_17*
timescaledb-tsl_$v*timescaledb-tsl_17*
Alias Definition (Debian/Ubuntu)Expanded Result
postgresql-$vpostgresql-17
postgresql-$v-pgvectorpostgresql-17-pgvector
postgresql-$v-timescaledb-tslpostgresql-17-timescaledb-tsl

Wildcard Matching

On EL systems, many aliases use the * wildcard to match related subpackages. For example:

  • postgis36_17* will match postgis36_17, postgis36_17-client, postgis36_17-utils, etc.
  • postgresql17* will match postgresql17, postgresql17-server, postgresql17-libs, postgresql17-contrib, etc.

This design ensures you don’t need to list each subpackage individually - one alias can install the complete extension.

4 - User/Role

User/Role refers to logical objects created by the SQL command CREATE USER/ROLE within a database cluster.

In this context, user refers to logical objects created by the SQL command CREATE USER/ROLE within a database cluster.

In PostgreSQL, users belong directly to the database cluster rather than a specific database. Therefore, when creating business databases and business users, the principle of “users first, databases later” should be followed.


Define Users

Pigsty defines roles and users in database clusters through two config parameters:

  • pg_default_roles: Define globally shared roles and users
  • pg_users: Define business users and roles at the database cluster level

The former defines roles and users shared across the entire env, while the latter defines business roles and users specific to a single cluster. Both have the same format as arrays of user definition objects.

You can define multiple users/roles. They will be created sequentially: first global, then cluster, and finally by array order. So later users can belong to roles defined earlier.

Here is the business user definition in the default pg-meta cluster in the Pigsty demo env:

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta
    pg_users:
      - {name: dbuser_meta     ,password: DBUser.Meta     ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: pigsty admin user }
      - {name: dbuser_view     ,password: DBUser.Viewer   ,pgbouncer: true ,roles: [dbrole_readonly] ,comment: read-only viewer for meta database }
      - {name: dbuser_grafana  ,password: DBUser.Grafana  ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for grafana database    }
      - {name: dbuser_bytebase ,password: DBUser.Bytebase ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for bytebase database   }
      - {name: dbuser_kong     ,password: DBUser.Kong     ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for kong api gateway    }
      - {name: dbuser_gitea    ,password: DBUser.Gitea    ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for gitea service       }
      - {name: dbuser_wiki     ,password: DBUser.Wiki     ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for wiki.js service     }
      - {name: dbuser_noco     ,password: DBUser.Noco     ,pgbouncer: true ,roles: [dbrole_admin]    ,comment: admin user for nocodb service      }
      - {name: dbuser_remove   ,state: absent }  # use state: absent to delete user

Each user/role definition is an object that may include the following fields, using dbuser_meta user as an example:

- name: dbuser_meta               # Required, `name` is the only mandatory field
  state: create                   # Optional, user state: create (default), absent (delete)
  password: DBUser.Meta           # Optional, password, can be scram-sha-256 hash or plaintext
  login: true                     # Optional, can login by default
  superuser: false                # Optional, default false, is it a superuser?
  createdb: false                 # Optional, default false, can create databases?
  createrole: false               # Optional, default false, can create roles?
  inherit: true                   # Optional, can this role use inherited privileges by default?
  replication: false              # Optional, default false, can this role perform replication?
  bypassrls: false                # Optional, default false, can bypass row-level security?
  pgbouncer: true                 # Optional, default false, add to pgbouncer user list? (prod users should set to true)
  connlimit: -1                   # Optional, user connection limit, default -1 disables limit
  expire_in: 3650                 # Optional, expire after n days from creation (higher priority than expire_at)
  expire_at: '2030-12-31'         # Optional, expiration date in YYYY-MM-DD format (lower priority than expire_in)
  comment: pigsty admin user      # Optional, description and comment string
  roles: [dbrole_admin]           # Optional, default roles: dbrole_{admin,readonly,readwrite,offline}
  parameters:                     # Optional, role-level params via `ALTER ROLE SET`
    search_path: public           # e.g., set default search_path
  pool_mode: transaction          # Optional, pgbouncer pool mode, default transaction
  pool_connlimit: -1              # Optional, user-level max pool connections, -1 disables limit
  • The only required field is name, which should be a valid and unique username in the PostgreSQL cluster.
  • Username must match regex ^[a-z_][a-z0-9_]{0,62}$ (lowercase letters, digits, underscores, starts with letter or underscore, max 63 chars).
  • Roles don’t need password, but for login-able business users, a password is usually needed.
  • password can be plaintext or scram-sha-256 / md5 hash string. Please avoid using plaintext passwords.
  • Users/roles are created sequentially in array order, so ensure role/group definitions come before their members.
  • login, superuser, createdb, createrole, inherit, replication, bypassrls are boolean flags.
  • pgbouncer is disabled by default: to add business users to the pgbouncer user list, you should explicitly set it to true.

Parameter Overview

FieldCategoryTypeMutabilityDescription
nameBasicstringRequiredUsername, must be valid and unique identifier
stateBasicenumOptionalUser state: create (default), absent
passwordBasicstringMutableUser password, plaintext or hash
commentBasicstringMutableUser comment/description
loginPrivilegeboolMutableCan login, default true
superuserPrivilegeboolMutableIs superuser, default false
createdbPrivilegeboolMutableCan create database, default false
createrolePrivilegeboolMutableCan create role, default false
inheritPrivilegeboolMutableInherit role privileges, default true
replicationPrivilegeboolMutableCan replicate, default false
bypassrlsPrivilegeboolMutableCan bypass RLS, default false
connlimitPrivilegeintMutableConnection limit, -1 means no limit
expire_inValidityintMutableExpire N days from now (higher priority than expire_at)
expire_atValiditystringMutableExpiration date, YYYY-MM-DD format
rolesRolearrayIncrementalRoles array, supports string or object format
parametersParamsobjectMutableRole-level parameters
pgbouncerPoolboolMutableAdd to connection pool, default false
pool_modePoolenumMutablePool mode: transaction (default)
pool_connlimitPoolintMutablePool user max connections

Mutability Notes

MutabilityMeaning
RequiredMust be specified
OptionalOptional field with default value
MutableCan be modified by re-running playbook
IncrementalOnly adds new content, doesn’t remove existing

Basic Parameters

name

  • Type: string
  • Mutability: Required
  • Description: Username, unique identifier within cluster

Username must be a valid PostgreSQL identifier matching regex ^[a-z_][a-z0-9_]{0,62}$:

  • Starts with lowercase letter or underscore
  • Contains only lowercase letters, digits, underscores
  • Max 63 characters
- name: dbuser_app         # standard naming
- name: app_readonly       # underscore separated
- name: _internal          # underscore prefix (for internal roles)

state

  • Type: enum
  • Mutability: Optional
  • Default: create
  • Values: create, absent
  • Description: Target user state
StateDescription
createCreate user (default), update if exists
absentDelete user via DROP ROLE
- name: dbuser_app             # state defaults to create
- name: dbuser_old
  state: absent                # delete user

Note: These system users cannot be deleted via state: absent:

  • postgres (superuser)
  • replicator (or pg_replication_username configured user)
  • dbuser_dba (or pg_admin_username configured user)
  • dbuser_monitor (or pg_monitor_username configured user)

password

  • Type: string
  • Mutability: Mutable
  • Default: None
  • Description: User password

Password can be one of:

  • Plaintext: DBUser.Meta (not recommended for prod)
  • SCRAM-SHA-256 hash: SCRAM-SHA-256$4096:... (recommended)
  • MD5 hash: md5... (legacy compatibility)
# Plaintext (logged to config file, not recommended)
- name: dbuser_app
  password: MySecretPassword

# SCRAM-SHA-256 hash (recommended)
- name: dbuser_app
  password: 'SCRAM-SHA-256$4096:xxx$yyy:zzz'

comment

  • Type: string
  • Mutability: Mutable
  • Default: business user {name}
  • Description: User comment/description

Executes COMMENT ON ROLE statement. Supports special chars (single quotes auto-escaped).

- name: dbuser_app
  comment: 'Main business application account'

Privilege Parameters

login

  • Type: bool
  • Mutability: Mutable
  • Default: true
  • Description: Can login

Set to false creates a Role rather than User, typically used for permission grouping.

# Create login-able user
- name: dbuser_app
  login: true

# Create role (no login)
- name: dbrole_custom
  login: false

superuser

  • Type: bool
  • Mutability: Mutable
  • Default: false
  • Description: Is superuser

createdb

  • Type: bool
  • Mutability: Mutable
  • Default: false
  • Description: Can create databases

createrole

  • Type: bool
  • Mutability: Mutable
  • Default: false
  • Description: Can create roles

inherit

  • Type: bool
  • Mutability: Mutable
  • Default: true
  • Description: Auto-inherit privileges from member roles

Set to false requires explicit SET ROLE to use inherited privileges.

replication

  • Type: bool
  • Mutability: Mutable
  • Default: false
  • Description: Can initiate streaming replication

Usually only replication users (like replicator) need this privilege.

bypassrls

  • Type: bool
  • Mutability: Mutable
  • Default: false
  • Description: Can bypass row-level security (RLS) policies

connlimit

  • Type: int
  • Mutability: Mutable
  • Default: -1 (no limit)
  • Description: Max concurrent connections for user
- name: dbuser_app
  connlimit: 100           # max 100 concurrent connections

- name: dbuser_batch
  connlimit: 10            # limit batch user connections

Validity Parameters

expire_in

  • Type: int
  • Mutability: Mutable
  • Description: Expire N days from current date

This param has higher priority than expire_at. Expiration time recalculated on each playbook run.

- name: temp_user
  expire_in: 30            # expire in 30 days

- name: long_term_user
  expire_in: 3650          # expire in ~10 years

expire_at

  • Type: string
  • Mutability: Mutable
  • Description: Specify expiration date

Format YYYY-MM-DD or special value infinity (never expires).

- name: contractor_user
  expire_at: '2024-12-31'  # expire on specific date

- name: permanent_user
  expire_at: 'infinity'    # never expires

Note: expire_in has higher priority than expire_at. If both specified, only expire_in takes effect.


Role Membership Parameter

roles

  • Type: array
  • Mutability: Incremental
  • Description: Roles this user belongs to

roles array supports two formats:

Simple Format (String)

- name: dbuser_app
  roles:
    - dbrole_readwrite
    - pg_read_all_data

Generated SQL:

GRANT "dbrole_readwrite" TO "dbuser_app";
GRANT "pg_read_all_data" TO "dbuser_app";

Extended Format (Object)

Object format supports finer-grained role membership control:

- name: dbuser_app
  roles:
    - dbrole_readwrite                              # simple string: GRANT role
    - { name: dbrole_admin, admin: true }           # GRANT WITH ADMIN OPTION
    - { name: pg_monitor, set: false }              # PG16+: REVOKE SET OPTION
    - { name: pg_signal_backend, inherit: false }   # PG16+: REVOKE INHERIT OPTION
    - { name: old_role, state: absent }             # REVOKE role membership

Object Format Parameters

ParamTypeDescription
namestringRole name (required)
stateenumgrant (default) or absent/revoke: control membership
adminbooltrue: WITH ADMIN OPTION / false: REVOKE ADMIN
setboolPG16+: true: WITH SET TRUE / false: REVOKE SET
inheritboolPG16+: true: WITH INHERIT TRUE / false: REVOKE INHERIT

PostgreSQL 16+ New Features

PostgreSQL 16 introduced finer-grained role membership control:

  • ADMIN OPTION: Allow granting role to other users
  • SET OPTION: Allow using SET ROLE to switch to this role
  • INHERIT OPTION: Auto-inherit this role’s privileges
# PostgreSQL 16+ complete example
- name: dbuser_app
  roles:
    # Normal membership
    - dbrole_readwrite

    # Can grant dbrole_admin to other users
    - { name: dbrole_admin, admin: true }

    # Cannot SET ROLE to pg_monitor (can only inherit privileges)
    - { name: pg_monitor, set: false }

    # Don't auto-inherit pg_execute_server_program privileges (need explicit SET ROLE)
    - { name: pg_execute_server_program, inherit: false }

    # Revoke old_role membership
    - { name: old_role, state: absent }

Note: set and inherit options only work in PostgreSQL 16+. On earlier versions they’re ignored with warning comments.


Role-Level Parameters

parameters

  • Type: object
  • Mutability: Mutable
  • Description: Role-level config parameters

Set via ALTER ROLE ... SET, params apply to all sessions for this user.

- name: dbuser_analyst
  parameters:
    work_mem: '256MB'
    statement_timeout: '5min'
    search_path: 'analytics,public'
    log_statement: 'all'

Generated SQL:

ALTER USER "dbuser_analyst" SET "work_mem" = '256MB';
ALTER USER "dbuser_analyst" SET "statement_timeout" = '5min';
ALTER USER "dbuser_analyst" SET "search_path" = 'analytics,public';
ALTER USER "dbuser_analyst" SET "log_statement" = 'all';

Reset Parameter to Default

Use special value DEFAULT (case-insensitive) to reset param to PostgreSQL default:

- name: dbuser_app
  parameters:
    work_mem: DEFAULT         # reset to PostgreSQL default
    statement_timeout: '30s'  # set new value

Common Role-Level Parameters

ParameterDescriptionExample
work_memQuery work memory'64MB'
statement_timeoutStatement timeout'30s'
lock_timeoutLock wait timeout'10s'
idle_in_transaction_session_timeoutIdle transaction timeout'10min'
search_pathSchema search path'app,public'
log_statementLog level'ddl'
temp_file_limitTemp file size limit'10GB'

Connection Pool Parameters

These params control user behavior in Pgbouncer connection pool.

pgbouncer

  • Type: bool
  • Mutability: Mutable
  • Default: false
  • Description: Add user to Pgbouncer user list
# Prod user: needs connection pool
- name: dbuser_app
  password: DBUser.App
  pgbouncer: true

# Internal user: no connection pool needed
- name: dbuser_internal
  password: DBUser.Internal
  pgbouncer: false           # default, can be omitted

pool_mode

  • Type: enum
  • Mutability: Mutable
  • Values: transaction, session, statement
  • Default: transaction
  • Description: User-level pool mode
ModeDescriptionUse Case
transactionReturn connection after txn (default)Most OLTP apps
sessionReturn connection after sessionApps needing session state
statementReturn connection after statementSimple stateless queries
# DBA user: session mode (may need SET commands etc.)
- name: dbuser_dba
  pgbouncer: true
  pool_mode: session

# Normal business user: transaction mode
- name: dbuser_app
  pgbouncer: true
  pool_mode: transaction

pool_connlimit

  • Type: int
  • Mutability: Mutable
  • Default: -1 (no limit)
  • Description: User-level max pool connections
- name: dbuser_app
  pgbouncer: true
  pool_connlimit: 50         # max 50 pool connections for this user

ACL System

Pigsty has a built-in, out-of-the-box access control / ACL system. You only need to assign these four default roles to business users:

  • dbrole_readwrite: Global read-write access role (primary business prod accounts should have this)
  • dbrole_readonly: Global read-only access role (for other businesses needing read-only access)
  • dbrole_admin: DDL privileges role (business admins, scenarios requiring table creation in apps)
  • dbrole_offline: Restricted read-only role (can only access offline instances, typically for individual users)

If you want to redesign your own ACL system, consider customizing:


Pgbouncer Users

Pgbouncer is enabled by default as connection pool middleware, with users managed automatically.

Pigsty adds all users in pg_users with explicit pgbouncer: true flag to the pgbouncer user list.

Users in Pgbouncer connection pool are listed in /etc/pgbouncer/userlist.txt:

"postgres" ""
"dbuser_wiki" "SCRAM-SHA-256$4096:+77dyhrPeFDT/TptHs7/7Q==$KeatuohpKIYzHPCt/tqBu85vI11o9mar/by0hHYM2W8=:X9gig4JtjoS8Y/o1vQsIX/gY1Fns8ynTXkbWOjUfbRQ="
"dbuser_view" "SCRAM-SHA-256$4096:DFoZHU/DXsHL8MJ8regdEw==$gx9sUGgpVpdSM4o6A2R9PKAUkAsRPLhLoBDLBUYtKS0=:MujSgKe6rxcIUMv4GnyXJmV0YNbf39uFRZv724+X1FE="
"dbuser_monitor" "SCRAM-SHA-256$4096:fwU97ZMO/KR0ScHO5+UuBg==$CrNsmGrx1DkIGrtrD1Wjexb/aygzqQdirTO1oBZROPY=:L8+dJ+fqlMQh7y4PmVR/gbAOvYWOr+KINjeMZ8LlFww="
"dbuser_meta" "SCRAM-SHA-256$4096:leB2RQPcw1OIiRnPnOMUEg==$eyC+NIMKeoTxshJu314+BmbMFpCcspzI3UFZ1RYfNyU=:fJgXcykVPvOfro2MWNkl5q38oz21nSl1dTtM65uYR1Q="

User-level connection pool params are maintained in /etc/pgbouncer/useropts.txt:

dbuser_dba                  = pool_mode=session max_user_connections=16
dbuser_monitor              = pool_mode=session max_user_connections=8

When you create users, Pgbouncer’s user list definition file will be refreshed and take effect via online config reload, without affecting existing connections.

Pgbouncer runs with the same dbsu as PostgreSQL, defaulting to the postgres OS user. You can use the pgb alias to access pgbouncer admin functions using dbsu.

Note that the pgbouncer_auth_query param allows dynamic query for connection pool user auth—a compromise when you’re lazy about managing pool users.

For user management operations, see User Management.

For user access privileges, see ACL: Role Privileges.

5 - Database

Database refers to logical objects created by the SQL command CREATE DATABASE within a database cluster.

In this context, database refers to logical objects created by the SQL command CREATE DATABASE within a database cluster.

A PostgreSQL server can serve multiple databases simultaneously. In Pigsty, you can define the required databases in the cluster config.

Pigsty modifies and customizes the default template database template1, creating default schemas, installing default extensions, and configuring default privileges. Newly created databases will inherit these settings from template1 by default.

By default, all business databases are added 1:1 to the Pgbouncer connection pool; pg_exporter will automatically discover all business databases through an auto-discovery mechanism and monitor objects within them.


Define Database

Business databases are defined in the cluster parameter pg_databases, which is an array of database definition objects. Databases in the array are created sequentially in definition order, so databases defined later can use previously defined databases as templates.

Here is the database definition in the default pg-meta cluster in the Pigsty demo env:

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta
    pg_databases:
      - { name: meta ,baseline: cmdb.sql ,comment: pigsty meta database ,schemas: [pigsty] ,extensions: [{name: postgis, schema: public}, {name: timescaledb}]}
      - { name: grafana  ,owner: dbuser_grafana  ,revokeconn: true ,comment: grafana primary database }
      - { name: bytebase ,owner: dbuser_bytebase ,revokeconn: true ,comment: bytebase primary database }
      - { name: kong     ,owner: dbuser_kong     ,revokeconn: true ,comment: kong the api gateway database }
      - { name: gitea    ,owner: dbuser_gitea    ,revokeconn: true ,comment: gitea meta database }
      - { name: wiki     ,owner: dbuser_wiki     ,revokeconn: true ,comment: wiki meta database }
      - { name: noco     ,owner: dbuser_noco     ,revokeconn: true ,comment: nocodb database }

Each database definition is an object that may include the following fields, using the meta database as an example:

- name: meta                      # Required, `name` is the only mandatory field
  state: create                   # Optional, database state: create (default), absent (delete), recreate (rebuild)
  baseline: cmdb.sql              # Optional, SQL baseline file path (relative to ansible search path, e.g., files/)
  pgbouncer: true                 # Optional, add to pgbouncer database list? Default true
  schemas: [pigsty]               # Optional, schemas to create, array of schema names
  extensions:                     # Optional, extensions to install: array of extension objects
    - { name: postgis , schema: public }  # can specify schema for extension
    - { name: timescaledb }               # some extensions create their own fixed schemas
  comment: pigsty meta database   # Optional, database comment
  owner: postgres                 # Optional, database owner, default postgres
  template: template1             # Optional, template to use, default template1
  strategy: FILE_COPY             # Optional, clone strategy: FILE_COPY or WAL_LOG (PG15+)
  encoding: UTF8                  # Optional, inherits from template/cluster config (UTF8)
  locale: C                       # Optional, inherits from template/cluster config (C)
  lc_collate: C                   # Optional, inherits from template/cluster config (C)
  lc_ctype: C                     # Optional, inherits from template/cluster config (C)
  locale_provider: libc           # Optional, locale provider: libc, icu, builtin (PG15+)
  icu_locale: en-US               # Optional, ICU locale rule (PG15+)
  icu_rules: ''                   # Optional, ICU collation rule (PG16+)
  builtin_locale: C.UTF-8         # Optional, builtin locale provider rule (PG17+)
  tablespace: pg_default          # Optional, default tablespace, default 'pg_default'
  is_template: false              # Optional, mark as template database, allows any user with CREATEDB to clone
  allowconn: true                 # Optional, allow connections, default true. Explicit false prohibits all connections
  revokeconn: false               # Optional, revoke public connect privilege. Default false, true revokes CONNECT from non-owner/admin
  register_datasource: true       # Optional, register as grafana datasource? Default true, false skips registration
  connlimit: -1                   # Optional, connection limit, default -1 (no limit)
  parameters:                     # Optional, database-level params via ALTER DATABASE SET
    work_mem: '64MB'
    statement_timeout: '30s'
  pool_auth_user: dbuser_meta     # Optional, auth user for this pgbouncer database (requires pgbouncer_auth_query)
  pool_mode: transaction          # Optional, database-level pgbouncer pool mode, default transaction
  pool_size: 64                   # Optional, database-level pool size, default 64
  pool_reserve: 32                # Optional, database-level pool reserve, default 32
  pool_size_min: 0                # Optional, database-level min pool size, default 0
  pool_connlimit: 100             # Optional, database-level max connections, default 100

The only required field is name, which should be a valid and unique database name in the current PostgreSQL cluster.


Parameter Overview

FieldCategoryTypeMutabilityDescription
nameBasicstringRequiredDatabase name, must be valid and unique identifier
stateBasicenumOptionalDatabase state: create (default), absent, recreate
ownerBasicstringMutableDatabase owner, default postgres
commentBasicstringMutableDatabase comment
templateTemplatestringImmutableTemplate database to clone from, default template1
strategyTemplateenumImmutableClone strategy: FILE_COPY or WAL_LOG (PG15+)
encodingEncodingstringImmutableCharacter encoding, inherits from template (UTF8)
localeEncodingstringImmutableLocale rule, inherits from template (C)
lc_collateEncodingstringImmutableCollation rule, inherits from template (C)
lc_ctypeEncodingstringImmutableCharacter classification, inherits from template (C)
locale_providerEncodingenumImmutableLocale provider: libc, icu, builtin (PG15+)
icu_localeEncodingstringImmutableICU locale rule (PG15+)
icu_rulesEncodingstringImmutableICU collation custom rule (PG16+)
builtin_localeEncodingstringImmutableBuiltin locale provider rule (PG17+)
tablespaceStoragestringMutableDefault tablespace, may trigger data migration
is_templatePrivilegeboolMutableMark as template database
allowconnPrivilegeboolMutableAllow connections, default true
revokeconnPrivilegeboolMutableRevoke PUBLIC CONNECT privilege
connlimitPrivilegeintMutableConnection limit, -1 means no limit
baselineInitstringOne-timeSQL baseline file path, runs only on first creation
schemasInit(string|object)[]IncrementalSchema definitions to create
extensionsInitobject[]IncrementalExtension definitions to install
parametersInitobjectMutableDatabase-level parameters
pgbouncerPoolboolMutableAdd to connection pool, default true
pool_modePoolenumMutablePool mode: transaction (default)
pool_sizePoolintMutableDefault pool size, default 64
pool_size_minPoolintMutableMin pool size, default 0
pool_reservePoolintMutableReserve pool size, default 32
pool_connlimitPoolintMutableMax database connections, default 100
pool_auth_userPoolstringMutableAuth query user
register_datasourceMonitorboolMutableRegister to Grafana datasource, default true

Mutability Notes

MutabilityMeaning
RequiredMust be specified
OptionalOptional field with default value
ImmutableOnly effective at creation, requires rebuild to change
MutableCan be modified by re-running playbook
One-timeOnly runs on first creation, skipped if db exists
IncrementalOnly adds new content, doesn’t remove existing

Basic Parameters

name

  • Type: string
  • Mutability: Required
  • Description: Database name, unique identifier within cluster

Database name should be a valid PostgreSQL identifier. Recommend lowercase letters, digits, and underscores.

- name: myapp              # simple naming
- name: my_application     # underscore separated
- name: app_v2             # with version number

state

  • Type: enum
  • Mutability: Optional
  • Default: create
  • Values: create, absent, recreate
  • Description: Target database state
StateDescription
createCreate database (default), skip if exists
absentDelete database via DROP DATABASE WITH (FORCE)
recreateDelete then create, used to reset database
- name: myapp                # state defaults to create
- name: olddb
  state: absent              # delete database
- name: testdb
  state: recreate            # rebuild database

owner

  • Type: string
  • Mutability: Mutable
  • Default: postgres (current user)
  • Description: Database owner

Specified user must already exist. Changing owner executes:

ALTER DATABASE "myapp" OWNER TO "new_owner";
GRANT ALL PRIVILEGES ON DATABASE "myapp" TO "new_owner";

comment

  • Type: string
  • Mutability: Mutable
  • Default: business database {name}
  • Description: Database comment

Executes COMMENT ON DATABASE statement. Supports special chars (single quotes auto-escaped).


Template & Clone Parameters

template

  • Type: string
  • Mutability: Immutable
  • Default: template1
  • Description: Template database for creation

Common templates:

TemplateDescription
template1Default template, includes Pigsty preconfigured extensions and privileges
template0Clean template, required when specifying different encoding/locale
Custom DBCan use existing database as template to clone

Important: When using icu or builtin locale provider, you must specify template: template0.

- name: myapp_icu
  template: template0        # required when using ICU
  locale_provider: icu
  icu_locale: en-US

strategy

  • Type: enum
  • Mutability: Immutable
  • Version: PostgreSQL 15+
  • Values: FILE_COPY, WAL_LOG
  • Description: Strategy for cloning from template
StrategyDescriptionUse Case
FILE_COPYDirect file copy (PG15+ default)Large templates, general use
WAL_LOGCopy via WAL logsSmall templates, non-blocking

Ignored on PostgreSQL 14 and earlier versions.


Encoding & Locale Parameters

encoding

  • Type: string
  • Mutability: Immutable
  • Default: Inherits from template (usually UTF8)
  • Description: Database character encoding

Common encodings: UTF8, LATIN1, SQL_ASCII

locale

  • Type: string
  • Mutability: Immutable
  • Default: Inherits from template (usually C)
  • Description: Database locale rule, sets both lc_collate and lc_ctype

lc_collate

  • Type: string
  • Mutability: Immutable
  • Default: Inherits from template (usually C)
  • Description: String collation rule

Common values: C, C.UTF-8, en_US.UTF-8, zh_CN.UTF-8

lc_ctype

  • Type: string
  • Mutability: Immutable
  • Default: Inherits from template (usually C)
  • Description: Character classification rule (upper/lower case, digits, etc.)

locale_provider

  • Type: enum
  • Mutability: Immutable
  • Version: PostgreSQL 15+
  • Values: libc, icu, builtin
  • Default: libc
  • Description: Locale implementation provider
ProviderVersionDescription
libc-Uses OS C library, traditional default
icuPG15+Uses ICU library, cross-platform consistent
builtinPG17+PostgreSQL built-in, most efficient for C/C.UTF-8

Note: When using icu or builtin, you must specify template: template0.

icu_locale

  • Type: string
  • Mutability: Immutable
  • Version: PostgreSQL 15+
  • Description: ICU locale identifier

Common values:

ValueDescription
en-USAmerican English
en-GBBritish English
zh-HansSimplified Chinese
zh-HantTraditional Chinese
ja-JPJapanese
ko-KRKorean
- name: chinese_db
  template: template0
  locale_provider: icu
  icu_locale: zh-Hans
  encoding: UTF8

icu_rules

  • Type: string
  • Mutability: Immutable
  • Version: PostgreSQL 16+
  • Description: ICU collation custom rule

Used to customize sorting behavior with ICU rule syntax.

- name: custom_sort_db
  template: template0
  locale_provider: icu
  icu_locale: en-US
  icu_rules: '&V << w <<< W'  # customize V/W sorting

builtin_locale

  • Type: string
  • Mutability: Immutable
  • Version: PostgreSQL 17+
  • Values: C, C.UTF-8
  • Description: Builtin locale provider rule

builtin provider is faster than libc, especially suitable when only C or C.UTF-8 collation is needed.

- name: fast_db
  template: template0
  locale_provider: builtin
  builtin_locale: C.UTF-8
  encoding: UTF8

Storage & Privilege Parameters

tablespace

  • Type: string
  • Mutability: Mutable
  • Default: pg_default
  • Description: Database default tablespace

Changing tablespace triggers physical data migration, may take long time for large databases.

- name: archive_db
  tablespace: slow_hdd       # use slow storage for archive data

is_template

  • Type: bool
  • Mutability: Mutable
  • Default: false
  • Description: Mark as template database

When set to true, any user with CREATEDB privilege can use this database as template to clone new databases.

- name: app_template
  is_template: true          # allow normal users to clone
  schemas: [core, api]
  extensions: [postgis]

Note: Databases marked is_template: true will first execute ALTER DATABASE ... IS_TEMPLATE false when deleted.

allowconn

  • Type: bool
  • Mutability: Mutable
  • Default: true
  • Description: Allow connections to this database

Setting to false completely prohibits any user from connecting (including superusers).

- name: archive_db
  allowconn: false           # prohibit connections

revokeconn

  • Type: bool
  • Mutability: Mutable
  • Default: false
  • Description: Revoke PUBLIC CONNECT privilege

When set to true:

  • Revokes PUBLIC CONNECT privilege
  • Grants replicator, monitor connection privilege
  • Grants admin, owner connection privilege (WITH GRANT OPTION)

When set to false:

  • Restores PUBLIC CONNECT privilege
- name: secure_db
  owner: dbuser_secure
  revokeconn: true           # only specified users can connect

connlimit

  • Type: int
  • Mutability: Mutable
  • Default: -1 (no limit)
  • Description: Database max connection limit
- name: limited_db
  connlimit: 50              # max 50 concurrent connections

Initialization Parameters

baseline

  • Type: string
  • Mutability: One-time
  • Description: SQL baseline file path

Specifies SQL file to execute after database creation for initializing table structure, data, etc.

  • Path is relative to Ansible search path (usually files/ directory)
  • Only executes on first database creation
  • Re-executes when using state: recreate
- name: myapp
  baseline: myapp_init.sql   # will search files/myapp_init.sql

schemas

  • Type: (string | object)[]
  • Mutability: Incremental
  • Description: Schema definitions to create

Supports two formats:

schemas:
  # Simple format: schema name only
  - app
  - api

  # Full format: object definition
  - name: core               # schema name (required)
    owner: dbuser_app        # schema owner (optional)
  - name: old_schema
    state: absent            # delete schema

Schema owner: Use owner to specify schema owner, generates AUTHORIZATION clause:

- name: myapp
  owner: dbuser_myapp
  schemas:
    - name: app
      owner: dbuser_myapp    # schema owner same as database owner
    - name: audit
      owner: dbuser_audit    # schema owner is different user

Generated SQL:

CREATE SCHEMA IF NOT EXISTS "app" AUTHORIZATION "dbuser_myapp";
CREATE SCHEMA IF NOT EXISTS "audit" AUTHORIZATION "dbuser_audit";

Delete schema: Use state: absent to delete schema:

schemas:
  - { name: deprecated_schema, state: absent }

Generated SQL:

DROP SCHEMA IF EXISTS "deprecated_schema" CASCADE;

Note:

  • Create operations are incremental, uses IF NOT EXISTS
  • Delete operations use CASCADE, deletes all objects in schema

extensions

  • Type: object[]
  • Mutability: Incremental
  • Description: Extension definitions to install

Supports two formats:

extensions:
  # Simple format: extension name only
  - postgis
  - pg_trgm

  # Full format: object definition
  - name: vector             # extension name (required)
    schema: public           # install to specified schema (optional)
    version: '0.5.1'         # specify version (optional)
    state: absent            # set absent to uninstall extension (optional)

Uninstall extension: Use state: absent to uninstall:

extensions:
  - { name: pg_trgm, state: absent }  # uninstall pg_trgm

Uninstall executes DROP EXTENSION IF EXISTS "name" CASCADE. Note CASCADE deletes dependent objects.

parameters

  • Type: object
  • Mutability: Mutable
  • Description: Database-level config parameters

Set via ALTER DATABASE ... SET, params apply to all sessions connecting to this database.

- name: analytics
  parameters:
    work_mem: '256MB'
    maintenance_work_mem: '512MB'
    statement_timeout: '5min'
    search_path: 'analytics,public'

Reset parameter: Use special value DEFAULT (case-insensitive) to reset to PostgreSQL default:

- name: myapp
  parameters:
    work_mem: DEFAULT        # reset to default
    statement_timeout: '30s' # set new value

Generated SQL:

ALTER DATABASE "myapp" SET "work_mem" = DEFAULT;
ALTER DATABASE "myapp" SET "statement_timeout" = '30s';

Connection Pool Parameters

These params control database behavior in Pgbouncer connection pool.

pgbouncer

  • Type: bool
  • Mutability: Mutable
  • Default: true
  • Description: Add database to Pgbouncer connection pool
- name: internal_db
  pgbouncer: false           # not accessed via connection pool

pool_mode

  • Type: enum
  • Mutability: Mutable
  • Values: transaction, session, statement
  • Default: transaction
  • Description: Database-level pool mode
ModeDescriptionUse Case
transactionReturn connection after txnMost OLTP apps
sessionReturn connection after sessionApps needing session state
statementReturn connection after statementSimple stateless queries

pool_size

  • Type: int
  • Mutability: Mutable
  • Default: 64
  • Description: Database default pool size

pool_size_min

  • Type: int
  • Mutability: Mutable
  • Default: 0
  • Description: Minimum pool size, pre-warmed connections

pool_reserve

  • Type: int
  • Mutability: Mutable
  • Default: 32
  • Description: Reserve connections, extra burst connections available when default pool exhausted

pool_connlimit

  • Type: int
  • Mutability: Mutable
  • Default: 100
  • Description: Max connections accessing this database via pool

pool_auth_user

  • Type: string
  • Mutability: Mutable
  • Description: Auth query user

Requires pgbouncer_auth_query enabled. When specified, all connections to this database use this user to query passwords.


Monitoring Parameter

register_datasource

  • Type: bool
  • Mutability: Mutable
  • Default: true
  • Description: Register to Grafana datasource

Set to false to skip Grafana datasource registration, suitable for temporary databases not needing monitoring.


Template Inheritance

Many params inherit from template database if not explicitly specified. Default template is template1, whose encoding settings are determined by cluster init params:

Cluster ParameterDefaultDescription
pg_encodingUTF8Cluster default encoding
pg_localeCCluster default locale
pg_lc_collateCCluster default collation
pg_lc_ctypeCCluster default ctype

Newly created databases are forked from template1 by default. This template database is customized during PG_PROVISION phase: configured with extensions, schemas, and default privileges, so newly created databases also inherit these configs, unless you explicitly use another database as template.

For database management operations, see Database Management.

For database access privileges, see ACL: Database Privileges.

6 - HBA Rules

Detailed explanation of PostgreSQL and Pgbouncer Host-Based Authentication (HBA) rules configuration in Pigsty.

HBA (Host-Based Authentication) controls “who can connect to the database from where and how”. Pigsty manages HBA rules declaratively through pg_default_hba_rules and pg_hba_rules.


Overview

Pigsty renders the following config files during cluster init or HBA refresh:

Config FilePathDescription
PostgreSQL HBA/pg/data/pg_hba.confPostgreSQL server HBA rules
Pgbouncer HBA/etc/pgbouncer/pgb_hba.confConnection pool Pgbouncer HBA rules

HBA rules are controlled by these parameters:

ParameterLevelDescription
pg_default_hba_rulesGPostgreSQL global default HBA rules
pg_hba_rulesG/C/IPostgreSQL cluster/instance additional rules
pgb_default_hba_rulesGPgbouncer global default HBA rules
pgb_hba_rulesG/C/IPgbouncer cluster/instance additional rules

Rule features:

  • Role filtering: Rules support role field, auto-filter based on instance’s pg_role
  • Order sorting: Rules support order field, controls position in final config file
  • Two syntaxes: Supports alias form (simplified) and raw form (direct HBA text)

Parameter Reference

pg_default_hba_rules

PostgreSQL global default HBA rule list, usually defined in all.vars, provides base access control for all PostgreSQL clusters.

  • Type: rule[]
  • Level: Global (G)
  • Default: See below
pg_default_hba_rules:
  - {user: '${dbsu}'    ,db: all         ,addr: local     ,auth: ident ,title: 'dbsu access via local os user ident'  ,order: 100}
  - {user: '${dbsu}'    ,db: replication ,addr: local     ,auth: ident ,title: 'dbsu replication from local os ident' ,order: 150}
  - {user: '${repl}'    ,db: replication ,addr: localhost ,auth: pwd   ,title: 'replicator replication from localhost',order: 200}
  - {user: '${repl}'    ,db: replication ,addr: intra     ,auth: pwd   ,title: 'replicator replication from intranet' ,order: 250}
  - {user: '${repl}'    ,db: postgres    ,addr: intra     ,auth: pwd   ,title: 'replicator postgres db from intranet' ,order: 300}
  - {user: '${monitor}' ,db: all         ,addr: localhost ,auth: pwd   ,title: 'monitor from localhost with password' ,order: 350}
  - {user: '${monitor}' ,db: all         ,addr: infra     ,auth: pwd   ,title: 'monitor from infra host with password',order: 400}
  - {user: '${admin}'   ,db: all         ,addr: infra     ,auth: ssl   ,title: 'admin @ infra nodes with pwd & ssl'   ,order: 450}
  - {user: '${admin}'   ,db: all         ,addr: world     ,auth: ssl   ,title: 'admin @ everywhere with ssl & pwd'    ,order: 500}
  - {user: '+dbrole_readonly',db: all    ,addr: localhost ,auth: pwd   ,title: 'pgbouncer read/write via local socket',order: 550}
  - {user: '+dbrole_readonly',db: all    ,addr: intra     ,auth: pwd   ,title: 'read/write biz user via password'     ,order: 600}
  - {user: '+dbrole_offline' ,db: all    ,addr: intra     ,auth: pwd   ,title: 'allow etl offline tasks from intranet',order: 650}

pg_hba_rules

PostgreSQL cluster/instance-level additional HBA rules, can be overridden at cluster or instance level, merged with default rules and sorted by order.

  • Type: rule[]
  • Level: Global/Cluster/Instance (G/C/I)
  • Default: []
pg_hba_rules:
  - {user: app_user, db: app_db, addr: intra, auth: pwd, title: 'app user access'}

pgb_default_hba_rules

Pgbouncer global default HBA rule list, usually defined in all.vars.

  • Type: rule[]
  • Level: Global (G)
  • Default: See below
pgb_default_hba_rules:
  - {user: '${dbsu}'    ,db: pgbouncer   ,addr: local     ,auth: peer  ,title: 'dbsu local admin access with os ident',order: 100}
  - {user: 'all'        ,db: all         ,addr: localhost ,auth: pwd   ,title: 'allow all user local access with pwd' ,order: 150}
  - {user: '${monitor}' ,db: pgbouncer   ,addr: intra     ,auth: pwd   ,title: 'monitor access via intranet with pwd' ,order: 200}
  - {user: '${monitor}' ,db: all         ,addr: world     ,auth: deny  ,title: 'reject all other monitor access addr' ,order: 250}
  - {user: '${admin}'   ,db: all         ,addr: intra     ,auth: pwd   ,title: 'admin access via intranet with pwd'   ,order: 300}
  - {user: '${admin}'   ,db: all         ,addr: world     ,auth: deny  ,title: 'reject all other admin access addr'   ,order: 350}
  - {user: 'all'        ,db: all         ,addr: intra     ,auth: pwd   ,title: 'allow all user intra access with pwd' ,order: 400}

pgb_hba_rules

Pgbouncer cluster/instance-level additional HBA rules.

  • Type: rule[]
  • Level: Global/Cluster/Instance (G/C/I)
  • Default: []

Note: Pgbouncer HBA does not support db: replication.


Rule Fields

Each HBA rule is a YAML dict supporting these fields:

FieldTypeRequiredDefaultDescription
userstringNoallUsername, supports all, variable placeholders, +rolename, etc.
dbstringNoallDatabase name, supports all, replication, specific db name
addrstringYes*-Address alias or CIDR, see Address Aliases
authstringNopwdAuth method alias, see Auth Methods
titlestringNo-Rule description/comment, rendered as comment in config file
rolestringNocommonInstance role filter, see Role Filtering
orderintNo1000Sort weight, lower numbers first, see Order Sorting
ruleslistYes*-Raw HBA text lines, mutually exclusive with addr

Either addr or rules must be specified. Use rules to write raw HBA format directly.


Address Aliases

Pigsty provides address aliases to simplify HBA rule writing:

AliasExpands ToDescription
localUnix socketLocal Unix socket connection
localhostUnix socket + 127.0.0.1/32 + ::1/128Loopback addresses
admin${admin_ip}/32Admin IP address
infraAll infra group node IPsInfrastructure node list
clusterAll current cluster member IPsAll instances in same cluster
intra / intranet10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16Intranet CIDR ranges
world / all0.0.0.0/0 + ::/0Any address (IPv4 + IPv6)
<CIDR>Direct usee.g., 192.168.1.0/24, 10.1.1.100/32

Intranet CIDRs can be customized via node_firewall_intranet:

node_firewall_intranet:
  - 10.0.0.0/8
  - 172.16.0.0/12
  - 192.168.0.0/16

Auth Methods

Pigsty provides auth method aliases for simplified config:

AliasActual MethodConnection TypeDescription
pwdscram-sha-256 or md5hostAuto-select based on pg_pwd_enc
sslscram-sha-256 or md5hostsslForce SSL + password
ssl-shascram-sha-256hostsslForce SSL + SCRAM-SHA-256
ssl-md5md5hostsslForce SSL + MD5
certcerthostsslClient certificate auth
trusttrusthostUnconditional trust (dangerous)
deny / rejectrejecthostReject connection
identidenthostOS user mapping (PostgreSQL)
peerpeerlocalOS user mapping (Pgbouncer/local)

pg_pwd_enc defaults to scram-sha-256, can be set to md5 for legacy client compatibility.


User Variables

HBA rules support these user placeholders, auto-replaced with actual usernames during rendering:

PlaceholderDefaultDescription
${dbsu}postgresDatabase superuser
${repl}replicatorReplication user
${monitor}dbuser_monitorMonitor user
${admin}dbuser_dbaAdmin user

Actual values controlled by corresponding params:

pg_dbsu: postgres
pg_replication_username: replicator
pg_monitor_username: dbuser_monitor
pg_admin_username: dbuser_dba

Role Filtering

The role field in HBA rules controls which instances the rule applies to:

RoleDescription
commonDefault, applies to all instances
primaryPrimary instance only
replicaReplica instance only
offlineOffline instance only (pg_role: offline or pg_offline_query: true)
standbyStandby instance
delayedDelayed replica instance

Role filtering matches based on instance’s pg_role variable. Non-matching rules are commented out (prefixed with #).

pg_hba_rules:
  # Only applies on primary
  - {user: writer, db: all, addr: intra, auth: pwd, role: primary, title: 'writer only on primary'}

  # Only applies on offline instances
  - {user: '+dbrole_offline', db: all, addr: '172.20.0.0/16', auth: ssl, role: offline, title: 'offline dedicated'}

Order Sorting

PostgreSQL HBA is first-match-wins, rule order is critical. Pigsty controls rule rendering order via the order field.

Order Interval Convention

IntervalUsage
0 - 99User high-priority rules (before all default rules)
100 - 650Default rule zone (spaced by 50 for easy insertion)
1000+User rule default (rules without order append to end)

Default Rule Order Assignment

PostgreSQL Default Rules:

OrderRule Description
100dbsu local ident
150dbsu replication local
200replicator localhost
250replicator intra replication
300replicator intra postgres
350monitor localhost
400monitor infra
450admin infra ssl
500admin world ssl
550dbrole_readonly localhost
600dbrole_readonly intra
650dbrole_offline intra

Pgbouncer Default Rules:

OrderRule Description
100dbsu local peer
150all localhost pwd
200monitor pgbouncer intra
250monitor world deny
300admin intra pwd
350admin world deny
400all intra pwd

Sorting Example

pg_hba_rules:
  # order: 0, before all default rules (blacklist)
  - {user: all, db: all, addr: '10.1.1.100/32', auth: deny, order: 0, title: 'blacklist bad ip'}

  # order: 120, between dbsu(100) and replicator(200)
  - {user: auditor, db: all, addr: local, auth: ident, order: 120, title: 'auditor access'}

  # order: 420, between monitor(400) and admin(450)
  - {user: exporter, db: all, addr: infra, auth: pwd, order: 420, title: 'prometheus exporter'}

  # no order, defaults to 1000, appends after all default rules
  - {user: app_user, db: app_db, addr: intra, auth: pwd, title: 'app user access'}

Syntax Examples

Alias Form

Using Pigsty simplified syntax:

pg_hba_rules:
  - title: allow grafana view access
    role: primary
    user: dbuser_view
    db: meta
    addr: infra
    auth: ssl

Rendered result:

# allow grafana view access [primary]
hostssl  meta               dbuser_view        10.10.10.10/32     scram-sha-256

Raw Form

Using PostgreSQL HBA syntax directly:

pg_hba_rules:
  - title: allow intranet password access
    role: common
    rules:
      - host all all 10.0.0.0/8 scram-sha-256
      - host all all 172.16.0.0/12 scram-sha-256
      - host all all 192.168.0.0/16 scram-sha-256

Rendered result:

# allow intranet password access [common]
host all all 10.0.0.0/8 scram-sha-256
host all all 172.16.0.0/12 scram-sha-256
host all all 192.168.0.0/16 scram-sha-256

Common Configuration Examples

1. Intranet Password Access to Business DBs

pg_hba_rules:
  - title: 'intra readwrite access'
    role: common
    user: '+dbrole_readwrite'
    db: all
    addr: intra
    auth: pwd

Effect: All business read-write roles can access any database from intranet using password.

2. Offline Instance Dedicated Network

pg_hba_rules:
  - title: 'offline replica dedicated network'
    role: offline
    user: '+dbrole_offline'
    db: all
    addr: 172.20.0.0/16
    auth: ssl-sha

Effect: Only instances with pg_role: offline or pg_offline_query: true enable this rule.

3. Blacklist IP

pg_hba_rules:
  - user: all
    db: all
    addr: '10.1.1.100/32'
    auth: deny
    order: 0
    title: 'block compromised host'

Effect: order: 0 ranks before all default rules (100+), matches and rejects first.

4. Whitelist Specific Application

pg_hba_rules:
  - title: 'allow app server access'
    user: app_user
    db: app_db
    addr: '192.168.1.10/32'
    auth: ssl
    order: 50

Effect: Specific app server uses SSL connection, high priority (50) ensures match before default rules.

5. Admin Forced Certificate Auth

pg_hba_rules:
  - title: 'admin cert access'
    role: common
    user: '${admin}'
    db: all
    addr: world
    auth: cert
    order: 10

Effect: Admin must carry client certificate to connect, order: 10 takes priority over default ssl rules (450/500).

6. Allow Internet Read-Only Access

pg_hba_rules:
  - title: 'readonly from internet'
    role: replica
    user: '+dbrole_readonly'
    db: all
    addr: world
    auth: ssl

Effect: Read-only users can connect to replicas from internet via SSL.

7. Pgbouncer Dedicated Rules

pgb_hba_rules:
  - title: 'app via pgbouncer'
    role: common
    user: '+dbrole_readwrite'
    db: all
    addr: world
    auth: ssl

Note: Pgbouncer HBA does not support db: replication.

8. Multi-Condition Combination

pg_hba_rules:
  # Dev env: trust local connections
  - {user: all, db: all, addr: local, auth: trust, title: 'dev trust local'}

  # Prod env: strict SSL
  - {user: '+dbrole_readwrite', db: all, addr: intra, auth: ssl-sha, title: 'prod ssl only'}

  # Monitor dedicated: from Prometheus nodes
  - {user: '${monitor}', db: all, addr: infra, auth: pwd, order: 380, title: 'prometheus access'}

9. Restrict Access by Database

pg_hba_rules:
  # Finance system: only specific network
  - {user: fin_user, db: finance_db, addr: '10.20.0.0/16', auth: ssl, title: 'finance restricted'}

  # HR system: only HR network
  - {user: hr_user, db: hr_db, addr: '10.30.0.0/16', auth: ssl, title: 'hr restricted'}

10. Complete Cluster Config Example

pg-prod:
  hosts:
    10.10.10.11: {pg_seq: 1, pg_role: primary}
    10.10.10.12: {pg_seq: 2, pg_role: replica}
    10.10.10.13: {pg_seq: 3, pg_role: offline}
  vars:
    pg_cluster: pg-prod

    pg_hba_rules:
      # Blacklist: known malicious IP
      - {user: all, db: all, addr: '10.1.1.100/32', auth: deny, order: 0, title: 'blacklist'}

      # App server whitelist
      - {user: app_user, db: app_db, addr: '192.168.1.0/24', auth: ssl, order: 50, title: 'app servers'}

      # ETL tasks: offline instances only
      - {user: etl_user, db: all, addr: '172.20.0.0/16', auth: pwd, role: offline, title: 'etl tasks'}

      # Enhanced monitoring
      - {user: '${monitor}', db: all, addr: cluster, auth: pwd, order: 380, title: 'cluster monitor'}

    pgb_hba_rules:
      # App via connection pool
      - {user: '+dbrole_readwrite', db: all, addr: '192.168.1.0/24', auth: ssl, title: 'app via pgbouncer'}

Rendering Principle

Pigsty uses Jinja2 templates to render HBA config files:

  1. Merge rules: pg_default_hba_rules + pg_hba_rules
  2. Sort rules: Ascending by order field (rules without order append to end)
  3. Role filter: Filter based on instance pg_role, non-matching rules commented out
  4. Variable replace: ${dbsu} etc. replaced with actual usernames
  5. Address expand: intra, infra etc. expanded to actual IP/CIDRs
  6. Auth map: pwd, ssl etc. mapped to actual auth methods

Template locations:

  • PostgreSQL: roles/pgsql/templates/pg_hba.conf
  • Pgbouncer: roles/pgsql/templates/pgbouncer.hba

Important Notes

  1. Order sensitive: PostgreSQL HBA is first-match-wins, rule order matters
  2. Role matching: Ensure role field matches target instance’s pg_role
  3. Address validation: CIDR format must be correct, e.g., 10.0.0.0/8 not 10.0.0.0/255.0.0.0
  4. Pgbouncer limitation: Does not support db: replication
  5. Variable scope: User variables limited to four predefined (${dbsu}, ${repl}, ${monitor}, ${admin})
  6. SSL config: Ensure SSL is properly configured before using ssl, cert auth methods
  7. Test first: Recommend validating in test env before modifying HBA

Testing & Verification

Pigsty provides HBA order sorting test tools to verify config correctness before deployment:

Run Sorting Logic Test

# Run sorting logic test in pigsty directory
./files/test-hba-order.yml

This test verifies:

  • Rules sorted correctly by order field
  • Rules without order append to end
  • Same order values maintain original order (stable sort)
  • Backward compatibility (old configs need no changes)

Run Template Rendering Test

# Test HBA template rendering on target server
./files/test-hba-render.yml -l 10.10.10.10

This test renders HBA template on target server, verifying:

  • Template syntax correct
  • Rule order as expected
  • High-priority rules appear first

Verify Rendered Result

# View rendered PostgreSQL HBA
cat /pg/data/pg_hba.conf

# View rule titles order (verify order is effective)
grep '^#' /pg/data/pg_hba.conf | grep -v '^#=' | head -20

# Verify first rule is expected high-priority rule
head -30 /pg/data/pg_hba.conf

ParameterDescription
pg_pwd_encPassword encryption: scram-sha-256 (default) or md5
pg_dbsuDatabase superuser name
pg_replication_usernameReplication username
pg_monitor_usernameMonitor username
pg_admin_usernameAdmin username
node_firewall_intranetIntranet CIDR definition

7 - Access Control

Default role system and privilege model provided by Pigsty

Access control is determined by the combination of “role system + privilege templates + HBA”. This section focuses on how to declare roles and object privileges through configuration parameters.

Pigsty provides a streamlined ACL model, fully described by the following parameters:

  • pg_default_roles: System roles and system users.
  • pg_users: Business users and roles.
  • pg_default_privileges: Default privileges for objects created by administrators/owners.
  • pg_revoke_public, pg_default_schemas, pg_default_extensions: Control the default behavior of template1.

After understanding these parameters, you can write fully reproducible privilege configurations.


Default Role System (pg_default_roles)

By default, it includes 4 business roles + 4 system users:

NameTypeDescription
dbrole_readonlyNOLOGINShared by all business, has SELECT/USAGE
dbrole_readwriteNOLOGINInherits read-only role, with INSERT/UPDATE/DELETE
dbrole_adminNOLOGINInherits pg_monitor + read-write role, can create objects and triggers
dbrole_offlineNOLOGINRestricted read-only role, only allowed to access offline instances
postgresUserSystem superuser, same as pg_dbsu
replicatorUserUsed for streaming replication and backup, inherits monitoring and read-only privileges
dbuser_dbaUserPrimary admin account, also synced to pgbouncer
dbuser_monitorUserMonitoring account, has pg_monitor privilege, records slow SQL by default

These definitions are in pg_default_roles. They can theoretically be customized, but if you replace names, you must synchronize updates in HBA/ACL/script references.

Example: Add an additional dbrole_etl for offline tasks:

pg_default_roles:
  - { name: dbrole_etl, login: false, roles: [dbrole_offline], comment: 'etl read-only role' }
  - { name: dbrole_admin, login: false, roles: [pg_monitor, dbrole_readwrite, dbrole_etl] }

Effect: All users inheriting dbrole_admin automatically have dbrole_etl privileges, can access offline instances and execute ETL.


Default Users and Credential Parameters

System user usernames/passwords are controlled by the following parameters:

ParameterDefault ValuePurpose
pg_dbsupostgresDatabase/system superuser
pg_dbsu_passwordEmpty stringdbsu password (disabled by default)
pg_replication_usernamereplicatorReplication username
pg_replication_passwordDBUser.ReplicatorReplication user password
pg_admin_usernamedbuser_dbaAdmin username
pg_admin_passwordDBUser.DBAAdmin password
pg_monitor_usernamedbuser_monitorMonitoring user
pg_monitor_passwordDBUser.MonitorMonitoring user password

If you modify these parameters, please synchronize updates to the corresponding user definitions in pg_default_roles to avoid role attribute inconsistencies.


Business Roles and Authorization (pg_users)

Business users are declared through pg_users (see User Configuration for detailed fields), where the roles field controls the granted business roles.

Example: Create one read-only and one read-write user:

pg_users:
  - { name: app_reader,  password: DBUser.Reader,  roles: [dbrole_readonly],  pgbouncer: true }
  - { name: app_writer,  password: DBUser.Writer,  roles: [dbrole_readwrite], pgbouncer: true }

By inheriting dbrole_* to control access privileges, no need to GRANT for each database separately. Combined with pg_hba_rules, you can distinguish access sources.

For finer-grained ACL, you can use standard GRANT/REVOKE in baseline SQL or subsequent playbooks. Pigsty won’t prevent you from granting additional privileges.


Default Privilege Templates (pg_default_privileges)

pg_default_privileges will set DEFAULT PRIVILEGE on postgres, dbuser_dba, dbrole_admin (after business admin SET ROLE). The default template is as follows:

pg_default_privileges:
  - GRANT USAGE      ON SCHEMAS   TO dbrole_readonly
  - GRANT SELECT     ON TABLES    TO dbrole_readonly
  - GRANT SELECT     ON SEQUENCES TO dbrole_readonly
  - GRANT EXECUTE    ON FUNCTIONS TO dbrole_readonly
  - GRANT USAGE      ON SCHEMAS   TO dbrole_offline
  - GRANT SELECT     ON TABLES    TO dbrole_offline
  - GRANT SELECT     ON SEQUENCES TO dbrole_offline
  - GRANT EXECUTE    ON FUNCTIONS TO dbrole_offline
  - GRANT INSERT     ON TABLES    TO dbrole_readwrite
  - GRANT UPDATE     ON TABLES    TO dbrole_readwrite
  - GRANT DELETE     ON TABLES    TO dbrole_readwrite
  - GRANT USAGE      ON SEQUENCES TO dbrole_readwrite
  - GRANT UPDATE     ON SEQUENCES TO dbrole_readwrite
  - GRANT TRUNCATE   ON TABLES    TO dbrole_admin
  - GRANT REFERENCES ON TABLES    TO dbrole_admin
  - GRANT TRIGGER    ON TABLES    TO dbrole_admin
  - GRANT CREATE     ON SCHEMAS   TO dbrole_admin

As long as objects are created by the above administrators, they will automatically carry the corresponding privileges without manual GRANT. If business needs a custom template, simply replace this array.

Additional notes:

  • pg_revoke_public defaults to true, meaning automatic revocation of PUBLIC’s CREATE privilege on databases and the public schema.
  • pg_default_schemas and pg_default_extensions control pre-created schemas/extensions in template1/postgres, typically used for monitoring objects (monitor schema, pg_stat_statements, etc.).

Common Configuration Scenarios

Provide Read-Only Account for Partners

pg_users:
  - name: partner_ro
    password: Partner.Read
    roles: [dbrole_readonly]
pg_hba_rules:
  - { user: partner_ro, db: analytics, addr: 203.0.113.0/24, auth: ssl }

Effect: Partner account only has default read-only privileges after login, and can only access the analytics database via TLS from the specified network segment.

Grant DDL Capability to Business Administrators

pg_users:
  - name: app_admin
    password: DBUser.AppAdmin
    roles: [dbrole_admin]

Business administrators can inherit the default DDL privilege template by SET ROLE dbrole_admin or logging in directly as app_admin.

Customize Default Privileges

pg_default_privileges:
  - GRANT INSERT,UPDATE,DELETE ON TABLES TO dbrole_admin
  - GRANT SELECT,UPDATE ON SEQUENCES TO dbrole_admin
  - GRANT SELECT ON TABLES TO reporting_group

After replacing the default template, all objects created by administrators will carry the new privilege definitions, avoiding per-object authorization.


Coordination with Other Components

  • HBA Rules: Use pg_hba_rules to bind roles with sources (e.g., only allow dbrole_offline to access offline instances).
  • Pgbouncer: Users with pgbouncer: true will be written to userlist.txt, and pool_mode/pool_connlimit can control connection pool-level quotas.
  • Grafana/Monitoring: dbuser_monitor’s privileges come from pg_default_roles. If you add a new monitoring user, remember to grant pg_monitor + access to the monitor schema.

Through these parameters, you can version the privilege system along with code, truly achieving “configuration as policy”.