*The whole arr media stack as one Docker Compose project — with a one-command Proxmox installer.
Sonarr, Radarr, Lidarr, Prowlarr, Bazarr, qBittorrent, FlareSolverr and
Jellyseerr, wired to a single hardlink-friendly /data tree, behind an optional
reverse proxy, with an optional VPN kill-switch for the torrent client. Run it on
a fresh Proxmox LXC with one command, or docker compose up -d on any Docker host.
bash -c "$(curl -fsSL https://raw.githubusercontent.com/abarbarich/arrstack/main/arrstack.sh)"Run it on your Proxmox host as root. It will:
- create a Debian 12 LXC (unprivileged,
nesting=1so Docker works), - optionally format a spare disk as ext4 and bind-mount it as the media store,
- install Docker + the Compose plugin inside the container,
- drop the stack in
/opt/arrstack, generate.env, anddocker compose up -d, - print every app's URL.
With a terminal you get a whiptail menu (resources, network, storage, VPN);
add -y to take defaults from the environment for a fully unattended run.
git clone https://github.com/abarbarich/arrstack.git
cd arrstack
cp .env.example .env # edit PUID/PGID/TZ and DATA_ROOT
docker compose up -d| App | Port | Role |
|---|---|---|
| Sonarr | 8989 | TV |
| Radarr | 7878 | Movies |
| Lidarr | 8686 | Music |
| Prowlarr | 9696 | Indexer manager — syncs indexers to the *arr apps |
| FlareSolverr | 8191 | Solves Cloudflare/JS challenges for some indexers |
| Bazarr | 6767 | Subtitles |
| qBittorrent | 8080 | Torrent download client |
| Jellyseerr | 5055 | Requests (feeds Sonarr/Radarr; talks to Jellyfin/Plex/Emby) |
| Caddy | 80/443 | Optional reverse proxy for http://<app>.home names |
| gluetun | — | Optional VPN sidecar; routes qBittorrent through WireGuard/OpenVPN |
All apps are official LinuxServer.io images
(Jellyseerr uses the maintained seerr-team/seerr image). There is no separate
media server here — ArrStack is happy to feed an external Jellyfin/Plex (see
the Caddyfile for a jellyfin.home example).
Replaces Jackett. Prowlarr is the modern indexer manager and supersedes Jackett for every *arr app, so Jackett is not included.
- Docker Engine + Compose v2 (the Proxmox installer sets these up for you).
- For the Proxmox path: Proxmox VE 8.x (uses
pct/pveam/pvesh). - ~2 GB RAM is plenty (no build steps — everything is a prebuilt image); the Proxmox CT defaults to 4 GB for headroom.
- Storage on one filesystem for the whole
/datatree, so hardlinks work.
Every container sees a single tree so hardlinks and atomic moves work between the download client and the libraries (the TRaSH Guides layout). Copying is avoided entirely — an import is instant and uses no extra space.
/data
├── media/{movies,tv,music} # Sonarr/Radarr/Lidarr libraries
└── torrents/{...,incomplete} # qBittorrent downloads
DATA_ROOT in .env points at the host location of this tree.
scripts/setup-storage.sh (run on the host) formats a blank disk as ext4,
adds it to /etc/fstab by UUID, and bind-mounts it into the CT — with the right
ownership for an unprivileged container (in-CT UID 1000 → host 101000):
# interactive: lists only SAFE, blank disks (hides the OS disk + mounted/LVM/ZFS)
scripts/setup-storage.sh --ctid 111
# non-interactive: format a specific disk
scripts/setup-storage.sh --ctid 111 --disk /dev/sdb -y
# or bind an existing directory instead of formatting anything
scripts/setup-storage.sh --ctid 111 --existing /tank/mediaIt refuses to touch the OS disk or anything mounted / part of LVM/ZFS/RAID, and requires you to type the device path to confirm the (destructive) format.
Route only qBittorrent through a gluetun tunnel. If the VPN drops, gluetun's firewall kills torrent traffic — no leaks — while the rest of the stack keeps working on the LAN.
- Fill the
VPN_*block in.env(provider, WireGuard key, …). - Enable the overlay — either set in
.env:…or pass both files:COMPOSE_FILE=docker-compose.yml:docker-compose.vpn.ymldocker compose -f docker-compose.yml -f docker-compose.vpn.yml up -d(the Proxmox installer does this for you with--vpn). - Verify your exit IP:
docker exec gluetun wget -qO- https://ipinfo.io/ip
Provider-specific values live in the gluetun wiki.
| Variable | Default | Notes |
|---|---|---|
PUID / PGID |
1000 |
UID/GID that owns the media tree |
UMASK |
002 |
group-writable new files (shared media user) |
TZ |
Etc/UTC |
IANA timezone, e.g. Pacific/Auckland |
DATA_ROOT |
./data |
host path of the single /data tree |
CONFIG_ROOT |
./config |
where each app stores its config/db |
COMPOSE_FILE |
— | set to enable the VPN overlay (see above) |
VPN_SERVICE_PROVIDER |
mullvad |
gluetun provider name |
VPN_TYPE |
wireguard |
wireguard or openvpn |
WIREGUARD_PRIVATE_KEY / WIREGUARD_ADDRESSES |
— | WireGuard creds |
VPN_SERVER_COUNTRIES |
— | optional server pin |
LAN_SUBNET |
192.168.0.0/16 |
lets the LAN reach the WebUI through gluetun |
The stack is running, but the apps don't know about each other yet. The
step-by-step wiring guide with screenshots is in docs/SETUP.md —
Prowlarr indexers + FlareSolverr, syncing indexers to the *arr apps, adding
qBittorrent as the download client, root folders, categories, Bazarr and
Jellyseerr. It's all localhost/container-name wiring inside one stack.
Two things update independently: the apps (Docker images) and ArrStack
itself (the compose files / scripts). On Proxmox, pct enter <ctid> first.
cd /opt/arrstack
docker compose pull # fetch newer images
docker compose up -d # recreate only the containers that changed
docker image prune -f # optional: reclaim old image layersEach app's config/database lives in the CONFIG_ROOT volumes, so nothing is
lost on recreate. With Docker the image is the version — pulling a newer
image is the update, so ignore the *arr apps' built-in "Updates" screen.
The Proxmox one-liner installs /opt/arrstack as a git clone:
cd /opt/arrstack
git pull
docker compose up -dOnly needed when the compose file or scripts change upstream — not day to day. (If you didn't install from a clone, just re-fetch the files or re-run the installer.)
- VPN users: nothing extra —
COMPOSE_FILEin.envmeans everydocker composecommand already includes the gluetun overlay. - Rollback: if a
:latestimage regresses, pin that service to a known-good tag (e.g.image: lscr.io/linuxserver/sonarr:4.0.9) anddocker compose up -d. - Back up first: the config volumes are the only state worth keeping —
tar czf arrstack-config.tgz -C "$CONFIG_ROOT" .before a big jump. - Hands-off auto-updates: add a Watchtower container to pull + recreate on a schedule — trades control for convenience.
These apps ship with no authentication until you set it in each one. Don't expose the container to the internet directly — keep it behind your firewall and reach it over VPN/Tailscale, or front it with an authenticated reverse proxy. Set a WebUI login in each app after first launch.
arrstack.sh # PVE host: create LXC, storage, install Docker, deploy
scripts/setup-storage.sh # PVE host: format ext4 disk + bind-mount into the CT
docker-compose.yml # the stack
docker-compose.vpn.yml # optional gluetun overlay (routes qBittorrent)
.env.example # all configuration
caddy/Caddyfile # reverse proxy for *.home names
docs/SETUP.md # the app-wiring walkthrough
lib/helpers.sh # shared logging / helpers for the host scripts
MIT. Provided as-is — review the scripts before running them as root.