A lightweight, self-hosted web TV guide for one or more XMLTV sources. It provides schedules only: no IPTV player, M3U support, recording, or video streaming.
- Live programmes with progress, remaining time, and upcoming shows.
- Tonight and late-evening views with configurable time windows.
- Proportional timeline grid, date navigation, zoom, and responsive controls.
- Fast SQLite FTS5 search across titles, subtitles, and descriptions.
- XMLTV sources by URL or local file, with atomic imports and smart scheduling.
- Channel ordering, custom names/logos, portable configuration import/export.
- Light, dark, and system themes.
- English and French UI, selected from the saved preference or browser language.
- Multi-platform image for
linux/amd64andlinux/arm64.
Supported source formats: XML, GZIP, ZIP, 7-Zip, XZ, and BZIP2. Formats are detected from file contents.
services:
tvguide:
image: cerede2000/tvguide:latest
container_name: tvguide
restart: unless-stopped
ports:
- "8080:8080"
environment:
TZ: Europe/Paris
ADMIN_PASSWORD: change-this-password
XMLTV_SYNC_WORKERS: 1
MALLOC_ARENA_MAX: 2
volumes:
- ./data:/data
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp:size=16m,mode=1777mkdir -p data/imports
chown -R 1000:1000 data
docker compose up -dOpen http://localhost:8080, then go to Administration → XMLTV sources → Add source. The first import starts immediately.
The same image is also available from ghcr.io/cerede2000/tvguide:latest.
mkdir -p tvguide/data/imports
chown -R 1000:1000 tvguide/data
docker run -d --name tvguide \
-p 8080:8080 \
-e TZ=Europe/Paris \
-e ADMIN_PASSWORD=change-this-password \
-v "$(pwd)/tvguide/data:/data" \
--restart unless-stopped \
--security-opt no-new-privileges:true \
--read-only --tmpfs /tmp:size=16m,mode=1777 \
cerede2000/tvguide:latestThe container runs as UID/GID 1000. Ensure that the bind-mounted data directory is writable by that user.
| Variable | Default | Description |
|---|---|---|
TZ |
Europe/Paris |
Default display timezone and fallback timezone for XMLTV timestamps without an offset. |
APP_PORT |
8080 |
Host port used by the supplied Compose files. The container listens on 8080. |
DATA_DIR |
/data |
Persistent directory inside the container. |
LOG_LEVEL |
INFO |
DEBUG, INFO, WARNING, or ERROR. |
ADMIN_PASSWORD |
empty | Protects administration when set. Strongly recommended outside a trusted LAN. |
ALLOW_PRIVATE_XMLTV_SOURCES |
false |
Allows source URLs resolving to private or loopback addresses. |
XMLTV_MAX_DOWNLOAD_MB |
100 |
Maximum compressed or raw download size. |
XMLTV_MAX_UNCOMPRESSED_MB |
500 |
Maximum decompressed size. |
XMLTV_HTTP_TIMEOUT_SECONDS |
30 |
HTTP timeout for XMLTV downloads. |
XMLTV_DOWNLOAD_RETRIES |
3 |
Download retry count. |
XMLTV_SYNC_WORKERS |
1 |
Maximum concurrent imports. Keep 1 with SQLite for lower RAM/CPU use. |
XMLTV_USER_AGENT |
tvguide/1.0 |
User-Agent sent to XMLTV servers. |
MALLOC_ARENA_MAX |
2 |
Limits native allocator arenas so import peaks are returned more readily. |
FORWARDED_ALLOW_IPS |
* |
Reverse-proxy IPs allowed to provide forwarded headers. |
Display settings such as theme, default view, timezone, time format, and evening windows are stored in the database and managed from the application.
Copy a file below the data volume and use a path relative to /data:
cp guide.xml.gz ./data/imports/Then configure a local source with imports/guide.xml.gz. Paths outside /data are rejected.
docker compose pull
docker compose up -d
docker compose logs -f tvguideDatabase migrations run automatically at startup. Back up data/app.db before an upgrade:
docker compose exec tvguide python -c \
"import sqlite3; sqlite3.connect('/data/app.db').execute(\"VACUUM INTO '/data/backup.db'\")"Versions from 1.16.1 onward create an FTS5 index automatically. Allow free disk space roughly equal to the database size during its first migration. The WAL is checkpointed and truncated after migrations and successful imports.
git clone https://github.com/cerede2000/tvguide.git
cd tvguide
cp .env.example .env
mkdir -p data/imports
chown -R 1000:1000 data
docker compose up -d --buildRun the test suites:
cd backend && uv sync --frozen && uv run pytest -q
cd ../frontend && npm ci && npm test && npm run buildThe application serves plain HTTP on port 8080 and supports X-Forwarded-* headers. Use HTTPS at the reverse proxy and set a strong ADMIN_PASSWORD when the service is reachable from the Internet.
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}Public schedule endpoints remain accessible; administration endpoints require a bearer token when a password is configured. Login attempts are rate-limited.
All persistent state is under /data:
/data/app.db
/data/imports/
/data/config/
/data/tmp/
Health endpoint: GET /api/health. Container logs are written to stdout/stderr.