Automated Telegram backup with Docker. Performs incremental backups of messages and media on a configurable schedule.
- Incremental backups — Only downloads new messages since last backup
- Scheduled execution — Configurable cron schedule (default: every 6 hours)
- Real-time listener — Catch edits, deletions, and new messages instantly between backups
- Album support — Groups photos/videos sent together as albums
- Service messages — Tracks group photo changes, title changes, user joins/leaves
- Forwarded message info — Shows original sender name for forwarded messages
- Channel signatures — Displays post author when channels have signatures enabled
- Media deduplication — Symlinks identical files to save disk space
- Avatars always fresh — Profile photos updated on every backup run
- Photos, videos, documents, stickers, GIFs
- Voice messages and audio files with in-browser player
- Polls with vote counts and results
- Configurable size limits and selective download
- Telegram-like dark UI — Feels like the real app
- Mobile-friendly — Responsive design with iOS/Android optimizations
- Integrated lightbox — View photos and videos without leaving the page
- Keyboard navigation — Arrow keys to browse media, Esc to close
- Real-time updates — WebSocket sync shows new messages instantly
- Push notifications — Get notified even when browser is closed
- Chat search — Find messages by text content
- JSON export — Download chat history with date range filters
- Optional authentication — Password-protect your viewer
- Restricted sharing — Share specific chats via
DISPLAY_CHAT_IDS - Mass deletion protection — Rate limiting prevents accidental data loss
- Runs as non-root — Docker best practices
- SQLite (default) — Zero config, single file
- PostgreSQL — For larger deployments with real-time LISTEN/NOTIFY
See docs/CHANGELOG.md for complete version history.
Have a feature request? Open an issue!
Two separate Docker images are available (v4.0+):
| Image | Purpose | Size |
|---|---|---|
drumsergio/telegram-archive |
Backup scheduler (requires Telegram credentials) | ~300MB |
drumsergio/telegram-archive-viewer |
Web viewer only (no Telegram client) | ~150MB |
📦 Upgrading from v3.x? See Upgrading from v3.x to v4.0 for migration instructions.
- Go to https://my.telegram.org/apps
- Log in with your phone number
- Create a new application (any name/platform)
- Note your API ID (numbers) and API Hash (letters+numbers)
# Clone the repository
git clone https://github.com/GeiserX/Telegram-Archive
cd Telegram-Archive
# Create data directories
mkdir -p data/session data/backups
chmod -R 755 data/
# Configure environment
cp .env.example .envEdit .env with your credentials:
TELEGRAM_API_ID=12345678 # Your API ID
TELEGRAM_API_HASH=abcdef123456 # Your API Hash
TELEGRAM_PHONE=+1234567890 # Your phone (with country code)Option A: Using the provided scripts (recommended for fresh installs)
# Run authentication
./init_auth.sh # Linux/Mac
# init_auth.bat # WindowsOption B: Direct Docker command (for existing deployments or re-authentication)
If your session expires or you need to re-authenticate an existing container:
# Generic command - adjust volume paths and credentials
docker run -it --rm \
-e TELEGRAM_API_ID=YOUR_API_ID \
-e TELEGRAM_API_HASH=YOUR_API_HASH \
-e TELEGRAM_PHONE=+YOUR_PHONE_NUMBER \
-e SESSION_NAME=telegram_backup \
-v /path/to/your/session:/data/session \
drumsergio/telegram-archive:latest \
python -m src authExample for docker compose deployment:
# If using docker compose with a session volume
docker run -it --rm \
--env-file .env \
-v telegram-archive_session:/data/session \
drumsergio/telegram-archive:latest \
python -m src auth
# Then restart the backup container
docker compose restart telegram-backupWhat happens during authentication:
- The script connects to Telegram's servers
- Telegram sends a verification code to your Telegram app (check "Telegram" chat)
- Enter the code when prompted
- If you have 2FA enabled, enter your password when prompted
- Session is saved to the mounted volume for future use
docker compose up -dView your backup at http://localhost:8000
| Problem | Solution |
|---|---|
Permission denied |
Run chmod -R 755 data/ |
init_auth.sh: command not found |
Run chmod +x init_auth.sh first |
| Viewer shows no data | Both containers need same database path - see Database Configuration |
Failed to authorize |
Re-run ./init_auth.sh |
The standalone viewer image (drumsergio/telegram-archive-viewer) lets you browse backups without running the backup scheduler.
# Example: Viewer-only deployment
services:
telegram-viewer:
image: drumsergio/telegram-archive-viewer:latest
ports:
- "8000:8000"
environment:
BACKUP_PATH: /data/backups
DATABASE_DIR: /data/db
VIEWER_USERNAME: admin
VIEWER_PASSWORD: your-secure-password
VIEWER_TIMEZONE: Europe/Madrid
volumes:
- /path/to/backups:/data/backups:ro
- /path/to/db:/data/db:roBrowse your backups at http://localhost:8000
All settings are configured via environment variables. Set them in your .env file or as environment: entries in docker-compose.yml. See .env.example for a ready-to-use template.
ENABLE_LISTENERis a master switch. When set tofalse(the default), allLISTEN_*andMASS_OPERATION_*variables have no effect. You only need to configure those when you setENABLE_LISTENER=true.
The Scope column shows whether each variable applies to the backup scheduler (B), the web viewer (V), or both (B/V).
| Variable | Default | Scope | Description |
|---|---|---|---|
| Telegram Credentials | |||
TELEGRAM_API_ID |
required | B | API ID from my.telegram.org |
TELEGRAM_API_HASH |
required | B | API Hash from my.telegram.org |
TELEGRAM_PHONE |
required | B | Phone number with country code (e.g., +1234567890) |
| Backup Schedule & Storage | |||
SCHEDULE |
0 */6 * * * |
B | Cron expression for backup frequency |
BACKUP_PATH |
/data/backups |
B/V | Base path for backup data and media |
DOWNLOAD_MEDIA |
true |
B | Download media files (photos, videos, documents) |
MAX_MEDIA_SIZE_MB |
100 |
B | Skip media files larger than this (MB) |
BATCH_SIZE |
100 |
B | Messages processed per database batch |
DATABASE_TIMEOUT |
60.0 |
B/V | Database operation timeout in seconds |
SESSION_NAME |
telegram_backup |
B | Telethon session file name |
DEDUPLICATE_MEDIA |
true |
B | Symlink identical media files across chats to save disk space |
SYNC_DELETIONS_EDITS |
false |
B | Batch-check ALL messages for edits/deletions each run (expensive!) |
VERIFY_MEDIA |
false |
B | Re-download missing or corrupted media files |
STATS_CALCULATION_HOUR |
3 |
B | Hour (0-23) to recalculate backup statistics daily |
PRIORITY_CHAT_IDS |
- | B | Comma-separated chat IDs to process first in all operations |
LOG_LEVEL |
INFO |
B/V | Logging verbosity: DEBUG, INFO, WARNING/WARN, ERROR |
| Chat Filtering | See Chat Filtering below | ||
CHAT_IDS |
- | B | Whitelist mode: backup ONLY these chats (ignores all other filters) |
CHAT_TYPES |
private,groups,channels |
B | Type-based mode: comma-separated chat types to backup |
GLOBAL_EXCLUDE_CHAT_IDS |
- | B | Exclude specific chats (any type) |
GLOBAL_INCLUDE_CHAT_IDS |
- | B | Force-include specific chats (any type) |
PRIVATE_EXCLUDE_CHAT_IDS |
- | B | Exclude specific private chats |
PRIVATE_INCLUDE_CHAT_IDS |
- | B | Force-include specific private chats |
GROUPS_EXCLUDE_CHAT_IDS |
- | B | Exclude specific groups |
GROUPS_INCLUDE_CHAT_IDS |
- | B | Force-include specific groups |
CHANNELS_EXCLUDE_CHAT_IDS |
- | B | Exclude specific channels |
CHANNELS_INCLUDE_CHAT_IDS |
- | B | Force-include specific channels |
| Real-time Listener | See Real-time Listener below | ||
ENABLE_LISTENER |
false |
B | Master switch — enables all LISTEN_* features below |
LISTEN_EDITS |
true |
B | Apply text edits in real-time |
LISTEN_DELETIONS |
true |
B | Mirror deletions (protected by mass operation rate limiting) |
LISTEN_NEW_MESSAGES |
true |
B | Save new messages in real-time between scheduled backups |
LISTEN_NEW_MESSAGES_MEDIA |
false |
B | Also download media immediately (vs. next scheduled backup) |
LISTEN_CHAT_ACTIONS |
true |
B | Track chat photo, title, and member changes |
MASS_OPERATION_THRESHOLD |
10 |
B | Max operations per chat before rate limiting triggers |
MASS_OPERATION_WINDOW_SECONDS |
30 |
B | Sliding window for counting operations (seconds) |
MASS_OPERATION_BUFFER_DELAY |
2.0 |
B | Seconds to buffer operations before applying |
| Database | See Database Configuration below | ||
DATABASE_URL |
- | B/V | Full database URL (highest priority, overrides all below) |
DB_TYPE |
sqlite |
B/V | Database engine: sqlite or postgresql |
DB_PATH |
$BACKUP_PATH/telegram_backup.db |
B/V | Path to SQLite database file |
DATABASE_PATH |
- | B/V | Full path to SQLite file (v2 compatible alias for DB_PATH) |
DATABASE_DIR |
- | B/V | Directory containing telegram_backup.db (v2 compatible) |
POSTGRES_HOST |
localhost |
B/V | PostgreSQL host |
POSTGRES_PORT |
5432 |
B/V | PostgreSQL port |
POSTGRES_USER |
telegram |
B/V | PostgreSQL username |
POSTGRES_PASSWORD |
- | B/V | PostgreSQL password (required when using PostgreSQL) |
POSTGRES_DB |
telegram_backup |
B/V | PostgreSQL database name |
| Viewer & Authentication | |||
VIEWER_USERNAME |
- | V | Web viewer username (both username and password required to enable auth) |
VIEWER_PASSWORD |
- | V | Web viewer password |
AUTH_SESSION_DAYS |
30 |
V | Days before re-authentication is required |
DISPLAY_CHAT_IDS |
- | V | Restrict viewer to specific chats (comma-separated IDs) |
VIEWER_TIMEZONE |
Europe/Madrid |
V | Timezone for displayed timestamps (tz database names) |
SHOW_STATS |
true |
V | Show backup statistics dropdown in viewer header |
| Security | |||
CORS_ORIGINS |
* |
V | Allowed CORS origins, comma-separated (e.g., https://my.domain.com). Credentials auto-disabled when * |
SECURE_COOKIES |
auto |
V | Secure flag on auth cookies. Auto-detects from request protocol (X-Forwarded-Proto / scheme). Override with true or false |
| Notifications | |||
PUSH_NOTIFICATIONS |
basic |
V | off = disabled, basic = in-browser only, full = Web Push (works with browser closed) |
VAPID_PRIVATE_KEY |
auto-generated | V | Custom VAPID private key for Web Push |
VAPID_PUBLIC_KEY |
auto-generated | V | Custom VAPID public key for Web Push |
VAPID_CONTACT |
mailto:admin@example.com |
V | Contact email included in Web Push requests |
There are two modes for selecting which chats to backup:
Mode 1 — Whitelist (simple): set CHAT_IDS to backup only those specific chats. All other filtering variables are ignored.
CHAT_IDS=-1001234567890,-1009876543210 # Only these 2 chats, nothing elseMode 2 — Type-based (default): use CHAT_TYPES to backup all chats of certain types, then fine-tune with include/exclude lists:
# Backup all private chats and groups (no channels)
CHAT_TYPES=private,groups
# Backup all channels except one
CHAT_TYPES=channels
CHANNELS_EXCLUDE_CHAT_IDS=-1001234567890
# Backup all groups plus one specific channel
CHAT_TYPES=groups
CHANNELS_INCLUDE_CHAT_IDS=-1001234567890
*_INCLUDE_*variables are additive — they add chats to whatCHAT_TYPESalready selects. For exclusive selection, useCHAT_IDSinstead.
Chat ID format — Telegram uses "marked" IDs:
- Users: positive numbers (
123456789) - Basic groups: negative (
-123456789) - Supergroups/Channels: negative with
-100prefix (-1001234567890)
Find a chat's ID by forwarding a message to @userinfobot.
The scheduled backup only captures new messages. To also track edits and deletions between backups, enable the real-time listener:
ENABLE_LISTENER: "true" # Master switch — required
LISTEN_EDITS: "true" # Track text edits (safe, default: true)
LISTEN_DELETIONS: "true" # Mirror deletions (default: true, protected by rate limiting)
LISTEN_NEW_MESSAGES: "true" # Save new messages instantly (default: true)How it works: stays connected to Telegram between scheduled backups, captures changes as they happen, and automatically reconnects if disconnected.
Backup protection: when LISTEN_DELETIONS=true, deletions are protected by the mass operation rate limiter. Set LISTEN_DELETIONS=false to never delete anything from your backup.
Alternative — batch sync: set SYNC_DELETIONS_EDITS=true to check ALL backed-up messages on each scheduled run. This is expensive and slow — only use for a one-time catch-up, then switch to the real-time listener.
When the listener is enabled and LISTEN_DELETIONS=true, a sliding-window rate limiter protects against mass deletion attacks:
- Operations are buffered for
MASS_OPERATION_BUFFER_DELAYseconds before being applied - A sliding window tracks operations per chat over
MASS_OPERATION_WINDOW_SECONDS - When
MASS_OPERATION_THRESHOLDis exceeded, the entire buffer is discarded — zero changes written
Example: someone deletes 50 messages in 10 seconds with default settings (threshold=10, window=30s) — the first 10 are applied, remaining 40 are blocked. For zero deletions from your backup, set LISTEN_DELETIONS=false.
Telegram Archive supports SQLite (default, zero-config) and PostgreSQL (better for large deployments with real-time LISTEN/NOTIFY).
Viewer shows no data? Both backup and viewer containers must access the same database. Ensure
DB_TYPEandDB_PATH(orDATABASE_URL) match in both services.
SQLite path resolution (highest priority first): DATABASE_URL → DATABASE_PATH → DATABASE_DIR → DB_PATH → $BACKUP_PATH/telegram_backup.db
Using PostgreSQL:
- Uncomment the
postgresservice indocker-compose.yml - Set
DB_TYPE=postgresqlandPOSTGRES_PASSWORDin your.env - Uncomment
depends_onin both backup and viewer services - Run
docker compose up -d
If you're using the default docker-compose.yml with images from Docker Hub:
# Pull latest images and recreate containers
docker compose pull
docker compose up -dOr in one command:
docker compose up -d --pull alwaysNote: Running
git pullonly updates source code, not Docker images. You must usedocker compose pullto get new container versions.
If you've modified the code or prefer building locally:
git pull
docker build -t drumsergio/telegram-archive:latest .
docker build -t drumsergio/telegram-archive-viewer:latest -f Dockerfile.viewer .
docker compose up -dFor production stability, pin to specific versions instead of latest:
services:
telegram-backup:
image: drumsergio/telegram-archive:v5.3.7 # Pin to specific versionCheck Releases for available versions.
For major version upgrades with breaking changes and migration scripts, see docs/CHANGELOG.md.
Install the package in editable mode to get the telegram-archive command:
# Install in editable mode
pip install -e .
# Now telegram-archive is available system-wide
telegram-archive --help
telegram-archive --data-dir ./data list-chats
telegram-archive --data-dir ./data stats
telegram-archive --data-dir ./data backup
# Export to JSON
telegram-archive --data-dir ./data export -o backup.json -s 2024-01-01 -e 2024-12-31For development without installing, use the telegram-archive executable script:
# Show all available commands
./telegram-archive --help
# Use custom data directory (instead of /data)
./telegram-archive --data-dir ./data list-chats
./telegram-archive --data-dir ./data stats
./telegram-archive --data-dir ./data backup
# Or symlink to PATH for easier access
sudo ln -s $(pwd)/telegram-archive /usr/local/bin/telegram-archive
telegram-archive --data-dir ./data list-chatsAll commands use the unified python -m src interface inside containers:
# Show all available commands
docker compose exec telegram-backup python -m src --help
# View statistics
docker compose exec telegram-backup python -m src stats
# List chats
docker compose exec telegram-backup python -m src list-chats
# Export to JSON
docker compose exec telegram-backup python -m src export -o backup.json
# Export date range
docker compose exec telegram-backup python -m src export -o backup.json -s 2024-01-01 -e 2024-12-31
# Manual backup run (one-time)
docker compose exec telegram-backup python -m src backup
# Re-authenticate (if session expires)
docker compose exec -it telegram-backup python -m src authdata/
├── session/
│ └── telegram_backup.session
└── backups/
├── telegram_backup.db
└── media/
└── {chat_id}/
└── {files}
| Problem | Solution |
|---|---|
| "Failed to authorize" | Run ./init_auth.sh again |
| "Permission denied" | chmod -R 755 data/ |
| Media files missing/corrupted | Set VERIFY_MEDIA=true to re-download them |
| Backup interrupted | Set VERIFY_MEDIA=true once to recover missing files |
| "duplicate key value violates unique constraint reactions_pkey" | See Reactions Sequence Fix below |
If you see this error during backup:
duplicate key value violates unique constraint "reactions_pkey"
DETAIL: Key (id)=(XXXX) already exists
Cause: The PostgreSQL sequence for reactions.id got out of sync with the actual data. This commonly occurs after database restores or migrations.
Solutions:
-
Upgrade to v4.1.2+ (recommended) - The code automatically detects and recovers from this issue.
-
Manual fix - Run this SQL command:
docker exec -i <postgres-container> psql -U telegram -d telegram_backup -c \ "SELECT setval('reactions_id_seq', COALESCE((SELECT MAX(id) FROM reactions), 0) + 1, false);"
Or use the provided script:
curl -O https://raw.githubusercontent.com/GeiserX/Telegram-Archive/master/scripts/fix_reactions_sequence.sql docker exec -i <postgres-container> psql -U telegram -d telegram_backup < fix_reactions_sequence.sql
- Secret chats not supported (API limitation)
- Edit history not tracked (only latest version stored; enable
ENABLE_LISTENER=trueto track edits in real-time) - Deleted messages before first backup cannot be recovered
GPL-3.0. See LICENSE for details.
Built with Telethon.


