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), with an exclude list (POSTGRES_DB_EXCLUDE). - Multiple formats — gzip SQL, directory (
-Fd), each optionally GPG AES-256 encrypted. - 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.
- Restore tooling — interactive, by-file, cross-database, or
--from-telegramdisaster recovery. - Safety — backup verification,
pg_isreadyand disk-space checks,flockagainst 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 |
| 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); exclude names via POSTGRES_DB_EXCLUDE |
SCHEDULE |
@daily |
Cron expression for the backup schedule |
BACKUP_KEEP_DAYS / _WEEKS / _MONTHS |
7 / 4 / 6 |
Retention per rotation slot |
BACKUP_ENCRYPTION_KEY |
"" |
GPG passphrase (enables AES-256 encryption) |
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 |
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, or --from-telegram) |
list |
List backups by rotation slot (--cleanup-preview for a retention dry run) |
status |
Config, last result, inventory, disk usage, lock status |
help |
Quick command reference |
See CLI Commands for usage and example output.
Each cycle writes a timestamped file to last/, then hard-links it into daily/,
weekly/, and monthly/ (shared inode — no extra disk). Retention cleanup runs
after each successful backup, pruning each slot independently.
The
/backupsvolume must be a POSIX filesystem with hardlink and symlink support. VFAT, exFAT, and SMB/CIFS are not supported.
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.
# 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.