Automated PostgreSQL backups in Docker with rotating retention, Telegram notifications, optional GPG encryption, and built-in restore tooling.
Supports multiple databases, cluster-wide dumps (pg_dumpall), table exclusion, disk space checks, backup verification, webhook integrations, and Docker secrets. Available for linux/amd64 and linux/arm64 in both Debian and Alpine variants.
Create a docker-compose.yml (or copy a ready-made one — see Examples below):
services:
postgres:
image: postgres:17
environment:
POSTGRES_DB: mydb
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
volumes:
- pgdata:/var/lib/postgresql/data
backup:
image: ganiyevuz/backupgram:17
depends_on:
- postgres
environment:
POSTGRES_HOST: postgres
POSTGRES_DB: mydb
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
SCHEDULE: "@daily"
TELEGRAM_BOT_TOKEN: "${TELEGRAM_BOT_TOKEN}"
TELEGRAM_CHAT_ID: "${TELEGRAM_CHAT_ID}"
volumes:
- backups:/backups
volumes:
pgdata:
backups:docker compose up -dNew to the project? Follow the Getting Started guide for a zero-to-restorable-backup walkthrough.
Ready-to-run Compose files live in examples/. Pick the one closest
to your need, copy it to docker-compose.yml, copy
.env.example to .env and fill in your values, then
docker compose up -d.
| Example | Use it when | Max file size | Extra service |
|---|---|---|---|
minimal |
Quickest start — one DB, daily, Telegram | 50 MB | none |
full |
Reference — every option, commented | 50 MB | none |
large-files-mtproto |
Backups > 50 MB, simplest (recommended) | 2 GB | none |
large-files-server |
Backups > 50 MB, prefer a Bot API server | 2 GB | telegram-bot-api sidecar |
multi-destination |
Deliver each backup to several chats | 2 GB | none |
cp examples/docker-compose.minimal.yml docker-compose.yml
cp examples/.env.example .env # then edit .env with real values
docker compose up -d- Large files (> 50 MB): work out of the box up to 2 GB — every image ships a
shared Telegram app for MTProto upload (details in Large backups below). The two
large-files-*examples show using your own app or a self-hosted Bot API server. - Multiple chats: set
TELEGRAM_CHAT_IDto a comma-separated list — the backup is uploaded once and fanned out to every chat.
The official Telegram Bot API caps uploads at 50 MB. backupgram lifts that to 2 GB
over MTProto with zero setup — every image ships a shared Telegram app, so large
backups are delivered automatically once TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID
are set. The shared app only identifies the app to Telegram; your bot token
authenticates and backups go to your own chat, so your data is never exposed. It is
baked into the public image (recoverable), so using your own is recommended.
- Use your own app (recommended): set
TELEGRAM_API_ID/TELEGRAM_API_HASHfrom https://my.telegram.org/apps (also accepted as*_FILEDocker secrets). They take precedence over the shared default. - Disable the shared default:
TELEGRAM_USE_DEFAULT_API=FALSErequires your own credentials (oversized backups are otherwise reported with a text alert). - Force a transport:
TELEGRAM_UPLOAD_METHOD=smart(default) |botapi|mtproto.
Full details and the self-hosted Bot API server route: docs/LARGE_FILES.md.
- Scheduled backups via
go-cronwith a configurableSCHEDULE. - Rotating retention —
last/daily/weekly/monthlyslots via space-saving hard links. - Multiple databases and cluster-wide dumps (
pg_dumpall). - Auto-discover databases — back up every non-template database on the server (
POSTGRES_DB_AUTODISCOVER), narrowed by include globs (POSTGRES_DB_INCLUDE, e.g.control,pharmacy_*) and an exclude list (POSTGRES_DB_EXCLUDE); databases the login may notCONNECTto are skipped and logged. - Multiple formats — gzip SQL, custom, directory (
-Fd), each optionally GPG AES-256 encrypted (directory dumps excepted). - Safer pipeline — encryption streams (
pg_dumppiped into GPG, no clear dump on disk, key never on a command line); every dump is written to a.partfile, fully verified, and only then renamed into place, so a failed or truncated dump never replaces a good one.backupexits1when a database failed and75when another run holds the lock. - Row-level-security guard —
BACKUP_RLS_GUARD=TRUErefuses a dump that row-level security would silently cut short. - Snapshot layout —
BACKUP_LAYOUT=snapshotkeeps timestamped names in every folder (weekly on Sundays, monthly on the 1st) andlast/holds exactly the newest dump of each existing database. - Prometheus metrics + Grafana dashboard — per-database backup age, size and result, written for node-exporter's textfile collector or served at
GET /metrics, with a dashboard and alert rules inmonitoring/. See docs/MONITORING.md. - Telegram delivery — Bot API for small files, zero-setup MTProto upload up to 2 GB (a shared app ships in the image), multi-chat fan-out.
- Off-site copies (S3) — keep a copy of every dump in any S3-compatible storage (AWS S3, Hetzner, Backblaze B2, Wasabi, Cloudflare R2, or RustFS/Garage/SeaweedFS on another machine): at the end of each backup run or from a separate uploader container (
BACKUPGRAM_MODE=s3-sync), with remote retention tiers,list --s3,restore --from-s3, metrics and alerts. See docs/OFFSITE.md. - Restore tooling — interactive, by-file, cross-database, or
--from-telegram/--from-s3disaster recovery. - Safety — full backup verification,
pg_isreadyand disk-space checks, a lock inBACKUP_DIRagainst overlapping runs. - Integrations — webhooks (pre/post/error), custom
run-partshooks, Docker secrets (*_FILE). - REST API (opt-in) — trigger/observe/restore/download/delete backups and change runtime settings over HTTP, behind a bearer token. See docs/REST_API.md.
| Doc | What's in it |
|---|---|
| Getting Started | First-backup walkthrough and troubleshooting |
| Configuration Reference | Every environment variable, Docker secrets, retention math |
| CLI Commands | backup, restore, list, status, help with example output |
| Architecture | Runtime chain, backup cycle, rotation model, format branches (C4 + mermaid) |
| Large Files | MTProto upload for backups over 50 MB |
| Monitoring | Prometheus metrics, Grafana dashboard, alert rules |
| Off-site copies | S3-compatible off-site copies: both deployments, retention, providers, disaster recovery |
| REST API | Optional HTTP control surface: endpoints, auth, runtime config |
| Build | Multi-arch image builds |
| Changelog | Notable changes |
Full doc index: docs/.
All settings are environment variables; credentials also accept *_FILE (Docker
secrets) variants that take precedence over the plain value. The most common:
| Variable | Default | Description |
|---|---|---|
POSTGRES_HOST / POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB |
required | Connection + database name(s) |
POSTGRES_DB_AUTODISCOVER |
FALSE |
Back up every non-template DB (makes POSTGRES_DB optional); narrow with POSTGRES_DB_INCLUDE (globs) and POSTGRES_DB_EXCLUDE |
SCHEDULE |
@daily |
Cron expression for the backup schedule |
BACKUP_KEEP_DAYS / _WEEKS / _MONTHS |
7 / 4 / 6 |
Retention per rotation slot |
BACKUP_LAYOUT |
period |
period or snapshot (timestamped names in every folder) |
BACKUP_RLS_GUARD |
FALSE |
Refuse dumps that row-level security would cut short |
BACKUP_ENCRYPTION_KEY |
"" |
GPG passphrase (enables AES-256 encryption) |
METRICS_TEXTFILE_DIR / METRICS_ENABLE |
"" / FALSE |
Prometheus metrics: a textfile for node-exporter, and/or GET /metrics on REST_API_PORT |
TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID |
"" |
Telegram delivery (chat id list = fan-out) |
TELEGRAM_USE_DEFAULT_API |
TRUE |
Use the image's built-in shared app for large-file (2 GB) upload; set FALSE to require your own TELEGRAM_API_ID/TELEGRAM_API_HASH |
S3_BUCKET / S3_ENDPOINT / S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY |
"" / AWS / "" / "" |
Off-site copies to S3-compatible storage (credentials also as *_FILE); BACKUPGRAM_MODE=s3-sync runs a separate uploader |
See the Configuration Reference for the complete list.
Available inside the container via docker exec -it <container> <command>:
| Command | Purpose |
|---|---|
backup |
Trigger a full backup cycle immediately |
restore |
Restore from a backup (interactive, by file, cross-db, --from-telegram, or --from-s3) |
list |
List backups by rotation slot (--cleanup-preview for a retention dry run, --s3 for the off-site bucket) |
status |
Config, last result, inventory, disk usage, lock status |
help |
Quick command reference |
See CLI Commands for usage and example output.
Each cycle writes every database's dump to a .part file in last/, verifies it,
renames it into place, then hard-links it into daily/, weekly/, and monthly/
(shared inode — no extra disk). A database that fails keeps its previous dump and
the run exits 1. Retention cleanup runs once per run after all databases, over
every file of each slot; it leaves the copies of a database that failed in the run
alone, and is skipped when nothing was backed up.
The
/backupsvolume must be a POSIX filesystem with hardlink and symlink support. VFAT, exFAT, and SMB/CIFS are not supported. Use one backup folder per server/configuration: retention acts on every file in it.
Details and diagrams: Architecture.
Place executable scripts in /hooks; they run via run-parts with pre-backup,
post-backup, or error. The bundled 00-webhook implements the WEBHOOK_*
variables — add your own alongside it.
- Run the container as
postgres:postgresfor least-privilege operation. - Use Docker secrets (
*_FILEvariables) instead of plain-text passwords in production. - Enable
BACKUP_ENCRYPTION_KEYto encrypt backups at rest with GPG AES-256. - The healthcheck runs on an internal port (
8080by default) — do not expose it publicly unless needed. - The optional REST API (
REST_API_ENABLE) listens on8081(REST_API_PORT) behind a bearer token — bind it to loopback and front it with a TLS-terminating reverse proxy; never expose it directly. See docs/REST_API.md. - With
METRICS_ENABLE=TRUE,GET /metricson that same port needs no token and lists database names and backup results — keep the port on an internal network. See docs/MONITORING.md.
# Debian-based image (UID 999)
mkdir -p /var/opt/pgbackups && chown -R 999:999 /var/opt/pgbackups
# Alpine-based image (UID 70)
mkdir -p /var/opt/pgbackups && chown -R 70:70 /var/opt/pgbackupsImages are published as ganiyevuz/backupgram:<pg-version>[-alpine].
| Tag | Base | PostgreSQL |
|---|---|---|
latest, 18, 17, 16, 15 |
Debian | Matching version (latest = 18) |
alpine, 18-alpine, 17-alpine, ... |
Alpine | Matching version (alpine = 18) |
See LICENSE for details.