Docker Deployment

February 17, 2026 · View on GitHub

Run the tvh-guide-ng EPG guide with Docker Compose — no Node.js installation required.

Quick Start

cd docker

# Create your environment file
cp .env.example .env
# Edit .env — at minimum set TVH_URL to your TVHeadend instance

# Build and start
docker compose up -d

# View logs
docker compose logs -f

The web UI will be available at http://localhost:8080 and the EPG Cache API at http://localhost:3100.

Building Images

Using the build script

# From the repository root:
./docker/build.sh                              # Build all images (current arch)
./docker/build.sh --tag v1.0.0                 # Build with a specific tag
./docker/build.sh --service web                # Build only the web image
./docker/build.sh --service epg-cache          # Build only the epg-cache image

Build script options

FlagDescriptionDefault
--tag TAGDocker image taglatest
--service NAMEBuild only web or epg-cacheboth
--registry REGISTRYRegistry prefix (e.g. ghcr.io/codingfragments)local only
--epg-cache-url URLPUBLIC_EPG_CACHE_URL baked into the web buildhttp://epg-cache:3000
--platform PLATFORMSTarget platforms (all, or e.g. linux/amd64,linux/arm64)current arch
--pushPush images to the registryoff

Multi-architecture builds

Build images for both ARM (Apple Silicon, Raspberry Pi) and Intel/AMD simultaneously:

# Build for both architectures (images stay local in buildx cache)
./docker/build.sh --platform all

# Build for a single target architecture
./docker/build.sh --platform linux/amd64       # Intel/AMD only
./docker/build.sh --platform linux/arm64       # ARM only

The script automatically creates a Docker Buildx builder with QEMU emulation support on first use. Multi-platform images require either --push to a registry or will be kept in the buildx cache (not loaded into the local Docker image list).

Pushing to a container registry

Use --registry to prefix image names with a registry path, and --push to upload:

# GitHub Container Registry (ghcr.io)
docker login ghcr.io -u YOUR_USERNAME
./docker/build.sh --registry ghcr.io/codingfragments --platform all --push --tag v1.0.0

# Docker Hub
docker login
./docker/build.sh --registry docker.io/myuser --push --tag v1.0.0

This produces images like ghcr.io/codingfragments/tvh-guide-web:v1.0.0 with manifests for both linux/amd64 and linux/arm64.

Without --registry, images are tagged locally as tvh-guide-web and tvh-guide-epg-cache.

Using Docker Compose

cd docker
docker compose build

Using pre-built images

If images have been pushed to a registry, you can skip building and pull them directly. Set the IMAGE_REGISTRY and IMAGE_TAG variables in your .env:

IMAGE_REGISTRY=ghcr.io/codingfragments/
IMAGE_TAG=v1.0.0

Then use docker compose pull && docker compose up -d (no build step needed).

Configuration Reference

EPG Cache Service

VariableRequiredDefaultDescription
TVH_URLYesTVHeadend instance URL (e.g. http://192.168.1.10:9981)
TVH_USERNAMENo""TVHeadend username
TVH_PASSWORDNo""TVHeadend password
EPG_REFRESH_INTERVALNo3600Seconds between EPG syncs from TVHeadend
EPG_SQLITE_PATHNo/data/epg-cache.dbSQLite database path inside the container
PICON_PATHNoPath to picon images inside the container

Web Frontend

VariableRequiredDefaultDescription
PUBLIC_EPG_CACHE_URLNohttp://epg-cache:3000URL the browser uses to reach the EPG Cache API. Build-time only — baked into the SvelteKit build.

Compose-level Variables

VariableDefaultDescription
EPG_CACHE_PORT3100Host port for the EPG Cache API
WEB_PORT8080Host port for the web UI
IMAGE_REGISTRY(empty)Registry prefix with trailing slash (e.g. ghcr.io/codingfragments/)
IMAGE_TAGlatestDocker image tag

Volumes & Persistence

SQLite Data

The EPG Cache stores its database in a named Docker volume (epg-data) mounted at /data inside the container. This persists across container restarts and image updates.

# Inspect the volume
docker volume inspect docker_epg-data

# Back up the database
docker compose exec epg-cache cp /data/epg-cache.db /data/backup.db
docker cp "$(docker compose ps -q epg-cache)":/data/backup.db ./epg-cache-backup.db

Picon Images

To serve channel logos (picons), mount a local directory into the epg-cache container:

# In docker-compose.yml, under epg-cache → volumes:
- /path/to/your/picons:/picons:ro

Then set PICON_PATH=/picons in the epg-cache environment.

Networking

TVHeadend on the same host

If TVHeadend runs directly on the host machine:

TVH_URL=http://host.docker.internal:9981

host.docker.internal works on Docker Desktop (macOS/Windows). On Linux, add extra_hosts: ["host.docker.internal:host-gateway"] to the epg-cache service or use the host's LAN IP.

TVHeadend in another Docker container

If TVHeadend runs in its own Docker Compose stack, connect via an external network:

# In docker-compose.yml:
services:
  epg-cache:
    networks:
      - tvheadend_default

networks:
  tvheadend_default:
    external: true

Then use the TVHeadend container name as the host:

TVH_URL=http://tvheadend:9981

PUBLIC_EPG_CACHE_URL

This URL is used by the browser (not the server) to reach the EPG Cache API. It gets baked into the SvelteKit build at image build time.

  • Same host: http://localhost:3100 (or whatever EPG_CACHE_PORT you set)
  • Remote server: http://your-server-ip:3100
  • Behind a reverse proxy: https://epg-api.example.com

If you change this value, you must rebuild the web image.

Customizing

For a full configuration template with all options documented inline:

cp docker-compose.template.yml docker-compose.yml

You can also create a docker-compose.override.yml for local customizations that won't be overwritten by updates.

Troubleshooting

SQLite Permission Errors

If epg-cache fails with a database permission error, ensure the /data volume is writable:

docker compose exec epg-cache ls -la /data

The container runs as root by default, so this should not typically be an issue.

TVHeadend Connectivity

Verify the EPG Cache can reach TVHeadend:

docker compose exec epg-cache node -e "fetch('$TVH_URL/api/serverinfo').then(r => r.json()).then(console.log)"

Rebuilding After Config Changes

Since PUBLIC_EPG_CACHE_URL is a build-time variable, changing it requires a rebuild:

docker compose build web
docker compose up -d web

Runtime environment variables (like TVH_URL) can be changed with a simple restart:

docker compose up -d