Postgres Backup and Restore skill for Codex

Backups proven by a timed restore drill. Codex picks the backup mechanism from your RPO and RTO targets, configures logical dumps, WAL archiving and pgBackRest or provider PITR, and keeps an off-site copy outside the server's failure domain. It restores into separate instances by default, runs timed drills with row-count and pg_amcheck verification, and recovers rows lost to a bad DELETE without overwriting production.

  • Databases
  • $postgres-backup-restore
  • 186 lines
  • 2 files
  • ~1,900 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- postgres-backup-restore

Installs to ~/.agents/skills/postgres-backup-restore/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $postgres-backup-restore, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Sets up and tests PostgreSQL backups that actually restore - logical dumps with pg_dump/pg_restore (custom and directory formats, parallel jobs, globals), physical backups with pg_basebackup and WAL archiving, point-in-time recovery, pgBackRest stanzas, retention and encryption, managed-service snapshots, and timed restore drills with verification queries. Use when designing a Postgres backup strategy, restoring or cloning a database, recovering to a timestamp before a bad deploy or DELETE, or proving RPO and RTO with a drill.

Use it when

  • Designing or auditing a Postgres backup strategy
  • Restoring, cloning or migrating a database from a dump
  • Recovering data to a point in time before a bad change
  • Proving RPO and RTO with a restore drill

Use something else when

How to check the result

A restore into a fresh instance from backup storage alone reaches the target time, passes pg_amcheck and row-count checks against the source, and the measured restore time meets the RTO.

The mistake it prevents

Trusting an archive_command or nightly dump job that reports success but was never restored, then finding gaps during a real incident.

~/.agents/skills/postgres-backup-restore/SKILL.md

--- name: postgres-backup-restore description: Sets up and tests PostgreSQL backups that actually restore - logical dumps with pg_dump/pg_restore (custom and directory formats, parallel jobs,… license: MIT ---

A backup exists only once it has been restored, verified and timed. Done well means the team knows its RPO (how much data it can lose) and RTO (how long recovery takes), both are measured by a recent drill, backups live outside the failure domain of the server, and the restore runbook works when read by someone tired at 3 a.m.

Pick the mechanism

NeedMechanism
Copy one database, migrate versions, seed stagingLogical: pg_dump -Fc / -Fd + pg_restore
Recover to any second in the last N daysPhysical base backup + continuous WAL archive (PITR)
Self-managed productionpgBackRest (or WAL-G / Barman) doing full + incremental + WAL archive
RDS, Aurora, Cloud SQL, Azure, Neon, SupabaseProvider PITR and snapshots; add periodic logical dumps to a separate account for vendor-independence
Table-level undo after a bad DELETEPITR to a scratch instance, then copy the rows back

Logical dumps alone give an RPO equal to the dump interval and a slow restore for large databases (indexes rebuild). They complement, not replace, PITR for production.

Workflow

  1. Read AGENTS.md, infra code (Terraform, Helm, Ansible) and any existing backup jobs. Find where backups go, retention, encryption, and who gets alerted on failure.
  2. Ask for (or propose) RPO and RTO targets. They drive every choice below.
  3. Configure backups (sections below), then run a restore drill and record timings.
  4. Write or update the runbook; schedule recurring drills.

Restoring over an existing database, dropping databases, or changing archive_command on a production server are destructive or risky: describe the exact command and target, and get explicit confirmation from the user before running them. Default to restoring into a new, separate instance or database.

Logical backups

# One database, compressed custom format (allows selective, parallel restore)
pg_dump -h db -U backup -d app -Fc -Z zstd:3 -f app_$(date -u +%Y%m%dT%H%M%SZ).dump
# (-Z with zstd/lz4 needs PG 16+ client; older clients use -Z 6 for gzip)

# Large database: directory format with parallel workers
pg_dump -d app -Fd -j 8 -f app_dir/

# Roles and tablespaces are NOT in pg_dump output
pg_dumpall --globals-only -f globals.sql

# Restore into a fresh database
createdb -T template0 app_restore
pg_restore -d app_restore -j 8 --no-owner --exit-on-error app.dump

# Inspect or select contents
pg_restore -l app.dump > toc.txt      # edit toc.txt, then:
pg_restore -L toc.txt -d app_restore app.dump
  • Use a pg_dump client at least as new as the server; restore into the same or newer major version.
  • pg_dump takes a consistent snapshot but holds it for the whole run; on a busy primary this delays vacuum. Dump from a replica when possible (watch max_standby_streaming_delay cancellations).
  • Plain SQL format (-Fp) cannot be restored in parallel or selectively; avoid for anything large.
  • Dumps contain all the data: encrypt at rest and restrict access like production.

Physical backups and PITR (self-managed)

Core settings in postgresql.conf:

wal_level = replica
archive_mode = on
archive_command = 'pgbackrest --stanza=main archive-push %p'
archive_timeout = 60          # bounds RPO on quiet systems (seconds)

pgBackRest, /etc/pgbackrest/pgbackrest.conf:

[global]
repo1-type=s3
repo1-path=/pgbackrest
repo1-s3-bucket=acme-pg-backups
repo1-s3-endpoint=s3.eu-west-1.amazonaws.com
repo1-s3-region=eu-west-1
repo1-retention-full=2
repo1-cipher-type=aes-256-cbc
compress-type=zst
process-max=4

[main]
pg1-path=/var/lib/postgresql/18/main

Keep repo1-cipher-pass and S3 credentials in the secret store, not in the repo.

pgbackrest --stanza=main stanza-create
pgbackrest --stanza=main check                 # verifies archiving end to end
pgbackrest --stanza=main --type=full backup
pgbackrest --stanza=main --type=diff backup    # schedule: weekly full, daily diff/incr
pgbackrest --stanza=main info                  # backups, WAL range, sizes

Point-in-time restore onto a stopped, separate host:

pgbackrest --stanza=main --delta \
  --type=time "--target=2026-10-27 14:29:00+00" \
  --target-action=promote restore

Without pgBackRest, PostgreSQL’s own path is pg_basebackup -D ... -Ft -z -X stream, then on restore: restore_command plus recovery_target_time in postgresql.conf and an empty recovery.signal file. PG 17+ also supports pg_basebackup --incremental with summarize_wal = on, combined with pg_combinebackup.

Managed services

  • Confirm automated backups are on and retention matches RPO needs (RDS allows up to 35 days of PITR).
  • Snapshots in the same account and region do not survive account compromise or a region outage. Copy snapshots cross-account/region, or ship logical dumps elsewhere.
  • PITR on managed services creates a new instance; plan DNS or connection string changes in the runbook.

Restore drill

Run at least quarterly, and after any backup config change:

  • Pick a target time; restore into a new instance from backup storage only
  • Record wall-clock time for each phase (fetch, restore, WAL replay, ready)
  • Verify integrity and content:
SELECT count(*) FROM orders;                                  -- compare with source at target time
SELECT max(created_at) FROM orders;                           -- should be just before target
SELECT relname, n_live_tup FROM pg_stat_user_tables ORDER BY n_live_tup DESC LIMIT 20;
pg_amcheck -d app_restore --heapallindexed     # PG 14+, checks heap and B-tree consistency
  • Run the application’s smoke tests against the restored instance
  • Compare measured RTO and RPO to targets; file fixes if they miss
  • Destroy the drill instance (it holds production data)

Recovering from a bad change (the common emergency)

  1. Note the exact time of the bad statement (from logs, pg_stat_statements, app logs).
  2. Do not restore over production. PITR to a new instance at just before that time.
  3. Extract the affected rows (COPY ... TO, or pg_dump -t table --data-only).
  4. Show the user a diff of what will be re-inserted or updated, then apply in a transaction on production after confirmation.

Gotchas

  • archive_command that returns success without storing the file (e.g. cp to a full disk with errors ignored) makes PITR silently impossible. Use a tool that verifies, and alert on pg_stat_archiver.failed_count and last_failed_time.
  • A failing archive fills pg_wal until the disk is full and the primary stops. Monitor disk and archiver together.
  • Backups of only the primary data directory miss config files kept elsewhere (/etc/postgresql/...). Back up config with infra code.
  • Extensions and their versions must exist on the restore target.
  • Time zones: give --target with an explicit offset.
  • Retention that deletes the only full backup older than the WAL you need breaks PITR for that window; repo1-retention-full counts full backups, plan accordingly.

Output template

## Backup plan: <cluster>
**Targets:** RPO <min>, RTO <min>
**Mechanism:** <pgBackRest/WAL-G/provider>, schedule <full/diff/incr>, WAL archive <where>
**Retention:** <days / count>, encryption <how>, off-site copy <where>
**Monitoring:** <archiver failures, backup age, disk>
**Last drill:** <date>, restored to <time>, RTO measured <min>, issues <list>
**Runbook:** <link/path>

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Postgres Backup and Restore"
  short_description: "Backups proven by a timed restore drill"
  default_prompt: "$postgres-backup-restore Set up pgBackRest with S3 and point-in-time recovery for our Postgres 18 server"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem