Declarative deployment for a single-host media server: nine services in one Docker Compose file, with VPN-isolated downloads, pinned image versions, CI validation on every push, and automated update PRs via Renovate.
The entire stack is reproducible from this repository plus one .env file.
flowchart LR
subgraph host["Docker host (Ubuntu 22.04)"]
subgraph ms["mediastack network"]
plex[Plex :32400]
jellyseerr[Jellyseerr :5055]
sonarr[Sonarr :8989]
radarr[Radarr :7878]
prowlarr[Prowlarr :9696]
byparr[Byparr :8889]
subgraph vpn["gluetun network namespace"]
gluetun[Gluetun VPN]
qbit[qBittorrent :8200]
end
end
portainer[Portainer :9000]
end
jellyseerr -->|requests| sonarr & radarr
prowlarr -->|indexers| sonarr & radarr
prowlarr -->|CF solve| byparr
sonarr & radarr -->|grabs| qbit
qbit -.->|all traffic| gluetun
gluetun ==>|WireGuard| internet((VPN provider))
plex ---|library| media[(media drive)]
qbit ---|downloads| media
qBittorrent has no network of its own. It runs inside Gluetun's network
namespace (network_mode: service:gluetun), so if the VPN tunnel drops
there is no route out: fail-closed by construction, not by firewall rule.
Compose only starts it once Gluetun reports healthy.
| Service | Image | Role | Port |
|---|---|---|---|
| Gluetun | qmcgaw/gluetun |
WireGuard VPN client, egress for qBittorrent | 8888 (proxy) |
| qBittorrent | linuxserver/qbittorrent |
Downloads, VPN-isolated | 8200 |
| Prowlarr | linuxserver/prowlarr |
Indexer management, syncs to Sonarr/Radarr | 9696 |
| Sonarr | linuxserver/sonarr |
TV library automation | 8989 |
| Radarr | linuxserver/radarr |
Film library automation | 7878 |
| Byparr | thephaseless/byparr |
Cloudflare challenge solver for indexers | 8889 |
| Jellyseerr | fallenbagel/jellyseerr |
Request front end | 5055 |
| Plex | linuxserver/plex |
Media server, Intel iGPU transcoding | 32400 |
| Portainer | portainer/portainer-ce |
Container management UI | 9000 |
git clone https://github.com/cyclopsgd/media-server-iac.git
cd media-server-iac
./scripts/bootstrap.sh # first run creates .env and exits
nano .env # VPN credentials, paths, LAN subnet
./scripts/bootstrap.sh # validates, creates directories, deploys
make vpn-check # confirm qBittorrent egress is the VPN IPDay-to-day commands live in the Makefile (make help).
- Pinned images. Every service is pinned to an exact version tag. Nothing changes on the host until a change is merged here.
- Renovate raises a PR when a pinned image has a new release (configuration).
- CI validates every push and PR:
docker compose configagainst a generated env, yamllint, shellcheck, and gitleaks secret scanning (workflow). - Deploy is a manual, auditable step on the host:
git pull && make update.
docker-compose.yaml the whole stack
.env.example every variable the stack consumes, documented
Makefile operational entry points
scripts/bootstrap.sh idempotent first-run setup
docs/architecture.md design decisions and trade-offs
docs/runbook.md operations, updates, migration notes