pg-autodump

module
v1.0.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 16, 2026 License: GPL-2.0, GPL-3.0

README

pg-autodump

Image Size Platforms Go Report Card Test coverage Mutation OpenSSF Best Practices OpenSSF Scorecard SBOM

On-demand PostgreSQL logical-backup sidecar. Trigger it, and it writes a verified dump per database for your real backup tool to collect.

What it does

pg-autodump runs pg_dump (custom format) against every database in DB_SPECS, verifies each dump with pg_restore --list, and writes it atomically into a shared volume, keeping the newest DUMP_KEEP copies per database (7 by default). It does its one job well and delegates the heavy lifting: no compression, encryption, or off-site sync — your backup tool (Kopia, Restic, Borg, rsync) already does those, and points at the /dumps volume. If the collector also versions your backups, set DUMP_KEEP=1 to keep a single stable <dbname>.dump and hand retention to it.

It runs as an ordinary unprivileged user, connects to Postgres over the network (the PostgreSQL wire protocol, not HTTP), and never touches the Docker socket or runs as root. A truncated or failed dump can never overwrite a known-good backup.

Why this design
  • Unprivileged and socket-less — non-root, cap_drop: [ALL], read_only. A compromise reads databases through a least-privilege role; it cannot reach the host. No Docker socket, so no root-equivalent surface.
  • Verify before replace — each dump stages to a temp file, passes a non-empty and pg_restore --list (TOC) check, then atomically renames into place. The last known-good dump survives any failure (atomicfile adds the dir-fsync a plain mv lacks).
  • Bounded parallelismDUMP_CONCURRENCY dumps databases concurrently with no per-host serialization, so the common one-server-many-DBs case is not forced serial. One knob, safe default.
  • Built-in retention — keeps the newest DUMP_KEEP timestamped dumps per database (7 by default), pruning older ones after each successful run, so it works as a self-contained incremental backup out of the box. Set DUMP_KEEP=1 to instead keep a single stable <dbname>.dump and delegate versioning to your backup tool.
  • Standard surfacePOST /dump, GET /healthz. Trigger by the built-in daily timer (default), over HTTP, or docker exec ... pg-autodump trigger.

Quick start

The image is published to both GHCR (ghcr.io/cplieger/pg-autodump) and Docker Hub (cplieger/pg-autodump) — identical contents, use whichever you prefer.

  1. Create a least-privilege backup role in each database:

    CREATE ROLE dbdumper_ro LOGIN PASSWORD 'choose-a-strong-password';
    GRANT pg_read_all_data TO dbdumper_ro;          -- PostgreSQL 14+
    GRANT CONNECT ON DATABASE myapp TO dbdumper_ro;
    
  2. Create a .pgpass (mode 0600, or libpq silently ignores it). One line per host:port:dbname:user:

    mydb-host:5432:myapp:dbdumper_ro:choose-a-strong-password
    
  3. Run it (see compose.yaml for the full example):

    services:
      pg-autodump:
        image: ghcr.io/cplieger/pg-autodump:latest
        container_name: pg-autodump
        restart: unless-stopped
        read_only: true
        cap_drop: ["ALL"]
        security_opt: ["no-new-privileges:true"]
        environment:
          DB_SPECS: "mydb-host:5432:myapp:dbdumper_ro"
        ports:
          - "127.0.0.1:9847:9847"
        volumes:
          - "./secrets/.pgpass:/secrets/.pgpass:ro"   # mode 0600
          - "./dumps:/dumps"
        tmpfs:
          - "/tmp:size=16m,mode=1777"   # 1777 so the non-root user can write the health marker
    
  4. Trigger a backup:

    curl -fsS -X POST http://127.0.0.1:9847/dump
    # or: docker exec pg-autodump pg-autodump trigger
    

Configuration reference

Environment variables
Variable Description Default Required
DB_SPECS Space-separated host[:port]:dbname:user tuples (port defaults to 5432). Ids are [a-zA-Z0-9_-] (host also allows .), no leading -, no .., no control chars. Invalid entries are reported per-DB and skipped. IPv6 literals are unsupported (use a hostname). - Yes
PGPASSFILE Path to a read-only .pgpass (mode 0600). PGPASSWORD is also honoured by libpq but .pgpass is preferred (scoped per host/db/user). /secrets/.pgpass No
DUMP_DIR Output directory. Values containing .. are rejected. /dumps No
DUMP_TIMEOUT Per-dump seconds (min 10). 300 No
DUMP_CONCURRENCY Parallel dumps. Raise for many hosts / fast storage; set 1 for a single slow backup volume. 2 No
DUMP_INTERVAL Built-in timer cadence (Go duration). off / disabled / 0 hand scheduling to an external trigger. 24h No
DUMP_KEEP Retained dumps per database. >1 (default 7) writes timestamped <dbname>.<UTC>.dump files and prunes to the N newest. 1 writes a single stable <dbname>.dump, overwritten each run (delegate versioning to your backup tool). 7 No
DUMP_FREE_KB_WARN Warn when free space on /dumps falls below this (KB) at run start. 0 disables. 1048576 No
AUTH_TOKEN When set, /dump requires Authorization: Bearer <token>. Empty = open (fine on a private network / loopback). "" No
LISTEN_ADDR HTTP listen address. :9847 No
SHUTDOWN_GRACE Drain budget on SIGTERM. Set compose stop_grace_period >= this. DUMP_TIMEOUT+15s No
Volumes
Mount Description
/secrets/.pgpass Read-only .pgpass (mode 0600). Optional when PGPASSWORD is used.
/dumps Output directory; verified dump files per database (one stable <dbname>.dump, or DUMP_KEEP timestamped copies).
Endpoints
  • POST /dump — run all dumps; 200 if every database succeeded, 500 if any failed, 429 if a run is already in progress, 401 if AUTH_TOKEN is set and the bearer token is missing/wrong. The body has one host/db: reason line per database.
  • GET /healthz200 ok / 503 unhealthy. Reflects liveness preconditions (client binaries present, /dumps writable, DB_SPECS non-empty), not per-host database reachability, so a transiently-down database never flips the container unhealthy.

Healthcheck

The Docker HEALTHCHECK runs the pg-autodump health subcommand — a file-marker probe, so no shell, curl, or open port is needed in the image. The main process writes the marker once liveness preconditions hold (the client binaries resolve, /dumps is writable, DB_SPECS is non-empty); a transiently-down database does not flip the container unhealthy, because per-host reachability is a per-dump concern reported in POST /dump, not liveness.

The backup role

pg_read_all_data (PostgreSQL 14+) grants read on all ordinary tables, views, and sequences — exactly what a logical dump needs. Caveats to document for your databases:

  • Large objects (pg_largeobject) are not covered by pg_read_all_data (BUG #19379). A database using them needs an owning/superuser role, lo_compat_privileges, or --no-large-objects if blobs are not part of the backup contract.
  • Row-level security requires BYPASSRLS (a superuser-granted attribute, more than read-only) for pg_dump to read RLS-protected tables.
  • The role is cluster-level and SELECT-only: it cannot modify the database and is unaffected by application updates. A fresh data-directory re-init drops it (recreate it), and a dump holds ACCESS SHARE locks, so schedule dumps outside heavy DDL/migration windows.
  • On PostgreSQL < 14, grant SELECT on all tables plus schema USAGE instead.

Versioning

The image ships the newest PostgreSQL client major it is built with. pg_dump requires the client major to be >= the server major, so a client can dump any server up to its own version. Bump the client major when you upgrade a server ahead of it; a too-old client is reported per-DB as version_mismatch with a clear message rather than a cryptic pg_dump abort.

Security

  • No Docker socket, no root. The container needs only network reach to the databases, a read-only .pgpass, and a writable /dumps. Runs as a non-root user with cap_drop: [ALL] and read_only.
  • Credentials never on a command line or in logs. They live in .pgpass (or PGPASSWORD); pg_dump is invoked with --no-password so it never prompts.
  • No shell, explicit argv. DB_SPECS is validated once, and identifiers are passed as long options (--dbname=, --username=) so a value can never be read as a flag. No shell is ever invoked.
  • Keep it private or set AUTH_TOKEN. A stray trigger can at most write a read-only-role dump to the volume.

The CI battery runs govulncheck, golangci-lint (gosec, gocritic), trivy, grype, gitleaks, semgrep, and hadolint on every change; DB_SPECS parsing is fuzzed.

Dependencies

Updated automatically via Renovate and pinned by digest. Builds carry signed SBOMs and provenance attestations verifiable with gh attestation verify.

Dependency Source
golang Go
alpine Alpine
postgresql18-client PostgreSQL
tini GitHub
github.com/cplieger/atomicfile GitHub
github.com/cplieger/health GitHub
pgregory.net/rapid pkg.go.dev

A logical backup is far more than one query — pg_dump reconstructs the full schema from the system catalogs, dependency-orders it, streams every table via COPY, and emits the custom archive format pg_restore reads and verifies. So the postgresql-client (pg_dump/pg_restore/psql + libpq) is a required, irreducible dependency, and the reason the image is Alpine (libc) rather than distroless.

Credits

The PostgreSQL client tools pg_dump, pg_restore, and psql are part of PostgreSQL (PostgreSQL License). The pg_dump argument construction and exit-code handling were informed by orgrim/pg_back (2-clause BSD), used as a reference only — not vendored.

Migrating from db-dumper 1.x

pg-autodump is the successor to db-dumper, which ran pg_dump via docker exec over the root-equivalent Docker socket. pg-autodump is a network client instead: no socket, no root. To migrate: remove the socket mount and user: "0:0"; rewrite DB_SPECS from container:dbname:user to host[:port]:dbname:user; provide credentials via a read-only .pgpass and a least-privilege role (above); triggers move from GET /cgi-bin/dump to POST /dump (or pg-autodump trigger) and health to GET /healthz; the healthcheck becomes ["CMD", "pg-autodump", "health"]; set stop_grace_period >= SHUTDOWN_GRACE. CGI_DIR and TZ are gone.

Contributing

Issues and pull requests are welcome. Please open an issue first for larger changes so the approach can be discussed before implementation. See CONTRIBUTING.md for the package layout, load-bearing invariants, and local checks.

Disclaimer

These images are built with care and follow security best practices, but they are intended for homelab use. No guarantees of fitness for production environments. Use at your own risk.

This project was built with AI-assisted tooling using Claude Opus and Kiro. The human maintainer defines architecture, supervises implementation, and makes all final decisions.

License

This project is licensed under the GNU General Public License v3.0.

Directories

Path Synopsis
cmd
pg-autodump command
Command pg-autodump is the composition root.
Command pg-autodump is the composition root.
internal
config
Package config is the single environment-reading layer.
Package config is the single environment-reading layer.
dump
Package dump is the core of pg-autodump: it orchestrates one dump run, drives the pg boundary to stream a network pg_dump per database, verifies each dump locally, atomically replaces the previous good file, and reports a typed result per database.
Package dump is the core of pg-autodump: it orchestrates one dump run, drives the pg boundary to stream a network pg_dump per database, verifies each dump locally, atomically replaces the previous good file, and reports a typed result per database.
httpapi
Package httpapi is the HTTP control surface: POST /dump (optional bearer auth) and GET /healthz (liveness, via the health library).
Package httpapi is the HTTP control surface: POST /dump (optional bearer auth) and GET /healthz (liveness, via the health library).
obs
Package obs wires startup observability to pg-autodump's domain: a preflight check used to decide the health-marker state at boot.
Package obs wires startup observability to pg-autodump's domain: a preflight check used to decide the health-marker state at boot.
pg
Package pg is the external boundary over the pg_dump / pg_restore / psql command-line tools.
Package pg is the external boundary over the pg_dump / pg_restore / psql command-line tools.
spec
Package spec parses and validates the DB_SPECS environment variable: a whitespace-separated list of "host[:port]:dbname:user" tuples describing which PostgreSQL databases to dump and over which network coordinates.
Package spec parses and validates the DB_SPECS environment variable: a whitespace-separated list of "host[:port]:dbname:user" tuples describing which PostgreSQL databases to dump and over which network coordinates.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL