PostgreSQL Alpine image with restic baked in - back up your whole cluster to S3 (or R2/MinI
1.4K
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.
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:
forget
policy expresses "keep N recent / daily / monthly" in one command.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.
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):
| Env | Default | Keeps |
|---|---|---|
KEEP_LAST | 10 | the last 10 snapshots (~40h of 4-hourly runs) |
KEEP_DAILY | 7 | one per day for a week |
KEEP_MONTHLY | 6 | one 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
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
| Tag | Contents |
|---|---|
latest | current Postgres major + restic |
17 | Postgres 17 + restic |
Multi-arch: linux/amd64, linux/arm64 (Raspberry Pi 3/4/5, 64-bit OS), linux/arm/v7 (32-bit Raspberry Pi OS).
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.
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.
docker build -t postgres-alpine-restic:17 --build-arg PG_VERSION=17 .
GitHub Actions (.github/workflows/build.yml)
builds and pushes to Docker Hub:
main touching the build files,postgres:*-alpine and restic
patch releases,pg_version).| Secret | Value |
|---|---|
DOCKERHUB_TOKEN | a Docker Hub access token with Read/Write for the guestros account |
The Docker Hub username is hard-coded to
guestrosin the workflow (env.IMAGE); change it there if you push to a different namespace.
MIT — see LICENSE. restic and PostgreSQL retain their own licenses.
Content type
Image
Digest
sha256:505a56cfe…
Size
126.4 MB
Last updated
7 days ago
docker pull guestros/postgres-alpine-restic