Sign inSign up

guestros/postgres-alpine-restic

By guestros

Updated 7 days ago

PostgreSQL Alpine image with restic baked in - back up your whole cluster to S3 (or R2/MinI

Image
Developer tools
0

1.4K

guestros/postgres-alpine-restic repository overview

postgres-alpine-restic

build-and-push

The official postgres:*-alpine image with restic added, so you can back the whole cluster up to an S3 bucket on a schedule with nothing else installed.

Backups are logical dumps: pg_dumpall (already in the base image) streams every database plus roles/globals into restic, which does content-addressed dedup, encryption, retention, and the S3 upload. The image is just stock postgres:*-alpine + one Alpine package (restic) + two helper scripts.

Why this exists

The official postgres image ships Postgres and nothing else — no backup tool. The common answer, WAL-G, gives you point-in-time recovery but at real cost: an archive_command wired into the live server, continuous WAL streaming, a co-located sidecar, a multi-step physical restore, and physical backups that are tied to a specific libc/arch/Postgres version.

For the usual goal — "back up my database to S3 on a cron" — that's more machinery than you need. A logical pg_dumpall + restic gives you:

  • A self-contained snapshot of the whole cluster (all DBs + roles), portable across Postgres versions, architectures, and libc.
  • Dedup + retention for free — restic stores content-addressed chunks, so months of snapshots cost a fraction of months × dump-size, and its forget policy expresses "keep N recent / daily / monthly" in one command.
  • Encryption and S3/R2/B2 built in — no extra tooling.

Tradeoff: no PITR. A 4-hourly cron means up to ~4h of data loss on a crash-restore, and pg_dumpall/restore is heavier on very large databases. For app-sized databases that's a fine trade; if you need second-level PITR on a large, high-write cluster, use WAL-G instead.

Backing up to S3 on a cron

Give the container the connection + restic env (works with AWS S3, Cloudflare R2, MinIO, Backblaze B2 — anything S3-compatible):

# how to reach Postgres (PGUSER must be a superuser for pg_dumpall)
PGHOST=postgres
PGUSER=postgres
PGPASSWORD=...

# where restic puts the backup + how it's encrypted
RESTIC_REPOSITORY=s3:s3.amazonaws.com/my-bucket/pg   # AWS
# RESTIC_REPOSITORY=s3:https://<endpoint>/my-bucket/pg  # R2 / MinIO / B2
RESTIC_PASSWORD=<repo-encryption-password>            # keep this — backups are useless without it
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...

The image's entrypoint is the backup: running the container does the whole cycle — bootstrap the repo on first run, pg_dumpall | restic backup, then prune old snapshots — and exits. Drive it from a host cron:

# every 4 hours
0 */4 * * *  docker run --rm --env-file /etc/pg-backup.env guestros/postgres-alpine-restic:17 >> /var/log/pg-backup.log 2>&1

Retention is restic's forget policy, tunable via env (defaults shown):

EnvDefaultKeeps
KEEP_LAST10the last 10 snapshots (~40h of 4-hourly runs)
KEEP_DAILY7one per day for a week
KEEP_MONTHLY6one per month for six months

(equivalent to pg_dumpall \| restic backup --stdin --stdin-filename pgdumpall.sql then restic forget --keep-last 10 --keep-daily 7 --keep-monthly 6 --prune if you'd rather call the tools directly.)

Restore a snapshot straight back into Postgres:

restic snapshots                                   # find the one you want
restic dump latest pgdumpall.sql | psql -h <host> -U postgres postgres
Kubernetes

Two manifests in kubernetes/:

  • postgres.yaml — a normal Postgres Deployment on stock postgres:*-alpine (PVC + Service + password). No backup machinery, so it needs no special image.
  • backup-cronjob.yaml — a native CronJob that runs pg-backup every 4 hours and exits.

Because a logical dump is a network client, the CronJob just connects to the postgres Service — so there's no volume sharing, no RBAC, no serviceAccount, and it works on plain ReadWriteOnce storage.

# edit the Secrets first (postgres-auth password; restic-s3 bucket + creds + RESTIC_PASSWORD)
kubectl apply -f kubernetes/postgres.yaml
kubectl apply -f kubernetes/backup-cronjob.yaml

Tags

TagContents
latestcurrent Postgres major + restic
17Postgres 17 + restic

Multi-arch: linux/amd64, linux/arm64 (Raspberry Pi 3/4/5, 64-bit OS), linux/arm/v7 (32-bit Raspberry Pi OS).

Small & low attack surface

The image is essentially postgres:17-alpine plus one Alpine package (restic) — pg_dumpall is already in the base. There's no build stage, no compiled binary of our own, and zero source compilation: restic is Alpine's own prebuilt musl binary.

That keeps it tiny and low-CVE: it's built on musl/alpine and adds a single well-audited backup tool on top of stock Postgres, a fraction of a Debian-based postgres + backup-tooling stack. Its attack surface is essentially official postgres:17-alpine plus restic — far smaller (and far fewer CVEs to track) than a Debian Postgres plus a heavier backup agent.

Usage

This is a backup-only image — its entrypoint runs one pg_dumpall | restic backup + prune and exits. Point it at a running Postgres (which should be stock postgres:*-alpine) and give it the connection + restic env:

docker run --rm \
  -e PGHOST=postgres -e PGUSER=postgres -e PGPASSWORD=... \
  -e RESTIC_REPOSITORY=s3:s3.amazonaws.com/my-bucket/pg \
  -e RESTIC_PASSWORD=... \
  -e AWS_ACCESS_KEY_ID=... -e AWS_SECRET_ACCESS_KEY=... \
  guestros/postgres-alpine-restic:17

restic and pg-backup are on PATH if you need to run them directly (docker run --rm ... --entrypoint restic <image> snapshots). Configure the destination via restic's environment variables.

Building locally

docker build -t postgres-alpine-restic:17 --build-arg PG_VERSION=17 .

Automated builds

GitHub Actions (.github/workflows/build.yml) builds and pushes to Docker Hub:

  • on every push to main touching the build files,
  • weekly (Mondays 04:17 UTC) to pick up new postgres:*-alpine and restic patch releases,
  • manually via Run workflow (override pg_version).
Required repository secrets
SecretValue
DOCKERHUB_TOKENa Docker Hub access token with Read/Write for the guestros account

The Docker Hub username is hard-coded to guestros in the workflow (env.IMAGE); change it there if you push to a different namespace.

License

MIT — see LICENSE. restic and PostgreSQL retain their own licenses.

Tag summary

Content type

Image

Digest

sha256:505a56cfe

Size

126.4 MB

Last updated

7 days ago

docker pull guestros/postgres-alpine-restic