The Beacon Stack
TV, movies, and BitTorrent — each app works standalone, and all
three share indexers, quality profiles, download clients, and
media-handling settings through Pulse when you want them to. One docker compose up
for the whole stack.
The stack
Four apps, one config layer
Pilot, Prism, and Haul each run independently with local configuration. Pulse is the hub that lets all three share one indexer list (the search APIs that catalog releases), one set of quality profiles (ranked rules for what to grab), one download client — Haul registers itself — and a common media-handling settings namespace.
Pulse
control plane
Service registry and shared configuration layer. Beacon apps register here,
but so can anything else — the Go SDK at pkg/sdk lets any
service tap into shared indexers, quality profiles, download clients, and
media-handling settings. Optional for a single install, essential the
moment you run two.
Pilot
TV series managerTracks a TV library via TMDB, polls indexers for new episodes, grabs through your download client, and files completed downloads into season folders. Dead-torrent detection with auto-blocklist, and one-pass Sonarr import.
:8383 Deep dive →Prism
Movie collection managerEdition-aware release scoring, full release decision explainability, and a native Radarr v3 API so anything that already speaks to Radarr — request tools, dashboards, mobile clients — points at Prism with no changes. Import from a running Radarr instance in one pass.
:8282 Deep dive →Haul
BitTorrent clientBuilt on anacrolix/torrent. Accurate ETAs from exponential moving averages, multi-level stall detection that catches dead torrents the rest of the stack misses, and rename-on-complete using media metadata passed by Pilot or Prism. VPN-aware dashboard.
:8484 Deep dive →Per-app default theme. 18 presets, all switchable.
How it connects
How Pulse connects everything
Each Beacon service registers with Pulse on startup and heartbeats every 30 seconds. Shared state — indexers, quality profiles, download clients, media-handling settings — flows outward from Pulse to every subscribed service on a 30-second sync, and immediately when Pulse fires a push hook on save. When Haul registers, Pulse auto-creates the download-client entry so Pilot and Prism find it without anyone copying an API key.
Pulse isn't Beacon-only. Any Go service can join through the client SDK
at pkg/sdk — register, discover peers, pull assigned
indexers, and read shared config with one client instance.
Registration
Register once, configured automatically
When Pilot starts, it registers with Pulse: name, URL, capabilities
(content:tv, content:movies, etc.). Pulse hands
back the indexers, quality profiles, download clients, and shared
media-handling settings that match those capabilities.
Add an indexer to Pulse — its catalog category (Movies, TV) maps to the capability set, and Pulse auto-assigns it to every matching service. A new TV-only indexer reaches Pilot within 30 seconds; a Movies-and-TV one reaches both Pilot and Prism. One API key to update, not three.
{
"name": "pilot",
"type": "media-manager",
"api_url": "http://pilot:8383",
"capabilities": [
"content:tv",
"supports_torrent",
"supports_usenet"
]
} {
"indexers": [
{ "name": "NZBgeek", "categories": ["TV"] },
{ "name": "TorrentDB", "categories": ["TV"] }
],
"quality_profiles": [
{ "name": "HD-1080p", "managed_by_pulse": true }
],
"download_clients": [
{ "name": "haul", "host": "haul", "port": 8484 }
],
"shared_settings": {
"rename_files": true,
"colon_replacement": "smart"
}
} Architecture
One stack, four apps
Why it exists
Built for people who run their own stack
Register once, configure everywhere
Add an indexer to Pulse and every service whose capabilities match picks it up — within 30 seconds, or instantly via push hook. One API key, one quality profile, one place to change things when they break.
Standalone or connected
Pilot, Prism, and Haul each run fine with local config. Pulse is purely additive — bring it in when you're ready for the shared layer.
Zero telemetry, MIT licensed
No analytics, no crash reporting, no update checks. Everything runs in your network and stays there. All four apps are MIT licensed and built in the open.
Deployment
How the compose files fit together
The beacon-stack/deploy repo ships several Compose files because the stack tries to stay simple by default and flexible when you need it to be. Here's how they layer.
docker-compose.yml
The base file. Defines Postgres, Pulse, Pilot, Prism, Haul, and the two
init sidecars. docker compose up -d loads this without you
having to ask. For most users this is the only file that ever runs.
docker-compose.override.yml
Compose has a built-in convention: if a file named docker-compose.override.yml
sits next to the base file, it merges automatically on top of it. We use
that for your personal tweaks — extra bind mounts, healthcheck timing for
slow hardware, pinning a specific image tag. The file is gitignored, and
docker-compose.override.example.yml in the repo shows the
patterns you can copy.
docker-compose.vpn.yml
Routes Haul through a Gluetun VPN container. Loaded by setting
COMPOSE_FILE=docker-compose.yml:docker-compose.vpn.yml in your
.env (so plain docker compose up -d picks it up
forever after) — or by passing -f docker-compose.yml -f docker-compose.vpn.yml
on every command if you'd rather be explicit. Requires Docker Compose v2.20+
for the !reset directive that detaches Haul from the bridge
network and rehomes it into Gluetun's namespace.
COMPOSE_PROFILES
Profiles enable optional containers without a separate Compose file.
COMPOSE_PROFILES=flaresolverr in your .env spins
up a FlareSolverr container alongside the rest of the stack for indexers
behind Cloudflare bot protection. (You also need to uncomment the
PULSE_FLARESOLVERR_URL line in docker-compose.yml
so Pulse actually routes its scrapes through it.) Most users never need this.
How they layer
- Default
docker-compose.yml— the whole stack, no VPN. - Personalized
docker-compose.yml+docker-compose.override.yml— your local tweaks merged on top automatically. - VPN'd
docker-compose.yml+docker-compose.vpn.yml— Haul tunnels through Gluetun. - Both
docker-compose.yml+docker-compose.override.yml+docker-compose.vpn.yml— personalized and VPN'd.
Compose merges files in the order it loads them; later files win on conflicts.
By default, docker compose up auto-detects two files: the base and
docker-compose.override.yml (if present), with the override applied
last. The moment you set COMPOSE_FILE in .env, that
auto-detection turns off and you become responsible for the full chain — so if
you want both your overrides and the VPN, write
COMPOSE_FILE=docker-compose.yml:docker-compose.override.yml:docker-compose.vpn.yml.
Always keep docker-compose.vpn.yml last; its !reset
directive only applies if it merges after the base's network config for Haul.
Getting started
Up in three commands
The full stack — Postgres, Pulse, Pilot, Prism, Haul, and an optional
VPN container — lives in beacon-stack/deploy. Point three
paths at your media directories and run.
# Clone and configure $ git clone https://github.com/beacon-stack/deploy $ cd deploy && cp .env.example .env # Set TV_PATH, MOVIES_PATH, DOWNLOADS_PATH in .env # Start everything $ docker compose up -d