{"id":13523693,"url":"https://github.com/tvheadend/tvheadend","last_synced_at":"2025-05-14T11:08:55.422Z","repository":{"id":1269247,"uuid":"1208360","full_name":"tvheadend/tvheadend","owner":"tvheadend","description":"Tvheadend is the leading TV streaming server for Linux with ATSC, DVB-C/C2, DVB-S/S2, DVB-T/T2, IPTV, SAT\u003eIP and unix pipe input sources","archived":false,"fork":false,"pushed_at":"2025-05-12T14:20:26.000Z","size":63687,"stargazers_count":3033,"open_issues_count":57,"forks_count":925,"subscribers_count":235,"default_branch":"master","last_synced_at":"2025-05-12T15:41:07.933Z","etag":null,"topics":["atsc","dvb","dvb-c","dvb-s","dvb-s2","dvb-t","dvb-t2","dvr","epg","hdhomerun","htsp","iptv","json-api","mpeg-ts","pvr","sat-ip","scheduled-recording","timeshift","transcoding","xmltv"],"latest_commit_sha":null,"homepage":"https://tvheadend.org","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tvheadend.png","metadata":{"files":{"readme":"README.Docker.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":"support/README.pbuilder","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"open_collective":"tvheadend"}},"created_at":"2010-12-30T12:45:11.000Z","updated_at":"2025-05-11T09:35:09.000Z","dependencies_parsed_at":"2023-07-06T20:01:48.970Z","dependency_job_id":"ab83c6e1-a68f-4168-8bba-dc383ff9dfa9","html_url":"https://github.com/tvheadend/tvheadend","commit_stats":{"total_commits":11016,"total_committers":358,"mean_commits":30.77094972067039,"dds":0.5353122730573712,"last_synced_commit":"26ec161fb3c903f8b0d0be8b54d1b67c596fb829"},"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tvheadend%2Ftvheadend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tvheadend%2Ftvheadend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tvheadend%2Ftvheadend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tvheadend%2Ftvheadend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tvheadend","download_url":"https://codeload.github.com/tvheadend/tvheadend/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254071330,"owners_count":22009771,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["atsc","dvb","dvb-c","dvb-s","dvb-s2","dvb-t","dvb-t2","dvr","epg","hdhomerun","htsp","iptv","json-api","mpeg-ts","pvr","sat-ip","scheduled-recording","timeshift","transcoding","xmltv"],"created_at":"2024-08-01T06:01:02.783Z","updated_at":"2025-05-14T11:08:55.390Z","avatar_url":"https://github.com/tvheadend.png","language":"C","readme":"# TVHeadend in Docker\nTVHeadend can be run within a Docker container. This provides isolation from\nother processes by running it in a containerized environment. As this is not\nand in-depth tutorial on docker, those with Docker, containers or cgroups see\n[docker.com][docker].\n\n\n## Building the TVHeadend image\nWhile it is recommended to pull the image from the [GHCR][ghcr] (GitHub\nContainer Registry), it is certainly possible to build the image locally.\n\nTo do so, clone this repository:\n\n$ git clone https://github.com/tvheadend/tvheadend\n\nThen, from within the repository\n\n```sh\ndocker image build\n    --rm \\\n    --tag 'tvheadend:issue-123' \\\n    './'\n```\n\nThe tag 'issue-123' in the example, is just that, an example, anything can be\nused for the tag.\n\n\u003e _Note_: Omitting the tag, will use `latest` by default.\n\n\n## Running the TVHeadend image\nIf the container wasn't built with the above instructions, it can be optionally\nbe pulled from the [GHCR][ghcr] first instead.\n\n```sh\ndocker image pull ghcr.io/tvheadend/tvheadend:latest\n```\n\n\u003e _Note_: The `latest` tag can be replaced with any desired tag, including\n\u003e `master` for the git development version.\n\nRunning the TVHeadend container is then done as follows.\n\n```sh\ndocker container run \\\n    --interactive \\\n    --name 'TVHeadend_container_01' \\\n    --rm \\\n    --tty \\\n    'ghcr.io/tvheadend/tvheadend:issue-123' \\\n    --firstrun\n```\n\n\u003e _Note_: Docker will try to pull a container it doesn't know about yet. So if\n\u003e the container was not previously built or pulled, the `run` sub command\n\u003e will try to pull it instead. Likewise, `--pull always` can be used to force\n\u003e a pull. See the docker documentation for more details.\n\nThe above will now run TVHeadend and the log output should be visible.\n\nIn the snippet above, the `--firstrun` flag was used. This flag is of course\noptional. Please do read the remainder of the next chapter\n[Persistently storing configuration](#Persistently-storing-configuration)\nto learn more about where configuration is stored.\n\n\n## Persistently storing configuration\nContainers do not store files persistently, they are ephemeral by design.\nObviously, storing of configuration (or video) data is desirable and of course\nis there a solution for this. There are three (probably more) potential options.\n\nIt is also important be aware that the configuration directory can be different\ndepending on how TVHeadend is started. If tvheadend is started, and a directory\nexists in `/var/lib/tvheadend` or `/etc/tvheadend` which matches the UID of the\nuser executing TVHeadend, those locations will be preferred. Since the container\nwill always run as the user `tvheadend` and these locations are always created,\nTVHeadend in the container will always use `/var/lib/tvheadend` as its\nconfiguration directory.\n\nBefore diving more deeply into things, the current container has two default\nvolumes defined, `/var/lib/tvheadend` and `/var/lib/tvheadend/recordings`. These\ncan also be defined as named/external volumes as well as more that can be added.\n\n\n### Secrets\nDocker secrets are files that can be mounted in a container. They are not\never written to disk (they live in `/run/secrets/`). While useful, not\napplicable, as they can't be controlled runtime, and store one file per secret.\n\n\n### Config\nVery similar to Secrets, but stored on disk within the container.\n\n\n### Volumes\nVolumes come in a few flavors actually, but the basics are the same. A local\ndirectory is mounted inside the container. The flavors come in the form of\nwhere to get the source directory from.\n\n\n#### Local directory\nLets say the docker container should share the configuration files with the\nlocal user. In such case, the local `.config/hts` directory is needed within\nthe container, can be easily mounted with the following example. Note that\nthe `--volume` flag requires a absolute path.\n\n```sh\ndocker container run \\\n    --interactive \\\n    --name 'TVHeadend_container_01' \\\n    --rm \\\n    --tty \\\n    --user \"$(id -u):$(id -g)\" \\\n    --volume '/home/user/.config/hts:/var/lib/tvheadend:rw' \\\n    --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \\\n    'ghcr.io/tvheadend/tvheadend:issue-123'\n```\n\n\u003e _Note_: The `--user` flag is also used here, to ensure file-ownership does\n\u003e not change. By default the TVHeadend container runs as user `tvheadend`\n\u003e which may not have the same UID or GID as the local user. If the `id` command\n\u003e is not available, `\"1000:1000\"` could be used instead, where `1000` would be\n\u003e the actual UID and GID for the current user. The `rw` (read-write) or `ro`\n\u003e (read-only) flags can set the access mode, but are optional.\n\n\n#### Anonymous Volumes\nDocker manage volumes also internally. This is actually very common when using\ndocker containers, as the volumes are fully docker managed. It is even possible\nto share volumes between containers, e.g. have multiple TVHeadend instances\nrunning, with their own configuration, but sharing the recordings volume.\n\nThe TVHeadend container already creates an anonymous volume by default, so that\nconfiguration is stored/re-used. Anonymous volumes are not 'forever persistent'\nand are removed by regular cleanup actions (`docker system prune` for example).\n\n\n#### Named Volumes\nDocker named volumes are manually created and persistently stored. For long\nterm use (using a server for example), they are the preferred way of handling\ndata. Docker compose can create them automatically (more on that later) but\ngenerally, a volume is created beforehand as such.\n\n$ docker volume create 'hts_config'\n\nand mounted as:\n\n```sh\ndocker container run \\\n    --interactive \\\n    --name 'TVHeadend_container_01' \\\n    --rm \\\n    --tty \\\n    --user \"$(id -u):$(id -g)\" \\\n    --volume 'hts_config:/var/lib/tvheadend:rw' \\\n    --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \\\n    'ghcr.io/tvheadend/tvheadend:issue-123'\n```\n\n\n### Additional files\nIt is possible to use the volumes concept to add additional files to an\ndockerized setup. For example if there is a volume holding various picons,\nwhich are created and managed/updated through a different container. The volume\ncan be simply added in the same way.\n\n\n```sh\ndocker container run \\\n    --interactive \\\n    --name 'TVHeadend_container_01' \\\n    --rm \\\n    --tty \\\n    --user \"$(id -u):$(id -g)\" \\\n    --volume 'hts_config:/var/lib/tvheadend:rw' \\\n    --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \\\n    --volume 'picons_volume:/usr/share/picons' \\\n    'ghcr.io/tvheadend/tvheadend:issue-123'\n```\n\n\u003e _Note_: The same can be done for directories holding binaries or scripts\n\u003e but beware that this may not always work as expected. For example if a\n\u003e python script is added, and requires the python interpreter, which is not\n\u003e available in the TVHeadend container, the binary will be there, but it cannot\n\u003e run. The same is true for an executable, if the libraries it depends on are\n\u003e not there, it will fail to run. If in doubt, entering the container by\n\u003e appending `/bin/sh` and running the binary or `ldd`ing may give a clue.\n\n\n### Environment variables\nTVHeadend can also consume environment variables for additional configuration.\nMost notably being the timezone. Environment variables can be easily added from\nthe commandline.\n\n```sh\ndocker container run \\\n    --env 'TZ=\"Etc/UTC\"' \\\n    --interactive \\\n    --name 'TVHeadend_container_01' \\\n    --rm \\\n    --tty \\\n    --user \"$(id -u):$(id -g)\" \\\n    --volume 'hts_config:/var/lib/tvheadend:rw' \\\n    --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \\\n    --volume 'picons_volume:/usr/share/picons' \\\n    'ghcr.io/tvheadend/tvheadend:issue-123'\n```\n\n\n## Configuring devices\nTVHeadend gets most of its use via external devices, DVB tuners and the like.\nThe following section is completely optional however if no devices need to be\nmapped to the container however.\n\nThere are a some caveats depending on static or dynamic devices.\n\nIn both cases, the device needs to exist however when starting docker and\nthe permissions to access the device need to be correct. The TVHeadend\ncontainer is by default part of the `video`, `audio` and `usb` groups,\nwhich seems to use the same UID on at least Gentoo and Alpine, but Arch has\ndifferent GID's.\n\n\u003e _Note_: If there is no shared GID, and no desire to change the GID of the\n\u003e host, it is also possible to give **HIGHLY UNRECOMMENDED** 666 permissions to\n\u003e the device.\n\n\n### Static devices\nFor static devices, that are not added or removed while the container is\nrunning, this is easy enough with the add the `--device` flag. Assuming\nTVHeadend is to take care of all devices, the entire dvb directory can be\nshared.\n\n```sh\ndocker container run \\\n    --env 'TZ=\"Etc/UTC\"' \\\n    --device '/dev/dvb' \\\n    --interactive \\\n    --name 'TVHeadend_container_01' \\\n    --rm \\\n    --tty \\\n    --user \"$(id -u):$(id -g)\" \\\n    --volume 'hts_config:/var/lib/tvheadend:rw' \\\n    --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \\\n    --volume 'picons_volume:/var/lib/picons' \\\n    'ghcr.io/tvheadend/tvheadend:issue-123'\n```\n\n\n### Dynamic devices\nDynamic devices are a lot harder to deal with, due to their dynamic nature.\nFor DVB devices, that only create and remove themselves from `/dev/dvb` this\nshould work. Devices however that get exposed by their exact path, things\nare less easy.\n\nOne thing that seems to work quite well, is to create a specific udev rule,\nthat creates a symlink the device seems to work well. The following example\nexposes a USB serial converter as `/dev/myserialdevice`.\n\n```\nACTION==\"add\",\nSUBSYSTEM==\"tty\",\nATTRS{idVendor}==\"1236\",\nATTRS{idProduct}==\"5678\",\nATTRS{serial}==\"12345678\",\nMODE=\"660\",\nTAG+=\"uaccess\",\nSYMLINK+=\"myserialdevice\"\n```\n\n\u003e _Note_: Worse comes to worst, the entire `/dev` directory could be device\n\u003e mounted or the `--privileged` flag, but are **HIGHLY UNRECOMMENDED** from\n\u003e a security (and isolation) perspective.\n\u003e Also `udevadm info -q all -n '/dev/mydevice'` can be used to inspect a device\n\u003e and also show any currently installed symlinks.\n\u003e Finally, if permission access is not working, the `MODE` could be set to an\n\u003e insecure `666`.\n\n### Other devices\nThe above section spoke mostly of adding DVB devices to the container, but\nother devices can be added as well, for example `/dev/dri` to map a GPU for\nencoding acceleration, assuming the needed tools are available to do so.\n\n\u003e _Tip_: It is actually better to have a dedicated container running conversion\n\u003e and share storage locations via volumes instead.\n\n## Network access\nTVHeadend is a network connected device, but by default, docker will not map\nany ports that a service listen to the host. Full network isolation. As such\nnetwork ports need to be published to the host. The `--publish` flag does so.\nIn the example below, a range of ports gets published to all devices via the\nIP address `0.0.0.0`. This can be restricted to a specific interface via its\nspecific IP address.\n\n```sh\ndocker container run \\\n    --device '/dev/dvb' \\\n    --env 'TZ=\"Etc/UTC\"' \\\n    --interactive \\\n    --name 'TVHeadend_container_01' \\\n    --publish \"0.0.0.0:9981-9982:9981-9982/tcp\" \\\n    --rm \\\n    --tty \\\n    --user \"$(id -u):$(id -g)\" \\\n    --volume 'hts_config:/var/lib/tvheadend:rw' \\\n    --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \\\n    --volume 'picons_volume:/var/lib/picons' \\\n    'ghcr.io/tvheadend/tvheadend:issue-123'\n```\n\n\n\u003e _Note_: A proper docker setup isolates container's networking completely with\n\u003e unique interfaces per container. The default `run` command as used throughout\n\u003e this guide however, uses the default docker network, which means that\n\u003e individual containers are actually on the same network internally. Think of\n\u003e it as a networking router controlled by docker, where all containers are\n\u003e plugged into. A proper setup can be done with the normal `docker run`, but\n\u003e is out of scope, as it is better to use `docker compose` for that. More on\n\u003e this later.\n\n\n### CAClient access\nUsing a CAClient over the network, requires that both containers are on the\nsame network, as they have to be to reach each other. With the default\nnetwork setup of `docker run`, this shouldn't be an issue generally.\n\n\n### Firewall\nThere currently exists a bug with docker, in that a default firewall policy of\n`DROP` on the `INPUT` chain breaks certain container to container traffic.\n\nWhile it is possible to craft `iptables` rules to fix this, the dynamic nature\nof everything makes this very tricky in general. The only work around is\nto set the default policy to `ACCEPT` and ensure the chain `DROPS` all packets.\n\nAssuming `ppp0` is the internet connection that is a monitored interface and\neverything else is `okay`. On an empty/firewall;\n\n$ iptables -A -i ppp0 -j DROP\n$ iptables -P INPUT DROP\n\n\n## Troubleshooting\nTo trouble shoot device pass through, networking, or other permission related\nales, it is possible to start the container in privileged mode, connect directly\nto the host network and run TVHeadend as root.\n\nAll are **NOT** recommended for normal use, but can help isolate issues.\n\n$ docker container run --privileged --user 'root:root' --network 'host' ...\n\n\u003e _Tip_: After running the container as root, make sure to change all\n\u003e ownership back to `tvheadend:tvheadend`, most likely the configuration files.\n\u003e This is best done from within the container using\n\u003e\n\u003e $ chmod -R 'tvheadend:tvheadend' '/home/tvheadend'\n\u003e\n\n\n## Compose\nDocker compose can be used to define whole cluster of services. While this entry\nwill not be an expert piece on how to properly define compose services and setup\nwhole service clusters, here is an example that puts all of the above in a\nsingle file. It is here for reference only and users are expected to know what\nthey are doing when using this.\n\n```yaml\nnetworks:\n  tvheadend: {}\n  oscam: {}\n\nvolumes:\n  tvheadend:\n  oscam:\n\nservices:\n  oscam:\n    image: docker.io/olliver/oscam:latest # TODO\n    cap_drop:\n      - all\n    ulimits:\n      nproc: 64\n      nofile:\n        soft: 1024\n        hard: 65535\n    devices:\n      - /dev/cardreader\n    env_file:\n      - common.env\n    volumes:\n      - oscam:/etc/oscam:rw\n    networks:\n      - oscam\n    expose:\n      - \"1988/udp\"\n      - \"15000/tcp\"\n    ports:\n      - \"8888:8888/tcp\"\n    command: -d 64\n    restart: unless-stopped\n    healthcheck:\n      test: wget localhost:8888 2\u003e\u00261 | grep -q 'connected'\n\n\nservices:\n  tvheadend:\n    image: ghcr.io/tvheadend/tvheadend:latest\n    cap_drop:\n      - all\n    ulimits:\n      nproc: 256\n      nofile:\n        soft: 8192\n        hard: 65535\n    devices:\n      - /dev/dvb/\n    environment:\n      - TZ: 'Europe/Amsterdam'\n    volumes:\n      - tvheadend:/var/lib/tvheadend:rw\n      - /export/recordings:/var/lib/tvheadend/recordings:rw\n    networks:\n      - tvheadend\n      - oscam\n    ports:\n      - \"9981-9982:9981-9982/tcp\"\n    command: --config '/var/lib/tvheadend' --nosatip\n    restart: unless-stopped\n    healthcheck:\n      test: wget -O - -q 'http://localhost:9981/ping' | grep -q 'PONG'\n```\n\n\u003e _Tip_: The `ulimits` are optional but recommended. Oscam uses a `common.env`\n\u003e file holding the Timezone, TVHeadend uses a variable, both achieve the same.\n\n\u003e _Note_: TVHeadend automatically detects the location for storage, the above\n\u003e is just an example, if the location is changed here however, make sure to\n\u003e update the configuration in the UI to match.\n\n\n## Developing using a container\nWhile it is certainly easy and possible to develop using `docker image build`,\nit is quite slow, as some setup and teardown work needs to be done before\ncompilation can be done, also already compiled objects get discarded.\n\nInstead, the `builder` container, an intermediate step, can be used instead\nand the source directory volume mounted herein.\n\n```sh\ndocker image build\n    --rm \\\n    --tag 'tvheadend:builder' \\\n    --target 'builder' \\\n    './'\n```\n\n\u003e _Note_: Because the way the builder is set up, it will build everything\n\u003e once regardless. This is to keep the current `Containerfile` simple.\n\u003e Other then the cost of some time, it is harmless.\n\nWith the built `builder` image, the container can be entered, and make can be\nissued as normal.\n\n```sh\ndocker container run \\\n    --interactive \\\n    --rm \\\n    --tty \\\n    --user \"$(id -u):$(id -g)\" \\\n    --volume \"$(pwd):/workdir\" \\\n    --workdir '/workdir' \\\n    'tvheadend:builder' '/bin/sh'\n./configure\nmake\n```\n\n\u003e _Tip_: It might be tempting to short-circuit the command `/bin/sh` by\n\u003e replacing it with `make`. However it will still be slower due to the\n\u003e container creation/teardown, but otherwise functionally identical.\n\n\u003e _Note_: As the current working dir is volume mounted into the container,\n\u003e all changes done by the container will be made on the regular source code\n\u003e directory. As such, exiting and re-starting the container won't remove any\n\u003e intermediate object files etc. As the build-container runs normally runs as\n\u003e root, the `--user` flag has to be set as otherwise files created will be\n\u003e owned by root when exiting the container.\n\n\n[docker]: https://www.docker.com\n[ghcr]: https://github.com/tvheadend/tvheadend/pkgs/container/tvheadend\n","funding_links":["https://opencollective.com/tvheadend"],"categories":["C","IPTV and DVR"],"sub_categories":["APKs"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftvheadend%2Ftvheadend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftvheadend%2Ftvheadend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftvheadend%2Ftvheadend/lists"}