Dispatcharr on PMSS
On a Pulsed Media seedbox, Dispatcharr runs in rootless Docker inside your own account and manages the live-TV channels from your own tuner or provider subscriptions: it matches them to programme-guide (EPG) data and serves them as an M3U playlist, an Xtream Codes login or a virtual HDHomeRun tuner. Tested on a Pulsed Media seedbox on 24 October 2026 with ghcr.io/dispatcharr/dispatcharr v0.33.0, at 1.7 GiB of memory when idle, with steps that create the admin account before any other account on the server can reach it and close its playlist links, which out of the box answer anyone.
There is no panel address for it: Dispatcharr only works at the root of a hostname, so you reach it over an SSH tunnel (see how to reach Dispatcharr).

Privacy
Level: MEDIUM. Using Dispatcharr reduces your privacy:
- Your IPTV provider accounts, playlist addresses and Dispatcharr's own users sit in its data folder, in your account on a shared server. The folder modes in the install steps keep other accounts out of it.
- Out of the box, the playlist (
/output/m3u) and the guide (/output/epg) answer without a login. In the test, an anonymous request to127.0.0.1on the server, the same request another account on that server could make, got the channel list and its stream links. Step 7 of the install closes both. Per the v0.33.0 source (apps/hdhr/api_views.py), the virtual HDHomeRun tuner answers without a login too and follows the same rule, so step 7 closes it as well; that was read in the code, not tested. Whether a stream link plays without a login was not tested with a live stream. - To show the server's public address and location, Dispatcharr asks two outside services:
api64.ipify.org, thenipapi.co(orip-api.comif that fails), per core/api_views.py in v0.33.0. The settingDISPATCHARR_ENABLE_IP_LOOKUP=falseturns this off (settings.py, line 123); it was not part of the tested run. - Streams are fetched from the seedbox's address, so your provider sees that address and every channel you open.
Requirements
| Item | Measured or required |
|---|---|
| Memory | 1.7 GiB at idle with one channel (docker stats). The parts: uWSGI 673 MiB, Celery 620 MiB, PostgreSQL 269 MiB, Daphne 123 MiB, nginx 58 MiB, Redis 18 MiB. This counts against your plan's memory limit, together with your torrent clients; Seedbox and Storage Box Resource Tiers lists the limits.
|
| Disk | The image is 4.25 GB and about 85,000 files. The account used for the test had a file-count quota of 100,000 files (soft) and 125,000 (hard). Run quota -s first: the image alone can push an account near or over its file count.
|
| Own login | Yes, created from inside the container before the port is published. |
| Web address | None on the panel. Dispatcharr has no URL-base setting (upstream issue #125), so a /user-USERNAME/dispatcharr/ proxy fragment does not work. Reach it over SSH, or through your own domain.
|
| Other apps | None required. Jellyfin is optional (see Integrate). |
Install
Run these over SSH as your own user. PORT is a free port between 2000 and 38000; check with ss -tln that nothing listens on it. Run docker ps first: an empty list means Docker is running.
1. Make the folders. The parent stays 700 so other accounts cannot enter. The data folder must be 755, because Dispatcharr's own user inside the container has to enter it; with 700 the container restarts forever without an error message.
mkdir -p ~/docker/dispatcharr/data chmod 700 ~/docker/dispatcharr chmod 755 ~/docker/dispatcharr/data
2. Write the admin login to a 0600 file. The password is generated straight into the file and never appears on a command line.
umask 077 printf 'DJANGO_SUPERUSER_PASSWORD=%s\nDJANGO_SUPERUSER_USERNAME=admin\nDJANGO_SUPERUSER_EMAIL=admin@example.invalid\n' \ "$(head -c 24 /dev/urandom | base64 | tr -d '\n')" > ~/docker/dispatcharr/admin.env chmod 600 ~/docker/dispatcharr/admin.env
grep PASSWORD ~/docker/dispatcharr/admin.env shows the password when you need it.
3. Pull the tested image.
docker pull ghcr.io/dispatcharr/dispatcharr@sha256:96bc548978c68e684677da7895692eeeefb7f0b2edfa38311dac2b702c61d236
4. Start it the first time with no port published. Nothing outside the container can reach it yet. Leave PUID unset: Dispatcharr refuses to run as root inside the container. Keep DISPATCHARR_TRUSTED_PROXIES=none: it makes Dispatcharr ignore forwarded-address headers (per its README), so nobody can claim a different address in a header to get past its address rules.
docker run -d --name dispatcharr --restart unless-stopped \ -e DISPATCHARR_ENV=aio -e REDIS_HOST=localhost -e CELERY_BROKER_URL=redis://localhost:6379/0 \ -e DISPATCHARR_TRUSTED_PROXIES=none \ -v ~/docker/dispatcharr/data:/data \ ghcr.io/dispatcharr/dispatcharr@sha256:96bc548978c68e684677da7895692eeeefb7f0b2edfa38311dac2b702c61d236
Wait about a minute for its database setup to finish (docker logs dispatcharr | tail).
5. Create the admin account from inside the container. The command reads the login from the 0600 file. It also loads the secret key from /data/jwt, because a plain docker exec ... createsuperuser, as the upstream README has it, stops with "The SECRET_KEY setting must not be empty".
docker exec -w /app --env-file ~/docker/dispatcharr/admin.env dispatcharr sh -c \ 'export DJANGO_SECRET_KEY="$(tr -d "\r\n" < /data/jwt)"; python manage.py createsuperuser --noinput'
It prints Superuser created successfully. From now on the web setup page refuses to create another admin.
6. Recreate the container with a loopback port. The data stays in the folder.
docker rm -f dispatcharr docker run -d --name dispatcharr --restart unless-stopped \ -e DISPATCHARR_ENV=aio -e REDIS_HOST=localhost -e CELERY_BROKER_URL=redis://localhost:6379/0 \ -e DISPATCHARR_TRUSTED_PROXIES=none \ -p 127.0.0.1:PORT:9191 \ -v ~/docker/dispatcharr/data:/data \ ghcr.io/dispatcharr/dispatcharr@sha256:96bc548978c68e684677da7895692eeeefb7f0b2edfa38311dac2b702c61d236
It answered on the port about 20 seconds after the start in the test. Do not add a proxy file in ~/.lighttpd/custom.d/ for Dispatcharr: it cannot run under a path. The port on 127.0.0.1 is reachable by other accounts on the same server, which is why the admin account came first.
7. Close the open playlist and guide. This logs in with the admin account and sets the M3U_EPG network-access rule to 10.255.255.1/32, a single address that the server's own connections do not come from; in the test the anonymous playlist and guide then answered 403. Run it on the server; the temporary files are removed at the end.
cd ~/docker/dispatcharr
umask 077
set -a; . ./admin.env; set +a
printf '{"username":"admin","password":"%s"}' "$DJANGO_SUPERUSER_PASSWORD" > login.json
curl -s -o tok.json -H 'Content-Type: application/json' --data @login.json http://127.0.0.1:PORT/api/accounts/token/
printf 'Authorization: Bearer %s\n' "$(grep -oE '"access":"[^"]+"' tok.json | sed -E 's/"access":"//;s/"$//')" > hdr.txt
NID=$(curl -s -H @hdr.txt http://127.0.0.1:PORT/api/core/settings/ | grep -oE '\{"id":[0-9]+,"key":"network_access"' | grep -oE '[0-9]+' | head -1)
curl -s -o /dev/null -w 'PUT network_access: %{http_code}\n' -X PUT -H @hdr.txt -H 'Content-Type: application/json' \
--data '{"key":"network_access","name":"Network Access","value":{"M3U_EPG":"10.255.255.1/32"}}' \
http://127.0.0.1:PORT/api/core/settings/$NID/
rm -f login.json tok.json hdr.txt; unset DJANGO_SUPERUSER_PASSWORD DJANGO_SUPERUSER_USERNAME DJANGO_SUPERUSER_EMAIL
It prints PUT network_access: 200. Check the result: curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:PORT/output/m3u now prints 403. Players then sign in with a Dispatcharr user's login through its Xtream Codes output instead of the open playlist link; a player was not part of the test. That Xtream Codes output has its own network rule (XC_API), which step 7 leaves at its default: per the v0.33.0 source it accepts every address, so other accounts on the server can reach it too, and the Dispatcharr user's password (with Dispatcharr's limit on failed logins) is what protects it. Give each Dispatcharr user a long generated password.
Configure
- Log in through the SSH tunnel from Not on PMSS shared hosting at
http://127.0.0.1:9191/, asadminwith the password fromadmin.env. - Add your sources in the web interface: your provider's M3U or Xtream Codes account and an EPG source such as XMLTV or Schedules Direct, per the Dispatcharr README. Adding a provider was not part of the test.
- Keep
M3U_EPGclosed. Rootless Docker hands connections to the container from its own private network address (in the test of another app on the same setup, every client showed up that way), so connections from the server, from other accounts and through a tunnel look alike to Dispatcharr. Opening the rule to a private range opens it to every account on the server. - To turn off the outside address lookup from Privacy, add
-e DISPATCHARR_ENABLE_IP_LOOKUP=falseto bothdocker runlines. Not tested. - Dispatcharr users are for your own devices. Pulsed Media's terms say personal accounts are used by the primary owner only, and access to services on them may not be resold or shared.
- To use less memory: adding
-e UWSGI_WORKERS=1 -e CELERY_MAX_WORKERS=1 -e CELERY_MIN_WORKERS=1to bothdocker runlines brought Dispatcharr v0.31.0 down to 708 MiB at idle on another Docker host on 3 October 2026. It was not measured on a Pulsed Media seedbox. - Updates are yours to run: pull a newer image and repeat step 6 with it and the same
-elines. The database lives in the data folder, and the admin account survived recreating the container in the test; an update to a newer version was not tested.
Integrate
- Jellyfin: the Dispatcharr README says its HDHomeRun emulation lets Jellyfin add it as a live-TV tuner. Jellyfin from Install Media Stack runs on the same server and can reach
127.0.0.1:PORT. Not tested. WithM3U_EPGclosed, Jellyfin's requests to the tuner and the open playlist are refused like everyone else's, and opening the rule for Jellyfin opens it for every account on the server. A playlist address that carries a Dispatcharr user's login is the way that keeps it closed. - Sonarr, Radarr, Prowlarr: no integration; Dispatcharr is not part of that chain.
Test
Run these on the server. The commands without a login show what another account on the same server sees.
| Check | Command | Result in the test |
|---|---|---|
| Container up | docker ps |
dispatcharr running
|
| Login page | curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:PORT/ |
200 |
| Admin exists | curl -s http://127.0.0.1:PORT/api/accounts/initialize-superuser/ |
{"superuser_exists": true}; an anonymous attempt to create another admin is refused
|
| API needs a login | curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:PORT/api/channels/channels/ |
401 (also /get.php, /player_api.php, /xmltv.php)
|
| Playlist and guide closed | same, for /output/m3u and /output/epg |
200 before step 7, 403 after |
| From your computer | the SSH tunnel, then http://127.0.0.1:9191/ in a browser |
Dispatcharr login page (screenshot above) |
Also run ss -tln | grep ':PORT ': the port must show as 127.0.0.1:PORT, never 0.0.0.0:PORT or *:PORT, which would put it on the internet. /api/core/version/ answers without a login and shows the version number.
Uninstall
Remove the container:
docker rm -f dispatcharr
Removing the data is your choice. It deletes Dispatcharr's database (the admin and other users, your provider accounts, channels, EPG sources and settings) and the admin.env file. Its files belong to the container's own user, so a plain rm -rf fails with "Permission denied"; delete them from a container instead, then remove the folder:
docker run --rm --entrypoint /bin/sh -v ~/docker/dispatcharr:/srv/cleanup \ ghcr.io/dispatcharr/dispatcharr@sha256:96bc548978c68e684677da7895692eeeefb7f0b2edfa38311dac2b702c61d236 \ -c 'find /srv/cleanup -mindepth 1 -delete' rmdir ~/docker/dispatcharr
Then remove the image, which frees the 4.25 GB and its files:
docker image rm ghcr.io/dispatcharr/dispatcharr@sha256:96bc548978c68e684677da7895692eeeefb7f0b2edfa38311dac2b702c61d236
Troubleshooting
- The container restarts over and over and the log shows only "Starting user setup...". The
datafolder is not 755. Runchmod 755 ~/docker/dispatcharr/data; the next restart works. - The log says "PostgreSQL cannot run as root". A
PUID=0setting was added. Remove it; Dispatcharr runs as its own user inside the container. createsuperusersays "The SECRET_KEY setting must not be empty". Use the command from step 5, which loads the key from/data/jwt.- The pull fails with "disk quota exceeded". In the test this was the file-count quota, not disk space. Check
quota -s, and remove images you no longer use (docker image ls,docker image rm). - Playlist links point at
127.0.0.1. Dispatcharr builds stream links from the address you opened it at. Through an SSH tunnel those links work only on the computer that holds the tunnel. rm -rfon the data folder says "Permission denied". Use the container-side delete from Uninstall.
Apps you add on a shared Pulsed Media seedbox are served at a path under the server's name, such as /user-USERNAME/APP/. Dispatcharr works only at the root of a hostname (upstream issue #125). Two ways in remain.
SSH tunnel (tested)
From your own computer:
ssh -N -L 9191:127.0.0.1:PORT USERNAME@SERVER.pulsedmedia.com
Then open http://127.0.0.1:9191/ while that SSH connection stays open; -N keeps it open without a shell. The local number, 9191 here, can be any free port on your computer. This is how the login page in the screenshot was reached. Players work only on the same computer, for the reason under Troubleshooting.
Your own domain through a VPS (not tested end to end)
For players on other devices, a small VPS you control can be the front door: a reverse proxy on the VPS, as in Method 1 on Custom Domains on PMSS, with an SSH forward as its upstream instead of the permalink. Pulsed Media has not tested this route end to end.
- The VPS keeps the same SSH forward open, bound to its own loopback, for example
ssh -N -L 127.0.0.1:9191:127.0.0.1:PORT USERNAME@SERVER.pulsedmedia.comrun by its service manager or autossh. - Its reverse proxy serves your domain from that forward. With Caddy, which fetches the certificate itself:
example.com {
reverse_proxy 127.0.0.1:9191
}
- The port on the seedbox stays on
127.0.0.1; nothing new opens to the internet on the seedbox. Your domain, however, puts Dispatcharr's login page, API and Xtream Codes output on the internet, so its user passwords then stand between the internet and your provider logins. Whether stream links play without a login was not tested. - The VPS holds an SSH key that opens a shell on your seedbox account. Make a key used only for this and keep the VPS locked down.
- Every stream travels from the seedbox through the VPS to the viewer, so the VPS's bandwidth limits what you can watch.
- Every visitor reaches Dispatcharr through the same SSH forward, so its address-based access rules are not expected to tell visitors apart (not tested). Rely on user logins and keep
M3U_EPGclosed.
With a VPS in place, Dispatcharr can also run on the VPS itself. The VPS then needs the memory measured above and carries the streams on its own connection.
Without a VPS, the tunnel reaches the web interface from the one computer that holds it; players on other devices need the VPS route. Rootless Docker is part of every Pulsed Media seedbox plan, and the resource tiers page lists each plan's memory to set against Dispatcharr's 1.7 GiB. See the seedbox plans.
See also
- Rootless DOCKER — running containers in your account
- Self-Hosted Apps on PMSS — other applications for rootless Docker, with their sub-path and login facts
- Custom Domains on PMSS — the reverse-proxy pattern behind the VPS route
- SSH — connecting to your seedbox and forwarding ports
- Seedbox — Pulsed Media seedbox plans, where rootless Docker is included