Documentation
Everything you need to install, configure, and run Tikka Masala: from environment variables to automatic backups and notifications.
Overview
Cloudflare Tunnel configurations live only in the Cloudflare dashboard; there's no built-in way to snapshot one before a risky change, or to recover it if it gets misconfigured or deleted. Tikka Masala was built to close that gap: a small, self-hosted tool that talks to the Cloudflare API on your behalf, keeps JSON snapshots of tunnel configurations on disk, and can push a saved snapshot back to a tunnel when you need to roll back.
Day to day, its job is to sit quietly in the background: verify your API token, list the tunnels in an account, take manual or scheduled backups, and notify you over webhook or Telegram when something needs attention. Everything is kept local: SQLite for metadata and history, JSON files for the actual snapshots, so you stay in control of where your tunnel configurations are stored.
Requirements
- Docker and Docker Compose, or Python 3.12 to run the app directly
- A Cloudflare API token with permissions suitable for Cloudflare Tunnel configuration changes
Recommended token permissions:
Account: Cloudflare Tunnel -> EditZone: DNS -> Edit
For backup-only usage, the token can later be reduced to read-only permissions if your workflow allows it.
Environment variables
Configure the app through environment variables, whether you use Docker Compose, docker run, Portainer, or a direct shell session. Start from .env.sample.
| Variable | Required | Description |
|---|---|---|
CLOUDFLARE_ACCOUNT_ID | No | Prefills the Cloudflare account ID in the UI. |
CLOUDFLARE_API_TOKEN | No | Prefills the API token in the UI. |
TOKEN_ENCRYPTION_KEY | Recommended | Fernet key used to encrypt the API token before saving it in SQLite. |
AUTO_BACKUP_TIMEZONE | No | Force automatic backups to use a specific IANA timezone such as Europe/Rome. |
BACKUP_RETENTION_DAYS | No | Automatically delete backups older than this many days after new backups are created. |
NOTIFICATION_WEBHOOK_URL | No | Send JSON notifications to a generic webhook endpoint. |
NOTIFICATION_WEBHOOK_EVENTS | No | Comma-separated list of notification events to emit to the webhook. |
TELEGRAM_BOT_TOKEN | No | Telegram bot token used to send notifications through the Bot API. |
TELEGRAM_CHAT_ID | No | Telegram chat, group, or channel ID where notifications should be sent. |
TELEGRAM_NOTIFICATION_EVENTS | No | Comma-separated list of notification events to emit to Telegram. |
DATA_DIR | No | Storage path for the SQLite database and JSON backups. Default: /data. |
REQUEST_TIMEOUT | No | Outbound Cloudflare API timeout in seconds. Default: 20. |
LOG_LEVEL | No | Application log level written to container stdout. Default: INFO. |
CLOUDFLARE_API_BASE | No | Override for the Cloudflare API base URL. Default: https://api.cloudflare.com/client/v4. |
DEMO | No | Enable demo mode. Defaults to false. Disables persistence, automatic backups, notifications, and the backup archive. |
Generate a Fernet key for TOKEN_ENCRYPTION_KEY:
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Running the app
docker pull gfsolone/tikkamasala:latestfrom Docker Hubdocker pull ghcr.io/gioxx/tikkamasala:latestfrom GitHub Container Registry
Docker Compose
The included docker-compose.yml builds the image locally and mounts ./data into the container so backups and settings survive restarts.
docker compose up -d --build
Then open http://localhost:8080.
Docker only
docker build -t tikkamasala .
docker run -d \
--name tikkamasala \
--restart unless-stopped \
--env-file .env \
-p 8080:8080 \
-v "$(pwd)/data:/data" \
tikkamasala
Without Docker
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
set -a
source .env
set +a
uvicorn app.main:app --host 0.0.0.0 --port 8080
Portainer
Portainer stacks pull an image rather than building from a local Dockerfile, so swap build: . for one of the prebuilt images above. From Stacks > Add stack, paste:
services:
tikkamasala:
image: gfsolone/tikkamasala:latest
container_name: tikkamasala
restart: unless-stopped
environment:
CLOUDFLARE_ACCOUNT_ID: ${CLOUDFLARE_ACCOUNT_ID:-}
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN:-}
TOKEN_ENCRYPTION_KEY: ${TOKEN_ENCRYPTION_KEY:-}
AUTO_BACKUP_TIMEZONE: ${AUTO_BACKUP_TIMEZONE:-}
BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-90}
DEMO: ${DEMO:-false}
ports:
- "8080:8080"
volumes:
- tikkamasala_data:/data
volumes:
tikkamasala_data:
Use ghcr.io/gioxx/tikkamasala:latest instead of the Docker Hub image if you prefer GHCR. Set the actual values either in the stack's Environment variables panel (Portainer prompts for anything left as ${VAR} without a default) or by editing the environment: block directly; add any other variable from the environment variables table the same way. A named volume (tikkamasala_data) is used here instead of a host path because Portainer-managed hosts don't always expose a predictable working directory; switch it to a bind mount (e.g. /opt/tikkamasala/data:/data) if you'd rather keep backups on a known host path.
Prefill & persistence
The UI can prefill authentication fields from three sources: environment variables, saved values in the local database, and a browser cookie for the API token.
Effective priority:
- Account ID: database, then environment
- API token: browser cookie, then database, then environment
Automatic backups use only server-side credentials: database or environment. Browser cookies are never used by the scheduler.
After a successful token verification or API action: the Account ID is saved in ./data/app.db, the API token is stored in a browser cookie, and it is also saved in ./data/app.db only if TOKEN_ENCRYPTION_KEY is configured.
CLOUDFLARE_ACCOUNT_ID or CLOUDFLARE_API_TOKEN are still present in the environment, the UI keeps showing those values as prefilled.
Storage layout
Everything is stored under DATA_DIR, which defaults to /data in the container and typically maps to ./data on the host.
app.db: SQLite database for backup metadata, restore history, and saved auth settingsbackups/*.json: exported tunnel configuration snapshots
What gets backed up
Tikka Masala saves the tunnel configuration returned by the Cloudflare Tunnel configuration endpoints, along with backup metadata: export timestamp, account ID, tunnel ID, tunnel name, and notes. It does not back up arbitrary account-wide Cloudflare settings.
Restore behavior
Restoring a backup replaces the current remote configuration of the selected tunnel with the configuration stored in the snapshot.
The backup detail page keeps a restore history so you can see: when a backup was restored, which account and tunnel it was restored to, and whether the restore targeted the original tunnel, a different tunnel, or a different account.
Automatic backups
The scheduler is built into the app and stores its configuration in the local SQLite database. From the home page you can enable or disable automatic backups, set a cron expression, run the job immediately with Run now, and see the last run, next run, and recent execution history.
By default, automatic backups use the timezone detected from the browser when you save the schedule. Set AUTO_BACKUP_TIMEZONE for a fixed server-side timezone for all users.
Advanced schedule
The Advanced schedule page (/auto-backup/tunnel-filters) controls backup scope and per-tunnel frequency.
Backup mode:
- All tunnels (default): every tunnel in the account is backed up on each run, with optional per-tunnel frequency throttling.
- Selected tunnels only: only the tunnels you explicitly check are included in each run.
Frequency overrides (per tunnel):
- Every run: backed up on every scheduled or manual run (default).
- Once per week: skipped if a backup for that tunnel already exists within the last 7 days.
- Once per month: skipped if a backup for that tunnel already exists within the last 30 days.
This lets you run the scheduler daily while backing up low-priority tunnels only once a week.
BACKUP_RETENTION_DAYS is set, old backup files and related database records (including restore history) are deleted automatically after new backups are created.
Notifications
Tikka Masala can send server-side notifications through a generic webhook and/or Telegram. Set NOTIFICATION_WEBHOOK_URL / NOTIFICATION_WEBHOOK_EVENTS and/or TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID / TELEGRAM_NOTIFICATION_EVENTS.
Supported events: notification_test, manual_backup_success, manual_backup_failed, auto_backup_success, auto_backup_partial, auto_backup_failed, restore_success, restore_failed, retention_cleanup.
If the events variable is left empty for a channel, Tikka Masala enables the default set: auto_backup_success, auto_backup_partial, auto_backup_failed, restore_failed, retention_cleanup.
Webhook notifications send a JSON payload with app name and version, event name, message, timestamp, and event-specific details. Telegram sends the same information as a formatted plain-text message. Use Send test notification from the UI to verify delivery.
Customizing notification messages
Each event has a built-in default message. Override it from the Customize notification messages page (/notifications/messages). Messages support {placeholder} tokens replaced at runtime. {version} and {timestamp} are always available.
| Event | Available tokens |
|---|---|
notification_test | (none specific) |
manual_backup_success | {backup_id}, {account_id}, {tunnel_id}, {tunnel_name}, {route_count} |
manual_backup_failed | {account_id}, {tunnel_id}, {error} |
auto_backup_success / _partial / _failed | {trigger}, {account_id}, {tunnel_count}, {backup_count}, {error_count}, {skipped_count}, {processed_count}, {backed_up_tunnels}, {skipped_tunnels} |
restore_success | {backup_id}, {account_id}, {tunnel_id} |
restore_failed | {backup_id}, {account_id}, {tunnel_id}, {error} |
retention_cleanup | {deleted_count}, {retention_days}, {cutoff} |
For auto_backup_* events: {tunnel_count} is every tunnel discovered on the account, {processed_count} is backup_count + error_count, {skipped_count} is tunnels skipped by scheduling rules, and {backed_up_tunnels} / {skipped_tunnels} are comma-separated tunnel name lists (or - if none).
Leaving a field empty restores the built-in default for that event. Unknown tokens are left as-is.
Demo mode
Set DEMO=true to exercise the UI without persisting auth data or enabling server-side automation. In demo mode: authentication data is not stored in SQLite or the browser cookie, automatic backups are disabled, notifications are disabled, and the backup archive is disabled. Manual tunnel listing and manual backup downloads still work.
Security notes
- The API token is never persisted in plaintext in the database by current versions of the app.
- If
TOKEN_ENCRYPTION_KEYis missing, the token is not written to SQLite. - The API token can still be temporarily reused through the browser cookie or environment prefill.
- Older plaintext tokens already present in the database are upgraded to encrypted form the first time they are read successfully with a valid
TOKEN_ENCRYPTION_KEY.
Tikka Masala