Backup PostgreSQL locally with periodic rotation and Telegram bot integration.
4.5K
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 -d
New 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-* examples show using your own app or a self-hosted Bot API server.TELEGRAM_CHAT_ID to 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.
TELEGRAM_API_ID / TELEGRAM_API_HASH
from https://my.telegram.org/apps (also accepted as *_FILE Docker secrets).
They take precedence over the shared default.TELEGRAM_USE_DEFAULT_API=FALSE requires your own
credentials (oversized backups are otherwise reported with a text alert).TELEGRAM_UPLOAD_METHOD = smart (default) | botapi | mtproto.Full details and the self-hosted Bot API server route: docs/LARGE_FILES.md.
go-cron with a configurable SCHEDULE.last / daily / weekly / monthly slots via space-saving hard links.pg_dumpall).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 not CONNECT to are skipped and logged.-Fd), each optionally GPG AES-256 encrypted (directory dumps excepted).pg_dump piped into GPG, no clear dump on disk, key never on a command line); every dump is written to a .part file, fully verified, and only then renamed into place, so a failed or truncated dump never replaces a good one. backup exits 1 when a database failed and 75 when another run holds the lock.BACKUP_RLS_GUARD=TRUE refuses a dump that row-level security would silently cut short.BACKUP_LAYOUT=snapshot keeps timestamped names in every folder (weekly on Sundays, monthly on the 1st) and last/ holds exactly the newest dump of each existing database.GET /metrics, with a dashboard and alert rules in monitoring/. See docs/MONITORING.md.BACKUPGRAM_MODE=s3-sync), with remote retention tiers, list --s3, restore --from-s3, metrics and alerts. See docs/OFFSITE.md.--from-telegram / --from-s3 disaster recovery.pg_isready and disk-space checks, a lock in BACKUP_DIR against overlapping runs.run-parts hooks, Docker secrets (*_FILE).| 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.
postgres:postgres for least-privilege operation.*_FILE variables) instead of plain-text passwords in production.BACKUP_ENCRYPTION_KEY to encrypt backups at rest with GPG AES-256.8080 by default) — do not expose it publicly unless needed.REST_API_ENABLE) listens on 8081 (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.METRICS_ENABLE=TRUE, GET /metrics on 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/pgbackups
Images 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.
Content type
Image
Digest
sha256:3b6922364…
Size
170.8 MB
Last updated
18 days ago
docker pull ganiyevuz/backupgram