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

Return to the regular view of this page.

Backup & Restore

Point-in-Time Recovery (PITR) Backup and Restore

Pigsty uses pgBackRest to manage PostgreSQL backups, arguably the most powerful open-source backup tool in the ecosystem. It supports incremental/parallel backup and restore, encryption, MinIO/S3, and many other features. Pigsty configures backup functionality by default for each PGSQL cluster.

SectionContent
MechanismBackup scripts, cron jobs, pgbackrest, repository and management
PolicyBackup strategy, disk planning, recovery window tradeoffs
RepositoryConfiguring backup repositories: local, MinIO, S3
AdminCommon backup management commands
RestoreRestore to a specific point in time using playbooks
ExampleSandbox example: performing restore operations manually

Quick Start

  1. Backup Policy: Schedule base backups using Crontab
  2. WAL Archiving: Continuously record write activity
  3. Restore & Recovery: Recover from backups and WAL archives
node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ]
./pgsql-pitr.yml -e '{"pg_pitr": { "time": "2025-07-13 10:00:00+00" }}'

1 - Backup Policy

Design backup policies according to your needs
  • When: Backup schedule
  • Where: Backup repository
  • How: Backup method

When to Backup

The first question is when to backup your database - this is a tradeoff between backup frequency and recovery time. Since you need to replay WAL logs from the last backup to the recovery target point, the more frequent the backups, the less WAL logs need to be replayed, and the faster the recovery.

Daily Full Backup

For production databases, it’s recommended to start with the simplest daily full backup strategy. This is also Pigsty’s default backup strategy, implemented via crontab.

node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ]
pgbackrest_method: local          # Choose backup repository method: `local`, `minio`, or other custom repository
pgbackrest_repo:                  # pgbackrest repository configuration: https://pgbackrest.org/configuration.html#section-repository
  local:                          # Default pgbackrest repository using local POSIX filesystem
    path: /pg/backup              # Local backup directory, defaults to `/pg/backup`
    retention_full_type: count    # Retain full backups by count
    retention_full: 2             # Keep 2, up to 3 full backups when using local filesystem repository

When used with the default local local filesystem backup repository, this provides a 24~48 hour recovery window.

pitr-scope

Assuming your database size is 100GB and writes 10GB of data per day, the backup size is as follows:

pitr-space

This will consume 2~3 times the database size in space, plus 2 days of WAL logs. Therefore, in practice, you may need to prepare at least 3~5 times the database size for backup disk to use the default backup strategy.

Full + Incremental Backup

You can optimize backup space usage by adjusting these parameters.

If using MinIO / S3 as a centralized backup repository, you can use storage space beyond local disk limitations. In this case, consider using full + incremental backup with a 2-week retention policy:

node_crontab:  # Full backup at 1 AM on Monday, incremental backups on weekdays
  - '00 01 * * 1 postgres /pg/bin/pg-backup full'
  - '00 01 * * 2,3,4,5,6,7 postgres /pg/bin/pg-backup'
pgbackrest_method: minio
pgbackrest_repo:                  # pgbackrest repository configuration: https://pgbackrest.org/configuration.html#section-repository
  minio:                          # Optional minio repository
    type: s3                      # minio is S3 compatible
    s3_endpoint: sss.pigsty       # minio endpoint domain, defaults to `sss.pigsty`
    s3_region: us-east-1          # minio region, defaults to us-east-1, meaningless for minio
    s3_bucket: pgsql              # minio bucket name, defaults to `pgsql`
    s3_key: pgbackrest            # minio user access key for pgbackrest
    s3_key_secret: S3User.Backup  # minio user secret for pgbackrest
    s3_uri_style: path            # minio uses path-style URIs instead of host-style
    path: /pgbackrest             # minio backup path, defaults to `/pgbackrest`
    storage_port: 9000            # minio port, defaults to 9000
    storage_ca_file: /etc/pki/ca.crt  # minio CA certificate path, defaults to `/etc/pki/ca.crt`
    block: y                      # Enable block-level incremental backup
    bundle: y                     # Bundle small files into a single file
    bundle_limit: 20MiB           # Bundle size limit, recommended 20MiB for object storage
    bundle_size: 128MiB           # Bundle target size, recommended 128MiB for object storage
    cipher_type: aes-256-cbc      # Enable AES encryption for remote backup repository
    cipher_pass: pgBackRest       # AES encryption password, defaults to 'pgBackRest'
    retention_full_type: time     # Retain full backups by time
    retention_full: 14            # Keep full backups from the last 14 days

When used with the built-in minio backup repository, this provides a guaranteed 1-week PITR recovery window.

pitr-scope2

Assuming your database size is 100GB and writes 10GB of data per day, the backup size is as follows:

pitr-space2


Backup Location

By default, Pigsty provides two default backup repository definitions: local and minio backup repositories.

  • local: Default option, uses local /pg/backup directory (symlink to pg_fs_backup: /data/backups)
  • minio: Uses SNSD single-node MinIO cluster (supported by Pigsty, but not enabled by default)
pgbackrest_method: local          # Choose backup repository method: `local`, `minio`, or other custom repository
pgbackrest_repo:                  # pgbackrest repository configuration: https://pgbackrest.org/configuration.html#section-repository
  local:                          # Default pgbackrest repository using local POSIX filesystem
    path: /pg/backup              # Local backup directory, defaults to `/pg/backup`
    retention_full_type: count    # Retain full backups by count
    retention_full: 2             # Keep 2, up to 3 full backups when using local filesystem repository
  minio:                          # Optional minio repository
    type: s3                      # minio is S3 compatible
    s3_endpoint: sss.pigsty       # minio endpoint domain, defaults to `sss.pigsty`
    s3_region: us-east-1          # minio region, defaults to us-east-1, meaningless for minio
    s3_bucket: pgsql              # minio bucket name, defaults to `pgsql`
    s3_key: pgbackrest            # minio user access key for pgbackrest
    s3_key_secret: S3User.Backup  # minio user secret for pgbackrest
    s3_uri_style: path            # minio uses path-style URIs instead of host-style
    path: /pgbackrest             # minio backup path, defaults to `/pgbackrest`
    storage_port: 9000            # minio port, defaults to 9000
    storage_ca_file: /etc/pki/ca.crt  # minio CA certificate path, defaults to `/etc/pki/ca.crt`
    block: y                      # Enable block-level incremental backup
    bundle: y                     # Bundle small files into a single file
    bundle_limit: 20MiB           # Bundle size limit, recommended 20MiB for object storage
    bundle_size: 128MiB           # Bundle target size, recommended 128MiB for object storage
    cipher_type: aes-256-cbc      # Enable AES encryption for remote backup repository
    cipher_pass: pgBackRest       # AES encryption password, defaults to 'pgBackRest'
    retention_full_type: time     # Retain full backups by time
    retention_full: 14            # Keep full backups from the last 14 days

2 - Backup Mechanism

Backup scripts, cron jobs, backup repository and infrastructure

Backups can be invoked via built-in scripts, scheduled using node crontab, managed by pgbackrest, and stored in backup repositories, which can be local disk filesystems or MinIO / S3, supporting different retention policies.


Scripts

You can create backups using the pg_dbsu user (defaults to postgres) to execute pgbackrest commands:

pgbackrest --stanza=pg-meta --type=full backup   # Create full backup for cluster pg-meta
$ pgbackrest --stanza=pg-meta --type=full backup
2025-07-15 01:36:57.007 P00   INFO: backup command begin 2.54.2: --annotation=pg_cluster=pg-meta ...
2025-07-15 01:36:57.030 P00   INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
2025-07-15 01:36:57.105 P00   INFO: backup start archive = 000000010000000000000006, lsn = 0/6000028
2025-07-15 01:36:58.540 P00   INFO: new backup label = 20250715-013657F
2025-07-15 01:36:58.588 P00   INFO: full backup size = 44.5MB, file total = 1437
2025-07-15 01:36:58.589 P00   INFO: backup command end: completed successfully (1584ms)
$ pgbackrest --stanza=pg-meta --type=diff backup
2025-07-15 01:37:24.952 P00   INFO: backup command begin 2.54.2: ...
2025-07-15 01:37:24.985 P00   INFO: last backup label = 20250715-013657F, version = 2.54.2
2025-07-15 01:37:26.337 P00   INFO: new backup label = 20250715-013657F_20250715-013724D
2025-07-15 01:37:26.381 P00   INFO: diff backup size = 424.3KB, file total = 1437
2025-07-15 01:37:26.381 P00   INFO: backup command end: completed successfully (1431ms)
$ pgbackrest --stanza=pg-meta --type=incr backup
2025-07-15 01:37:30.305 P00   INFO: backup command begin 2.54.2: ...
2025-07-15 01:37:30.337 P00   INFO: last backup label = 20250715-013657F_20250715-013724D, version = 2.54.2
2025-07-15 01:37:31.356 P00   INFO: new backup label = 20250715-013657F_20250715-013730I
2025-07-15 01:37:31.403 P00   INFO: incr backup size = 8.3KB, file total = 1437
2025-07-15 01:37:31.403 P00   INFO: backup command end: completed successfully (1099ms)
$ pgbackrest --stanza=pg-meta info
stanza: pg-meta
    status: ok
    cipher: aes-256-cbc

    db (current)
        wal archive min/max (17): 000000010000000000000001/00000001000000000000000A

        full backup: 20250715-013657F
            timestamp start/stop: 2025-07-15 01:36:57+00 / 2025-07-15 01:36:58+00
            wal start/stop: 000000010000000000000006 / 000000010000000000000006
            database size: 44.5MB, database backup size: 44.5MB
            repo1: backup size: 8.7MB

        diff backup: 20250715-013657F_20250715-013724D
            timestamp start/stop: 2025-07-15 01:37:24+00 / 2025-07-15 01:37:26+00
            database size: 44.5MB, database backup size: 424.3KB
            repo1: backup size: 94KB
            backup reference total: 1 full

        incr backup: 20250715-013657F_20250715-013730I
            timestamp start/stop: 2025-07-15 01:37:30+00 / 2025-07-15 01:37:31+00
            database size: 44.5MB, database backup size: 8.3KB
            repo1: backup size: 504B
            backup reference total: 1 full, 1 diff

Here the stanza is the database cluster name: pg_cluster, which is pg-meta in the default configuration.

Pigsty provides the pb alias and pg-backup wrapper script, which automatically fills in the current cluster name as the stanza:

function pb() {
    local stanza=$(grep -o '\[[^][]*]' /etc/pgbackrest/pgbackrest.conf | head -n1 | sed 's/.*\[\([^]]*\)].*/\1/')
    pgbackrest --stanza=$stanza $@
}
pb ...    # pgbackrest --stanza=pg-meta ...
pb info   # pgbackrest --stanza=pg-meta info
pb backup # pgbackrest --stanza=pg-meta backup
pg-backup full   # Perform full backup         = pgbackrest --stanza=pg-meta --type=full backup
pg-backup incr   # Perform incremental backup  = pgbackrest --stanza=pg-meta --type=incr backup
pg-backup diff   # Perform differential backup = pgbackrest --stanza=pg-meta --type=diff backup

Scheduled Backups

Pigsty uses Linux crontab to schedule backup tasks. You can use it to define backup policies.

For example, most single-node configuration templates have the following node_crontab for backups:

node_crontab: [ '00 01 * * * postgres /pg/bin/pg-backup full' ]

You can design more complex backup strategies using crontab and the pg-backup script, for example:

node_crontab:  # Full backup at 1 AM on Monday, incremental backups on weekdays
  - '00 01 * * 1 postgres /pg/bin/pg-backup full'
  - '00 01 * * 2,3,4,5,6,7 postgres /pg/bin/pg-backup'

To apply crontab changes, use node.yml to update crontab on all nodes:

./node.yml -t node_crontab -l pg-meta    # Apply crontab changes to pg-meta group

pgbackrest

Here are the configuration details for pgbackrest in Pigsty:

  • pgbackrest backup tool is enabled and configured by default (pgbackrest_enabled)
  • Installed in the pg_install task of the pgsql.yml playbook, defined in pg_packages
  • Configured in the pg_backup task of the pgsql.yml playbook, see Parameters: PG_BACKUP
  • Backup repository initialized in the pgbackrest_init task, which will fail if the repository already exists (error can be ignored)
  • Initial backup created in the pgbackrest_backup task, controlled by pgbackrest_init_backup

File Hierarchy

  • bin: /usr/bin/pgbackrest, from PGDG’s pgbackrest package, in group alias pgsql-common.
  • conf: /etc/pgbackrest, main configuration file is /etc/pgbackrest/pgbackrest.conf.
  • logs: /pg/log/pgbackrest/*, controlled by pgbackrest_log_dir
  • tmp: /pg/spool used as temporary spool directory for pgbackrest
  • data: /pg/backup used to store data (when using the default local filesystem backup repository)

Additionally, during PITR recovery, Pigsty creates a temporary /pg/conf/pitr.conf pgbackrest configuration file, and writes postgres recovery logs to the /pg/tmp/recovery.log file.

Monitoring

There is a pgbackrest_exporter service running on pgbackrest_exporter_port (9854) port for exporting pgbackrest metrics. You can customize it via pgbackrest_exporter_options, or set pgbackrest_exporter_enabled to false to disable it.

Initial Backup

When creating a postgres cluster, Pigsty automatically creates an initial backup. Since the new cluster is almost empty, this is a very small backup. It leaves a /etc/pgbackrest/initial.done marker file to avoid recreating the initial backup. If you don’t want an initial backup, set pgbackrest_init_backup to false.


Management

Enable Backup

If pgbackrest_enabled is set to true when the database cluster is created, backups will be automatically enabled.

If this value was false at creation time, you can enable the pgbackrest component with the following command:

./pgsql.yml -t pg_backup    # Run pgbackrest subtask

Remove Backup

When removing the primary instance (pg_role = primary), Pigsty will delete the pgbackrest backup stanza.

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # Keep backups
./pgsql-rm.yml -t pg_backup            # Remove backups only

Use the pg_backup subtask to remove backups only, and the pg_rm_backup parameter (set to false) to preserve backups.

If your backup repository is locked (e.g., S3 / MinIO has locking options), this operation will fail.

List Backups

This command will list all backups in the pgbackrest repository (shared across all clusters)

pgbackrest info

Manual Backup

Pigsty provides a built-in script /pg/bin/pg-backup that wraps the pgbackrest backup command.

pg-backup        # Perform incremental backup
pg-backup full   # Perform full backup
pg-backup incr   # Perform incremental backup
pg-backup diff   # Perform differential backup

Base Backup

Pigsty provides an alternative backup script /pg/bin/pg-basebackup that does not depend on pgbackrest and directly provides a physical copy of the database cluster. The default backup directory is /pg/backup.

NAME
  pg-basebackup  -- make base backup from PostgreSQL instance

SYNOPSIS
  pg-basebackup -sdfeukr
  pg-basebackup --src postgres:/// --dst . --file backup.tar.lz4

DESCRIPTION
-s, --src, --url     Backup source URL, optional, defaults to "postgres:///", password should be provided in url, ENV, or .pgpass if required
-d, --dst, --dir     Location to store backup file, defaults to "/pg/backup"
-f, --file           Override default backup filename, "backup_${tag}_${date}.tar.lz4"
-r, --remove         Remove .lz4 files older than n minutes, defaults to 1200 (20 hours)
-t, --tag            Backup file tag, uses target cluster name or local IP address if not set, also used for default filename
-k, --key            Encryption key when --encrypt is specified, defaults to ${tag}
-u, --upload         Upload backup file to cloud storage (needs to be implemented by yourself)
-e, --encryption     Use OpenSSL RC4 encryption, uses tag as key if not specified
-h, --help           Print this help information
postgres@pg-meta-1:~$ pg-basebackup
[2025-07-13 06:16:05][INFO] ================================================================
[2025-07-13 06:16:05][INFO] [INIT] pg-basebackup begin, checking parameters
[2025-07-13 06:16:05][DEBUG] [INIT] filename  (-f)    :   backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][DEBUG] [INIT] src       (-s)    :   postgres:///
[2025-07-13 06:16:05][DEBUG] [INIT] dst       (-d)    :   /pg/backup
[2025-07-13 06:16:05][INFO] [LOCK] lock acquired success on /tmp/backup.lock, pid=107417
[2025-07-13 06:16:05][INFO] [BKUP] backup begin, from postgres:/// to /pg/backup/backup_pg-meta_20250713.tar.lz4
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/7000028 on timeline 1
pg_basebackup: write-ahead log end point: 0/7000FD8
pg_basebackup: syncing data to disk ...
pg_basebackup: base backup completed
[2025-07-13 06:16:06][INFO] [BKUP] backup complete!
[2025-07-13 06:16:06][INFO] [DONE] backup procedure complete!
[2025-07-13 06:16:06][INFO] ================================================================

The backup uses lz4 compression. You can decompress and extract the tarball with the following command:

mkdir -p /tmp/data   # Extract backup to this directory
cat /pg/backup/backup_pg-meta_20250713.tar.lz4 | unlz4 -d -c | tar -xC /tmp/data

Logical Backup

You can also perform logical backups using the pg_dump command.

Logical backups cannot be used for PITR (Point-in-Time Recovery), but are very useful for migrating data between different major versions or implementing flexible data export logic.

Bootstrap from Repository

Suppose you have an existing cluster pg-meta and want to clone it as pg-meta2:

You need to create a new pg-meta2 cluster branch and then run pitr on it.

3 - Backup Repository

PostgreSQL backup storage repository configuration

You can configure the backup storage location by specifying the pgbackrest_repo parameter. You can define multiple repositories here, and Pigsty will choose which one to use based on the value of pgbackrest_method.

Default Repositories

By default, Pigsty provides two default backup repository definitions: local and minio backup repositories.

  • local: Default option, uses local /pg/backup directory (symlink to pg_fs_backup: /data/backups)
  • minio: Uses SNSD single-node MinIO cluster (supported by Pigsty, but not enabled by default)
pgbackrest_method: local          # Choose backup repository method: `local`, `minio`, or other custom repository
pgbackrest_repo:                  # pgbackrest repository configuration: https://pgbackrest.org/configuration.html#section-repository
  local:                          # Default pgbackrest repository using local POSIX filesystem
    path: /pg/backup              # Local backup directory, defaults to `/pg/backup`
    retention_full_type: count    # Retain full backups by count
    retention_full: 2             # Keep 2, up to 3 full backups when using local filesystem repository
  minio:                          # Optional minio repository
    type: s3                      # minio is S3 compatible
    s3_endpoint: sss.pigsty       # minio endpoint domain, defaults to `sss.pigsty`
    s3_region: us-east-1          # minio region, defaults to us-east-1, meaningless for minio
    s3_bucket: pgsql              # minio bucket name, defaults to `pgsql`
    s3_key: pgbackrest            # minio user access key for pgbackrest
    s3_key_secret: S3User.Backup  # minio user secret for pgbackrest
    s3_uri_style: path            # minio uses path-style URIs instead of host-style
    path: /pgbackrest             # minio backup path, defaults to `/pgbackrest`
    storage_port: 9000            # minio port, defaults to 9000
    storage_ca_file: /etc/pki/ca.crt  # minio CA certificate path, defaults to `/etc/pki/ca.crt`
    block: y                      # Enable block-level incremental backup
    bundle: y                     # Bundle small files into a single file
    bundle_limit: 20MiB           # Bundle size limit, recommended 20MiB for object storage
    bundle_size: 128MiB           # Bundle target size, recommended 128MiB for object storage
    cipher_type: aes-256-cbc      # Enable AES encryption for remote backup repository
    cipher_pass: pgBackRest       # AES encryption password, defaults to 'pgBackRest'
    retention_full_type: time     # Retain full backups by time
    retention_full: 14            # Keep full backups from the last 14 days

Repository Retention Policy

If you backup daily but don’t delete old backups, the backup repository will grow indefinitely and exhaust disk space. You need to define a retention policy to keep only a limited number of backups.

The default backup policy is defined in the pgbackrest_repo parameter and can be adjusted as needed.

  • local: Keep the latest 2 full backups, allowing up to 3 during backup
  • minio: Keep all full backups from the last 14 days

Space Planning

Object storage provides almost unlimited storage capacity, so there’s no need to worry about disk space. You can use a hybrid full + differential backup strategy to optimize space usage.

For local disk backup repositories, Pigsty recommends using a policy that keeps the latest 2 full backups, meaning the disk will retain the two most recent full backups (there may be a third copy while running a new backup).

This guarantees at least a 24-hour recovery window. See Backup Policy for details.


Other Repository Options

You can also use other services as backup repositories, refer to the pgbackrest documentation for details:


Repository Versioning

You can even specify repo target time to get snapshots of object storage.

You can enable MinIO versioning by adding the versioning flag in minio_buckets:

minio_buckets:
  - { name: pgsql ,versioning: true }
  - { name: meta  ,versioning: true }
  - { name: data }

Repository Locking

Some object storage services (S3, MinIO, etc.) support locking functionality, which can prevent backups from being deleted, even by the DBA.

You can enable MinIO locking by adding the lock flag in minio_buckets:

minio_buckets:
  - { name: pgsql , lock: true }
  - { name: meta ,versioning: true  }
  - { name: data }

Using Object Storage

Object storage services provide almost unlimited storage capacity and provide remote disaster recovery capability for your system. If you don’t have an object storage service, Pigsty has built-in MinIO support.

MinIO

You can enable the MinIO backup repository by uncommenting the following settings. Note that pgbackrest only supports HTTPS / domain names, so you must run MinIO with domain names and HTTPS endpoints.

all:
  vars:
    pgbackrest_method: minio      # Use minio as default backup repository
  children:                       # Define a single-node minio SNSD cluster
    minio: { hosts: { 10.10.10.10: { minio_seq: 1 }} ,vars: { minio_cluster: minio }}

S3

If you only have one node, a meaningful backup strategy would be to use cloud provider object storage services like AWS S3, Alibaba Cloud OSS, or Google Cloud, etc. To do this, you can define a new repository:

pgbackrest_method: s3             # Use 'pgbackrest_repo.s3' as backup repository
pgbackrest_repo:                  # pgbackrest repository configuration: https://pgbackrest.org/configuration.html#section-repository

  s3:                             # Alibaba Cloud OSS (S3 compatible) object storage service
    type: s3                      # oss is S3 compatible
    s3_endpoint: oss-cn-beijing-internal.aliyuncs.com
    s3_region: oss-cn-beijing
    s3_bucket: <your_bucket_name>
    s3_key: <your_access_key>
    s3_key_secret: <your_secret_key>
    s3_uri_style: host
    path: /pgbackrest
    bundle: y                     # Bundle small files into a single file
    bundle_limit: 20MiB           # Bundle size limit, recommended 20MiB for object storage
    bundle_size: 128MiB           # Bundle target size, recommended 128MiB for object storage
    cipher_type: aes-256-cbc      # Enable AES encryption for remote backup repository
    cipher_pass: pgBackRest       # AES encryption password, defaults to 'pgBackRest'
    retention_full_type: time     # Retain full backups by time
    retention_full: 14            # Keep full backups from the last 14 days

  local:                          # Default pgbackrest repository using local POSIX filesystem
    path: /pg/backup              # Local backup directory, defaults to `/pg/backup`
    retention_full_type: count    # Retain full backups by count
    retention_full: 2             # Keep 2, up to 3 full backups when using local filesystem repository

Managing Backups

Enable Backup

If pgbackrest_enabled is set to true when the database cluster is created, backups will be automatically enabled.

If this value was false at creation time, you can enable the pgbackrest component with the following command:

./pgsql.yml -t pg_backup    # Run pgbackrest subtask

Remove Backup

When removing the primary instance (pg_role = primary), Pigsty will delete the pgbackrest backup stanza.

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # Keep backups
./pgsql-rm.yml -t pg_backup            # Remove backups only

Use the pg_backup subtask to remove backups only, and the pg_rm_backup parameter (set to false) to preserve backups.

If your backup repository is locked (e.g., S3 / MinIO has locking options), this operation will fail.

List Backups

This command will list all backups in the pgbackrest repository (shared across all clusters)

pgbackrest info

Manual Backup

Pigsty provides a built-in script /pg/bin/pg-backup that wraps the pgbackrest backup command.

pg-backup        # Perform incremental backup
pg-backup full   # Perform full backup
pg-backup incr   # Perform incremental backup
pg-backup diff   # Perform differential backup

Base Backup

Pigsty provides an alternative backup script /pg/bin/pg-basebackup that does not depend on pgbackrest and directly provides a physical copy of the database cluster. The default backup directory is /pg/backup.

NAME
  pg-basebackup  -- make base backup from PostgreSQL instance

SYNOPSIS
  pg-basebackup -sdfeukr
  pg-basebackup --src postgres:/// --dst . --file backup.tar.lz4

DESCRIPTION
-s, --src, --url     Backup source URL, optional, defaults to "postgres:///", password should be provided in url, ENV, or .pgpass if required
-d, --dst, --dir     Location to store backup file, defaults to "/pg/backup"
-f, --file           Override default backup filename, "backup_${tag}_${date}.tar.lz4"
-r, --remove         Remove .lz4 files older than n minutes, defaults to 1200 (20 hours)
-t, --tag            Backup file tag, uses target cluster name or local IP address if not set, also used for default filename
-k, --key            Encryption key when --encrypt is specified, defaults to ${tag}
-u, --upload         Upload backup file to cloud storage (needs to be implemented by yourself)
-e, --encryption     Use OpenSSL RC4 encryption, uses tag as key if not specified
-h, --help           Print this help information
postgres@pg-meta-1:~$ pg-basebackup
[2025-07-13 06:16:05][INFO] ================================================================
[2025-07-13 06:16:05][INFO] [INIT] pg-basebackup begin, checking parameters
[2025-07-13 06:16:05][DEBUG] [INIT] filename  (-f)    :   backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][DEBUG] [INIT] src       (-s)    :   postgres:///
[2025-07-13 06:16:05][DEBUG] [INIT] dst       (-d)    :   /pg/backup
[2025-07-13 06:16:05][INFO] [LOCK] lock acquired success on /tmp/backup.lock, pid=107417
[2025-07-13 06:16:05][INFO] [BKUP] backup begin, from postgres:/// to /pg/backup/backup_pg-meta_20250713.tar.lz4
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/7000028 on timeline 1
pg_basebackup: write-ahead log end point: 0/7000FD8
pg_basebackup: syncing data to disk ...
pg_basebackup: base backup completed
[2025-07-13 06:16:06][INFO] [BKUP] backup complete!
[2025-07-13 06:16:06][INFO] [DONE] backup procedure complete!
[2025-07-13 06:16:06][INFO] ================================================================

The backup uses lz4 compression. You can decompress and extract the tarball with the following command:

mkdir -p /tmp/data   # Extract backup to this directory
cat /pg/backup/backup_pg-meta_20250713.tar.lz4 | unlz4 -d -c | tar -xC /tmp/data

Logical Backup

You can also perform logical backups using the pg_dump command.

Logical backups cannot be used for PITR (Point-in-Time Recovery), but are very useful for migrating data between different major versions or implementing flexible data export logic.

Bootstrap from Repository

Suppose you have an existing cluster pg-meta and want to clone it as pg-meta2:

You need to create a new pg-meta2 cluster branch and then run pitr on it.

4 - Admin Commands

Managing backup repositories and backups

Enable Backup

If pgbackrest_enabled is set to true when the database cluster is created, backups will be automatically enabled.

If this value was false at creation time, you can enable the pgbackrest component with the following command:

./pgsql.yml -t pg_backup    # Run pgbackrest subtask

Remove Backup

When removing the primary instance (pg_role = primary), Pigsty will delete the pgbackrest backup stanza.

./pgsql-rm.yml
./pgsql-rm.yml -e pg_rm_backup=false   # Keep backups
./pgsql-rm.yml -t pg_backup            # Remove backups only

Use the pg_backup subtask to remove backups only, and the pg_rm_backup parameter (set to false) to preserve backups.

If your backup repository is locked (e.g., S3 / MinIO has locking options), this operation will fail.


List Backups

This command will list all backups in the pgbackrest repository (shared across all clusters)

pgbackrest info

Manual Backup

Pigsty provides a built-in script /pg/bin/pg-backup that wraps the pgbackrest backup command.

pg-backup        # Perform incremental backup
pg-backup full   # Perform full backup
pg-backup incr   # Perform incremental backup
pg-backup diff   # Perform differential backup

Base Backup

Pigsty provides an alternative backup script /pg/bin/pg-basebackup that does not depend on pgbackrest and directly provides a physical copy of the database cluster. The default backup directory is /pg/backup.

NAME
  pg-basebackup  -- make base backup from PostgreSQL instance

SYNOPSIS
  pg-basebackup -sdfeukr
  pg-basebackup --src postgres:/// --dst . --file backup.tar.lz4

DESCRIPTION
-s, --src, --url     Backup source URL, optional, defaults to "postgres:///", password should be provided in url, ENV, or .pgpass if required
-d, --dst, --dir     Location to store backup file, defaults to "/pg/backup"
-f, --file           Override default backup filename, "backup_${tag}_${date}.tar.lz4"
-r, --remove         Remove .lz4 files older than n minutes, defaults to 1200 (20 hours)
-t, --tag            Backup file tag, uses target cluster name or local IP address if not set, also used for default filename
-k, --key            Encryption key when --encrypt is specified, defaults to ${tag}
-u, --upload         Upload backup file to cloud storage (needs to be implemented by yourself)
-e, --encryption     Use OpenSSL RC4 encryption, uses tag as key if not specified
-h, --help           Print this help information
postgres@pg-meta-1:~$ pg-basebackup
[2025-07-13 06:16:05][INFO] ================================================================
[2025-07-13 06:16:05][INFO] [INIT] pg-basebackup begin, checking parameters
[2025-07-13 06:16:05][DEBUG] [INIT] filename  (-f)    :   backup_pg-meta_20250713.tar.lz4
[2025-07-13 06:16:05][DEBUG] [INIT] src       (-s)    :   postgres:///
[2025-07-13 06:16:05][DEBUG] [INIT] dst       (-d)    :   /pg/backup
[2025-07-13 06:16:05][INFO] [LOCK] lock acquired success on /tmp/backup.lock, pid=107417
[2025-07-13 06:16:05][INFO] [BKUP] backup begin, from postgres:/// to /pg/backup/backup_pg-meta_20250713.tar.lz4
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/7000028 on timeline 1
pg_basebackup: write-ahead log end point: 0/7000FD8
pg_basebackup: syncing data to disk ...
pg_basebackup: base backup completed
[2025-07-13 06:16:06][INFO] [BKUP] backup complete!
[2025-07-13 06:16:06][INFO] [DONE] backup procedure complete!
[2025-07-13 06:16:06][INFO] ================================================================

The backup uses lz4 compression. You can decompress and extract the tarball with the following command:

mkdir -p /tmp/data   # Extract backup to this directory
cat /pg/backup/backup_pg-meta_20250713.tar.lz4 | unlz4 -d -c | tar -xC /tmp/data

Logical Backup

You can also perform logical backups using the pg_dump command.

Logical backups cannot be used for PITR (Point-in-Time Recovery), but are very useful for migrating data between different major versions or implementing flexible data export logic.


Bootstrap from Repository

Suppose you have an existing cluster pg-meta and want to clone it as pg-meta2:

You need to create a new pg-meta2 cluster branch and then run pitr on it.

5 - Restore Operations

Restore PostgreSQL from backups

You can perform Point-in-Time Recovery (PITR) in Pigsty using pre-configured pgbackrest.

  • Manual Approach: Manually execute PITR using pg-pitr prompt scripts, more flexible but more complex.
  • Playbook Approach: Automatically execute PITR using pgsql-pitr.yml playbook, highly automated but less flexible and error-prone.

If you are very familiar with the configuration, you can use the fully automated playbook, otherwise manual step-by-step operation is recommended.


Quick Start

If you want to roll back the pg-meta cluster to a previous point in time, add the pg_pitr parameter:

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta2
    pg_pitr: { time: '2025-07-13 10:00:00+00' }  # Recover from latest backup

Then run the pgsql-pitr.yml playbook, which will roll back the pg-meta cluster to the specified point in time.

./pgsql-pitr.yml -l pg-meta

Post-Recovery

The recovered cluster will have archive_mode disabled to prevent accidental WAL writes. If the recovered database state is normal, you can enable archive_mode and perform a full backup.

psql -c 'ALTER SYSTEM RESET archive_mode; SELECT pg_reload_conf();'
pg-backup full    # Perform new full backup

Recovery Target

You can specify different types of recovery targets in pg_pitr, but they are mutually exclusive:

  • time: To which point in time to recover?
  • name: Recover to a named restore point (created by pg_create_restore_point)
  • xid: Recover to a specific transaction ID (TXID/XID)
  • lsn: Recover to a specific LSN (Log Sequence Number) point

If any of the above parameters are specified, the recovery type will be set accordingly, otherwise it will be set to latest (end of WAL archive stream). The special immediate type can be used to instruct pgbackrest to minimize recovery time by stopping at the first consistent point.

Target Types

pg_pitr: { }  # Recover to latest state (end of WAL archive stream)
pg_pitr: { time: "2025-07-13 10:00:00+00" }
pg_pitr: { lsn: "0/4001C80" }
pg_pitr: { xid: "250000" }
pg_pitr: { name: "some_restore_point" }
pg_pitr: { type: "immediate" }

Recover by Time

The most commonly used target is a point in time; you can specify the time point to recover to:

./pgsql-pitr.yml -e '{"pg_pitr": { "time": "2025-07-13 10:00:00+00" }}'

Time should be in valid PostgreSQL TIMESTAMP format, YYYY-MM-DD HH:MM:SS+TZ is recommended.

Recover by Name

You can create named restore points using pg_create_restore_point:

SELECT pg_create_restore_point('shit_incoming');

Then use that named restore point in PITR:

./pgsql-pitr.yml -e '{"pg_pitr": { "name": "shit_incoming" }}'

Recover by XID

If you have a transaction that accidentally deleted some data, the best way to recover is to restore the database to the state before that transaction.

./pgsql-pitr.yml -e '{"pg_pitr": { "xid": "250000", exclusive: true }}'

You can find the exact transaction ID from monitoring dashboards or from the TXID field in CSVLOG.

Recover by LSN

PostgreSQL uses LSN (Log Sequence Number) to identify the location of WAL records. You can find it in many places, such as the PG LSN panel in Pigsty dashboards.

./pgsql-pitr.yml -e '{"pg_pitr": { "lsn": "0/4001C80", timeline: "1" }}'

To recover to an exact position in the WAL stream, you can also specify the timeline parameter (defaults to latest)


Recovery Source

  • cluster: From which cluster to recover? Defaults to current pg_cluster, you can use any other cluster in the same pgbackrest repository
  • repo: Override backup repository, uses same format as pgbackrest_repo
  • set: Defaults to latest backup set, but you can specify a specific pgbackrest backup by label

Pigsty will recover from the pgbackrest backup repository. If you use a centralized backup repository (like MinIO/S3), you can specify another “stanza” (another cluster’s backup directory) as the recovery source.

pg-meta2:
  hosts: { 10.10.10.11: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta2
    pg_pitr: { cluster: pg-meta }  # Recover from pg-meta cluster backup

The above configuration will mark the PITR process to use the pg-meta stanza. You can also pass the pg_pitr parameter via CLI arguments:

./pgsql-pitr.yml -l pg-meta2 -e '{"pg_pitr": { "cluster": "pg-meta" }}'

You can also use these targets when PITR from another cluster:

./pgsql-pitr.yml -l pg-meta2 -e '{"pg_pitr": { "cluster": "pg-meta", "time": "2025-07-14 08:00:00+00" }}'

Step-by-Step Execution

This approach is semi-automatic, you will participate in the PITR process to make critical decisions.

For example, this configuration will restore the pg-meta cluster itself to the specified point in time:

pg-meta:
  hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
  vars:
    pg_cluster: pg-meta2
    pg_pitr: { time: '2025-07-13 10:00:00+00' }  # Recover from latest backup

Let’s execute step by step:

./pgsql-pitr.yml -l pg-meta -t down     # Pause patroni high availability
./pgsql-pitr.yml -l pg-meta -t pitr     # Run pitr process
./pgsql-pitr.yml -l pg-meta -t up       # Generate pgbackrest config and recovery script
# down                 : # Stop high availability and shutdown patroni and postgres
#   - pause            : # Pause patroni auto-failover
#   - stop             : # Stop patroni and postgres services
#     - stop_patroni   : # Stop patroni service
#     - stop_postgres  : # Stop postgres service
# pitr                 : # Perform PITR process
#   - config           : # Generate pgbackrest config and recovery script
#   - restore          : # Run pgbackrest restore command
#   - recovery         : # Start postgres and complete recovery
#   - verify           : # Verify recovered cluster control data
# up:                  : # Start postgres / patroni and restore high availability
#   - etcd             : # Clean etcd metadata before starting
#   - start            : # Start patroni and postgres services
#     - start_postgres : # Start postgres service
#     - start_patroni  : # Start patroni service
#   - resume           : # Resume patroni auto-failover

PITR Parameter Definition

The pg_pitr parameter has more options available:

pg_pitr:                           # Define PITR task
    cluster: "some_pg_cls_name"    # Source cluster name
    type: latest                   # Recovery target type: time, xid, name, lsn, immediate, latest
    time: "2025-01-01 10:00:00+00" # Recovery target: time, mutually exclusive with xid, name, lsn
    name: "some_restore_point"     # Recovery target: named restore point, mutually exclusive with time, xid, lsn
    xid:  "100000"                 # Recovery target: transaction ID, mutually exclusive with time, name, lsn
    lsn:  "0/3000000"              # Recovery target: log sequence number, mutually exclusive with time, name, xid
    timeline: latest               # Target timeline, can be integer, defaults to latest
    exclusive: false               # Whether to exclude target point, defaults to false
    action: pause                  # Post-recovery action: pause, promote, shutdown
    archive: false                 # Whether to keep archive settings? Defaults to false
    db_exclude: [ template0, template1 ]
    db_include: []
    link_map:
      pg_wal: '/data/wal'
      pg_xact: '/data/pg_xact'
    process: 4                     # Number of parallel recovery processes
    repo: {}                       # Recovery source repository
    data: /pg/data                 # Data recovery location
    port: 5432                     # Listening port for recovered instance

6 - Clone PG Cluster

How to use PITR to create a new PostgreSQL cluster and restore to a specified point in time?

Quick Start

  • Create an online replica of an existing cluster using Standby Cluster
  • Create a point-in-time snapshot of an existing cluster using PITR
  • Perform post-PITR cleanup to ensure the new cluster’s backup process works properly

You can use the PG PITR mechanism to clone an entire database cluster.

Reset a Cluster’s State

You can also consider creating a brand new empty cluster, then use PITR to reset it to a specific state of the pg-meta cluster.

Using this technique, you can clone any point-in-time (within backup retention period) state of the existing cluster pg-meta to a new cluster.

Using the Pigsty 4-node sandbox environment as an example, use the following command to reset the pg-test cluster to the latest state of the pg-meta cluster:

./pgsql-pitr.yml -l pg-test -e '{"pg_pitr": { "cluster": "pg-meta" }}'

Post-PITR Cleanup

When you restore a cluster using PITR, the new cluster’s PITR functionality is disabled. This is because if it also tries to generate backups and archive WAL, it could dirty the backup repository of the previous cluster.

Therefore, after confirming that the state of this PITR-restored new cluster meets expectations, you need to perform the following cleanup:

  • Upgrade the backup repository Stanza to accept new backups from different clusters (only when restoring from another cluster)
  • Enable archive_mode to allow the new cluster to archive WAL logs (requires cluster restart)
  • Perform a new full backup to ensure the new cluster’s data is included (optional, can also wait for crontab scheduled execution)
pb stanza-upgrade
psql -c 'ALTER SYSTEM RESET archive_mode;'
pg-backup full

Through these operations, your new cluster will have its own backup history starting from the first full backup. If you skip these steps, the new cluster’s backups will not work, and WAL archiving will not take effect, meaning you cannot perform any backup or PITR operations on the new cluster.

Consequences of Not Cleaning Up

Suppose you performed PITR recovery on the pg-test cluster using data from another cluster pg-meta, but did not perform cleanup.

Then at the next routine backup, you will see the following error:

postgres@pg-test-1:~$ pb backup
2025-12-27 10:20:29.336 P00   INFO: backup command begin...
2025-12-27 10:20:29.357 P00  ERROR: [051]: PostgreSQL version 18, system-id 7588470953413201282 do not match stanza version 18, system-id 7588470974940466058
                                    HINT: is this the correct stanza?

Clone a New Cluster

For example, suppose you have a cluster pg-meta, and now you want to clone a new cluster pg-meta2 from pg-meta.

You can consider using the Standby Cluster method to create a new cluster pg-meta2.

pgBackrest supports incremental backup/restore, so if you have already pulled pg-meta’s data through physical replication, the incremental PITR restore is usually very fast.

pb stop --force
pb stanza-delete --force
pb start
pb stanza-create

If you want to reset the pg-test cluster to the state of pg-meta cluster at 15:30 on December 26, 2025, you can use the following command:

./pgsql-pitr.yml -l pg-test -e '{"pg_pitr": { "cluster": "pg-meta", "time": "2025-12-27 17:50:00+08" ,archive: true }}'

Using this technique, you can not only clone the latest state of the pg-meta cluster, but also clone to any point in time.

7 - Clone Database

How to clone an existing database within a PostgreSQL cluster using instant XFS cloning

Clone Database

You can copy a PostgreSQL database through the template mechanism, but no active connections to the template database are allowed during this period.

If you want to clone the postgres database, you must execute the following two statements at the same time. Ensure all connections to the postgres database are cleaned up before executing Clone:

SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'postgres';
CREATE DATABASE pgcopy TEMPLATE postgres STRATEGY FILE_COPY;

Instant Clone

If you are using PostgreSQL 18 or higher, Pigsty sets file_copy_method by default. This parameter allows you to clone a database in O(1) (~200ms) time complexity without copying data files.

However, you must explicitly use the FILE_COPY strategy to create the database. Since the STRATEGY parameter of CREATE DATABASE was introduced in PostgreSQL 15, the default value has been WAL_LOG. You need to explicitly specify FILE_COPY for instant cloning.

SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'meta';
CREATE DATABASE pgcopy TEMPLATE meta STRATEGY FILE_COPY;

For example, cloning a 30 GB database: normal clone (WAL_LOG) takes 18 seconds, while instant clone (FILE_COPY) only needs constant time of 200 milliseconds.

However, you still need to ensure no active connections to the template database during cloning, but this time can be very short, making it practical for production environments.

If you need a new database copy for testing or development, instant cloning is an excellent choice. It doesn’t introduce additional storage overhead because it uses the file system’s CoW (Copy on Write) mechanism.

Since Pigsty v4.0, you can use strategy: FILE_COPY in the pg_databases parameter to achieve instant database cloning.

    pg-meta:
      hosts:
        10.10.10.10: { pg_seq: 1, pg_role: primary }
      vars:
        pg_cluster: pg-meta
        pg_version: 18
        pg_databases:

          - name: meta

          - name: meta_dev
            template: meta
            strategy: FILE_COPY         # <---- Introduced in PG 15, instant in PG18
            #comment: "meta clone"      # <---- Database comment
            #pgbouncer: false           # <---- Not added to connection pool?
            #register_datasource: false # <---- Not added to Grafana datasource?

After configuration, use the standard database creation SOP to create the database:

bin/pgsql-db pg-meta meta_dev

Limitations and Notes

This feature is only available on supported file systems (xfs, btrfs, zfs, apfs). If the file system doesn’t support it, PostgreSQL will fail with an error.

By default, mainstream OS distributions’ xfs have reflink=1 enabled by default, so you don’t need to worry about this in most cases.

OpenZFS requires explicit configuration to support CoW, but due to prior data corruption incidents, it’s not recommended for production use.

If your PostgreSQL version is below 15, specifying strategy will have no effect.

Please don’t use the postgres database as a template database for cloning, as management connections typically connect to the postgres database, which prevents the cloning operation.

Use instant cloning with caution in extremely high concurrency/throughput production environments, as it requires clearing all connections to the template database within the cloning window (200ms), otherwise the clone will fail.