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)
# Make script executable (Linux/Mac)
chmod +x init_auth.sh
# 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.setup_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.setup_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
| Variable | Description |
|---|---|
TELEGRAM_API_ID |
API ID from my.telegram.org |
TELEGRAM_API_HASH |
API Hash from my.telegram.org |
TELEGRAM_PHONE |
Phone with country code (+1234567890) |
| Variable | Default | Description |
|---|---|---|
SCHEDULE |
0 */6 * * * |
Cron schedule (every 6 hours) |
BACKUP_PATH |
/data/backups |
Backup storage path |
DATABASE_DIR |
Same as backup | Database location |
DOWNLOAD_MEDIA |
true |
Download media files |
MAX_MEDIA_SIZE_MB |
100 |
Max media file size |
CHAT_TYPES |
private,groups,channels |
Types to backup |
LOG_LEVEL |
INFO |
Logging level (DEBUG, INFO, WARNING/WARN, ERROR) |
BATCH_SIZE |
100 |
Messages per batch during backup |
DATABASE_TIMEOUT |
60.0 |
Database operation timeout (seconds) |
SESSION_NAME |
telegram_backup |
Telethon session file name |
| Variable | Default | Description |
|---|---|---|
VIEWER_USERNAME |
- | Web viewer username |
VIEWER_PASSWORD |
- | Web viewer password |
AUTH_SESSION_DAYS |
30 |
Days before re-authentication required |
DISPLAY_CHAT_IDS |
- | Restrict viewer to specific chats |
VIEWER_TIMEZONE |
Europe/Madrid |
Timezone for displayed timestamps |
SHOW_STATS |
true |
Show backup stats dropdown in header |
| Variable | Default | Description |
|---|---|---|
ENABLE_LISTENER |
false |
Real-time listener for edits/deletions |
LISTEN_EDITS |
true |
Apply text edits when listener is on |
LISTEN_DELETIONS |
true |
|
LISTEN_NEW_MESSAGES |
true |
Save new messages in real-time |
LISTEN_NEW_MESSAGES_MEDIA |
false |
Download media immediately (vs scheduled backup) |
LISTEN_CHAT_ACTIONS |
true |
Track chat photo/title changes |
LISTEN_ALBUMS |
true |
Group album uploads together |
PRIORITY_CHAT_IDS |
- | Process these chats FIRST in all operations |
| Variable | Default | Description |
|---|---|---|
MASS_OPERATION_THRESHOLD |
10 |
π‘οΈ Operations before rate limiting triggers |
MASS_OPERATION_WINDOW_SECONDS |
30 |
Time window for counting operations |
| Variable | Default | Description |
|---|---|---|
PUSH_NOTIFICATIONS |
basic |
Notification mode: off, basic, full |
VAPID_PRIVATE_KEY |
- | Custom VAPID private key (auto-generated if empty) |
VAPID_PUBLIC_KEY |
- | Custom VAPID public key (auto-generated if empty) |
VAPID_CONTACT |
mailto:admin@example.com |
VAPID contact email |
Push notification modes:
off- No notificationsbasic- In-browser only (tab must be open)full- Web Push (works even when browser closed)
| Variable | Default | Description |
|---|---|---|
DEDUPLICATE_MEDIA |
true |
Use symlinks to deduplicate identical media |
SYNC_DELETIONS_EDITS |
false |
Batch-check ALL messages for edits/deletions (expensive!) |
VERIFY_MEDIA |
false |
Re-download missing/corrupted media files |
STATS_CALCULATION_HOUR |
3 |
Hour (0-23) to recalculate backup stats |
| Variable | Default | Description |
|---|---|---|
GLOBAL_INCLUDE_CHAT_IDS |
- | Whitelist chats globally (any type) |
GLOBAL_EXCLUDE_CHAT_IDS |
- | Blacklist chats globally (any type) |
PRIVATE_INCLUDE_CHAT_IDS |
- | Whitelist private/DM chats only |
PRIVATE_EXCLUDE_CHAT_IDS |
- | Blacklist private/DM chats only |
GROUPS_INCLUDE_CHAT_IDS |
- | Whitelist group chats only |
GROUPS_EXCLUDE_CHAT_IDS |
- | Blacklist group chats only |
CHANNELS_INCLUDE_CHAT_IDS |
- | Whitelist channels only |
CHANNELS_EXCLUDE_CHAT_IDS |
- | Blacklist channels only |
How filtering works (priority order):
GLOBAL_EXCLUDEβ Skip (blacklist wins)- Type-specific exclude β Skip
GLOBAL_INCLUDEβ Backup (works for ANY chat type)- Type-specific include β Backup (only for that type)
CHAT_TYPESfilter β Backup if type matches
GLOBAL vs Type-specific: GLOBAL_INCLUDE_CHAT_IDS backs up a chat regardless of its type. Type-specific includes (e.g., PRIVATE_INCLUDE_CHAT_IDS) only apply to chats of that type. The chat type is auto-detected from Telegram.
Chat IDs use Telegram's "marked" format:
- Users: Positive numbers (e.g.,
123456789) - Basic groups: Negative numbers (e.g.,
-123456789) - Supergroups/Channels: Negative with
-100prefix (e.g.,-1001234567890)
Finding Chat IDs: Forward a message from the chat to @userinfobot on Telegram.
Whitelist-only mode: Set CHAT_TYPES= (empty) to backup ONLY explicitly included chat IDs:
- CHAT_TYPES= # Empty = no types by default
- GLOBAL_INCLUDE_CHAT_IDS=-1001234567 # Only backup this specific chatBy default, the backup runs on a schedule and only captures new messages. Edits and deletions made between backups are not tracked. You have two options:
Enable ENABLE_LISTENER=true to run a background listener that catches edits as they happen:
- ENABLE_LISTENER=true # Enable real-time listener
- LISTEN_EDITS=true # Apply text edits (default: true, safe)
- LISTEN_DELETIONS=true # Delete from backup (protected by zero-footprint mass operation detection)How it works:
- Stays connected to Telegram between scheduled backups
- Instantly captures message edits (and optionally deletions)
- Very efficient - only processes actual changes
- Automatically restarts if disconnected
By default, LISTEN_DELETIONS=true - deletions are synced but protected by rate limiting. If a mass deletion is detected (>10 deletions in 30s), the first 10 are applied but the remaining are blocked. Set to false if you want to keep ALL messages even when deleted on Telegram.
When LISTEN_DELETIONS=true, a sliding-window rate limiter protects against mass deletion attacks:
- MASS_OPERATION_THRESHOLD=10 # Max ops before rate limiting (default: 10)
- MASS_OPERATION_WINDOW_SECONDS=30 # Time window for counting (default: 30s)- Operations apply immediately - Normal usage (deleting a few messages) works instantly
- Sliding window - System tracks operations per chat in a time window
- Rate limiting - When threshold exceeded, chat is blocked for remainder of window
- First N applied - The first 10 operations ARE applied, remaining are blocked
Someone deletes 50 messages in 10 seconds:
π‘οΈ RATE LIMIT TRIGGERED
Chat: -1001234567890
Operations in 30s: 11 (max: 10)
First 10 were applied, remaining blocked
Chat blocked until: 2026-01-18 12:35:00
Result: First 10 deletions were applied, but the remaining 40 were blocked. Most of your backup is preserved.
For zero deletions from your backup, disable deletion sync entirely:
- LISTEN_DELETIONS=false # Deletions never affect your backupEnable SYNC_DELETIONS_EDITS=true to re-check ALL backed-up messages on each backup run:
- SYNC_DELETIONS_EDITS=true- One-time initial catch-up on edits/deletions
- If you can't use the real-time listener
After running once, switch back to ENABLE_LISTENER=true for ongoing sync.
Telegram Archive supports both SQLite and PostgreSQL.
β οΈ Viewer shows no data? Both backup and viewer containers must access the same database. If using SQLite, addDB_PATHto BOTH services in docker-compose.yml:environment: DB_TYPE: sqlite DB_PATH: /data/backups/telegram_backup.db
SQLite Path Resolution (in priority order):
| Variable | Description |
|---|---|
DATABASE_URL |
Full database URL (highest priority) |
DATABASE_PATH |
Full path to SQLite file (v2 compatible) |
DATABASE_DIR |
Directory for telegram_backup.db (v2 compatible) |
DB_PATH |
Full path to SQLite file (v3 style) |
| Default | $BACKUP_PATH/telegram_backup.db |
PostgreSQL Configuration:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
- | Full PostgreSQL URL (takes priority) |
DB_TYPE |
sqlite |
Set to postgresql to use PostgreSQL |
POSTGRES_HOST |
localhost |
PostgreSQL host |
POSTGRES_PORT |
5432 |
PostgreSQL port |
POSTGRES_USER |
telegram |
PostgreSQL username |
POSTGRES_PASSWORD |
- | PostgreSQL password (required) |
POSTGRES_DB |
telegram_backup |
PostgreSQL database name |
Using PostgreSQL:
- Uncomment the
postgresservice indocker-compose.yml - Set
POSTGRES_PASSWORDin your.env - Set
DB_TYPE=postgresqlin your.env - Uncomment
depends_onin backup and viewer services - Run
docker-compose up -d
For detailed upgrade instructions, breaking changes, and migration scripts, see docs/CHANGELOG.md.
# View statistics
docker-compose exec telegram-backup python -m src.export_backup stats
# List chats
docker-compose exec telegram-backup python -m src.export_backup list-chats
# Export to JSON
docker-compose exec telegram-backup python -m src.export_backup export -o backup.json
# Export date range
docker-compose exec telegram-backup python -m src.export_backup export -o backup.json -s 2024-01-01 -e 2024-12-31
# Manual backup run
docker-compose exec telegram-backup python -m src.telegram_backupdata/
βββ 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.


