Skip to content

Commit 3a07357

Browse files
authored
Merge pull request #36 from joshdev8/feat/transmission-vpn
Switch transmission to haugene/transmission-openvpn (real VPN tunneling)
2 parents 78938c3 + 90676bd commit 3a07357

5 files changed

Lines changed: 84 additions & 13 deletions

File tree

.env.example

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ USERDIR=/home/username
1616
# roughly 4 minutes. See https://www.plex.tv/claim
1717
PLEX_CLAIM=
1818

19+
# ============ Transmission / OpenVPN (required — stack won't start if blank) ============
20+
# Transmission runs through haugene/transmission-openvpn. See the list of
21+
# supported providers and config names at:
22+
# https://haugene.github.io/docker-transmission-openvpn/supported-providers/
23+
OPENVPN_PROVIDER=
24+
OPENVPN_CONFIG=
25+
OPENVPN_USERNAME=
26+
OPENVPN_PASSWORD=
27+
# CIDR(s) of networks that should bypass the VPN tunnel. Must include the
28+
# subnet your machine is on, or the web UI on :9091 will be unreachable.
29+
# Comma-separated for multiple networks (e.g. "192.168.0.0/16,10.0.0.0/8").
30+
LOCAL_NETWORK=192.168.0.0/16
31+
# Optional Transmission web UI auth. Leave blank for no auth.
32+
TRANSMISSION_RPC_USERNAME=
33+
TRANSMISSION_RPC_PASSWORD=
34+
1935
# ============ Grafana ============
2036
# Host port to expose Grafana on. Defaults to 3000 if unset.
2137
GRAFANA_PORT=3000

.github/workflows/compose-validate.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,18 @@ jobs:
2222
- name: Create .env from example
2323
run: |
2424
cp .env.example .env
25-
# Tracearr's compose entries use the ${VAR:?must be set} form, so
26-
# blank placeholders in .env.example would fail interpolation. Fill
27-
# them with throwaway values just for the validation step.
25+
# Several compose entries use the ${VAR:?must be set} form (Tracearr
26+
# secrets and OpenVPN credentials), which would fail interpolation
27+
# against the blank placeholders in .env.example. Fill them with
28+
# throwaway values just for the validation step.
2829
{
2930
echo "DB_PASSWORD=ci-validation"
3031
echo "JWT_SECRET=ci-validation"
3132
echo "COOKIE_SECRET=ci-validation"
33+
echo "OPENVPN_PROVIDER=ci-validation"
34+
echo "OPENVPN_CONFIG=ci-validation"
35+
echo "OPENVPN_USERNAME=ci-validation"
36+
echo "OPENVPN_PASSWORD=ci-validation"
3237
} >> .env
3338
3439
- name: docker compose config

CLAUDE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Radarr and Sonarr are deliberately on both `media_network` (so Seerr, Prowlarr,
3939

4040
**No Prometheus in the stack.** There is no `prometheus` service in `docker-compose.yml`. A starting-point config lives at `docs/prometheus.example.yml` for users who want to add Prometheus themselves — don't assume metrics are being scraped today.
4141

42-
**Transmission VPN is aspirational.** The README claims VPN support and `.env.example` has `OPENVPN_*` variables, but the active image is plain `linuxserver/transmission` with no VPN sidecar or `haugene/transmission-openvpn` config. If the user wants real VPN tunneling, that's a change, not a fix.
42+
**Transmission uses `haugene/transmission-openvpn` and won't start without VPN credentials.** The container runs an OpenVPN client internally; `OPENVPN_PROVIDER`, `OPENVPN_CONFIG`, `OPENVPN_USERNAME`, and `OPENVPN_PASSWORD` must all be set in `.env`. The compose service declares `cap_add: NET_ADMIN` and `devices: /dev/net/tun` for the OpenVPN client; the data volume is `/data` (haugene's convention), not `/config` like the linuxserver image. `LOCAL_NETWORK` (CIDR, default `192.168.0.0/16`) controls which destinations bypass the tunnel — if a user reports the web UI is unreachable, this is almost always the cause.
4343

4444
**Plex claim tokens expire in ~4 minutes.** `PLEX_CLAIM` must be set in `.env` immediately before `docker compose up -d` on first run. If the user reports a Plex auth issue on first boot, this is almost always why.
4545

@@ -58,7 +58,7 @@ The mounted config directory is `plex-meta-manager/config/`. Its structure is re
5858

5959
`.env.example` is the source of truth for what `.env` needs, and every var in it is actually consumed by `docker-compose.yml`. The values fall into two categories:
6060

61-
1. **Hard-required** (stack won't start): `DB_PASSWORD`, `JWT_SECRET`, `COOKIE_SECRET` (all Tracearr — they use the `${VAR:?must be set}` fail-fast form).
62-
2. **Effectively required for the feature to work**: `PUID`/`PGID`/`TZ`/`USERDIR` (everything), `PLEX_CLAIM` (first-boot only), `GRAFANA_PORT` (defaults to 3000 if unset), `PMM_*` (Kometa).
61+
1. **Hard-required** (stack won't start): `DB_PASSWORD`, `JWT_SECRET`, `COOKIE_SECRET` (Tracearr); `OPENVPN_PROVIDER`, `OPENVPN_CONFIG`, `OPENVPN_USERNAME`, `OPENVPN_PASSWORD` (Transmission VPN). All use the `${VAR:?must be set}` fail-fast form.
62+
2. **Effectively required for the feature to work**: `PUID`/`PGID`/`TZ`/`USERDIR` (everything), `PLEX_CLAIM` (first-boot only), `GRAFANA_PORT` (defaults to 3000 if unset), `LOCAL_NETWORK` (Transmission, defaults to `192.168.0.0/16`), `PMM_*` (Kometa), `TRANSMISSION_RPC_USERNAME`/`TRANSMISSION_RPC_PASSWORD` (optional web UI auth).
6363

64-
If a user mentions an env var not in this list (e.g. `OPENVPN_*`, `RADARR_API_KEY`, `DOCKER_INFLUXDB_*`), it's from an older version of the stack — not consumed today.
64+
If a user mentions an env var not in this list (e.g. `RADARR_API_KEY`, `DOCKER_INFLUXDB_*`, `PLEX_TOKEN`), it's from an older version of the stack — not consumed today.

README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ A complete, opinionated [Plex Media Server](https://www.plex.tv/) stack delivere
2929
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) (v2+)
3030
- A Plex account and a [claim token](https://www.plex.tv/claim) — generate this **immediately before** your first `docker compose up`; claim tokens expire roughly 4 minutes after they're issued
3131
- Values for `DB_PASSWORD`, `JWT_SECRET`, and `COOKIE_SECRET` in `.env` — Tracearr refuses to start without them and will fail the whole stack's `up` command
32+
- OpenVPN credentials from a [supported VPN provider](https://haugene.github.io/docker-transmission-openvpn/supported-providers/) (`OPENVPN_PROVIDER`, `OPENVPN_CONFIG`, `OPENVPN_USERNAME`, `OPENVPN_PASSWORD`) — Transmission tunnels all traffic through OpenVPN and won't start without them. See [Transmission VPN setup](#transmission-vpn-setup) for details
3233

3334
## Getting Started
3435

@@ -134,7 +135,7 @@ A ready-to-use [Kometa](https://kometa.wiki/) (Plex Meta Manager) configuration
134135

135136
| Service | Description | Port |
136137
|---------|-------------|------|
137-
| [Transmission](https://transmissionbt.com/) | Torrent client | `9091` |
138+
| [Transmission (VPN)](https://github.com/haugene/docker-transmission-openvpn) | Torrent client with OpenVPN tunnel — [setup notes](#transmission-vpn-setup) | `9091` |
138139

139140
### Monitoring
140141

@@ -176,6 +177,39 @@ Plex runs in host network mode for optimal streaming performance. Radarr and Son
176177

177178
Portainer mounts the host's Docker socket (`/var/run/docker.sock`) so it can manage every container. **This grants the Portainer UI root-equivalent access to the host** — anyone who logs in can stop, restart, or exec into any container, including those handling secrets. Set a strong admin password on first launch and don't expose port `9000` to the public internet.
178179

180+
## Transmission VPN setup
181+
182+
Transmission uses the [`haugene/transmission-openvpn`](https://github.com/haugene/docker-transmission-openvpn) image, which runs an OpenVPN client inside the container and tunnels all torrent traffic through it. The container fails to start without valid VPN credentials.
183+
184+
**Required `.env` values:**
185+
186+
| Variable | What it is |
187+
|----------|------------|
188+
| `OPENVPN_PROVIDER` | Provider name from the [supported list](https://haugene.github.io/docker-transmission-openvpn/supported-providers/) (e.g. `MULLVAD`, `PIA`, `NORDVPN`) |
189+
| `OPENVPN_CONFIG` | Server / region config name — provider-specific, see your provider's section in the linked docs |
190+
| `OPENVPN_USERNAME` | VPN account username (the one you use to log into the VPN, not the provider portal) |
191+
| `OPENVPN_PASSWORD` | VPN account password |
192+
| `LOCAL_NETWORK` | CIDR of your LAN (default `192.168.0.0/16`) — traffic to these subnets bypasses the tunnel so the web UI stays reachable |
193+
194+
**Optional:**
195+
196+
- `TRANSMISSION_RPC_USERNAME` / `TRANSMISSION_RPC_PASSWORD` — auth for the Transmission web UI. Leave blank for no auth.
197+
198+
**Required compose capabilities** (already configured in `docker-compose.yml`, mentioned here in case you fork):
199+
200+
- `cap_add: [NET_ADMIN]`
201+
- `devices: [/dev/net/tun]`
202+
203+
**Verifying the tunnel works:**
204+
205+
```bash
206+
docker compose exec transmission curl -s https://ipinfo.io | grep -E '"(ip|country)"'
207+
```
208+
209+
The IP and country in the response should match your VPN exit, not your home connection. If they match your home IP, the tunnel is not active — check `docker compose logs transmission` for OpenVPN errors.
210+
211+
**If the web UI on `:9091` is unreachable:** `LOCAL_NETWORK` probably doesn't cover the subnet your machine is on. Add your subnet (e.g. `192.168.1.0/24`) to `LOCAL_NETWORK`, comma-separated if you need multiple ranges, and restart the container.
212+
179213
## Kometa Configuration
180214

181215
The `plex-meta-manager/config/` directory contains a ready-to-use [Kometa](https://kometa.wiki/) configuration. Kometa itself is not in `docker-compose.yml` — run it as a one-shot container on whatever schedule you prefer (cron, systemd timer, or a separate compose file):

docker-compose.yml

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,18 +182,34 @@ services:
182182
restart: unless-stopped
183183

184184
# ============ DOWNLOADING ============
185+
# haugene/transmission-openvpn tunnels all transmission traffic through an
186+
# OpenVPN connection. NET_ADMIN + /dev/net/tun are required for the
187+
# in-container OpenVPN client. LOCAL_NETWORK must include your LAN CIDR
188+
# or the web UI on port 9091 will be unreachable from your machine.
185189
transmission:
186190
container_name: transmission
187-
image: linuxserver/transmission
188-
environment:
189-
- TRANSMISSION_RPC_HOST_WHITELIST=192.168.86.*
190-
- TZ=${TZ}
191+
image: haugene/transmission-openvpn
192+
cap_add:
193+
- NET_ADMIN
194+
devices:
195+
- /dev/net/tun
191196
networks:
192197
- download_network
193198
ports:
194199
- "9091:9091"
200+
environment:
201+
- PUID=${PUID}
202+
- PGID=${PGID}
203+
- TZ=${TZ}
204+
- OPENVPN_PROVIDER=${OPENVPN_PROVIDER:?OPENVPN_PROVIDER must be set}
205+
- OPENVPN_CONFIG=${OPENVPN_CONFIG:?OPENVPN_CONFIG must be set}
206+
- OPENVPN_USERNAME=${OPENVPN_USERNAME:?OPENVPN_USERNAME must be set}
207+
- OPENVPN_PASSWORD=${OPENVPN_PASSWORD:?OPENVPN_PASSWORD must be set}
208+
- LOCAL_NETWORK=${LOCAL_NETWORK:-192.168.0.0/16}
209+
- TRANSMISSION_RPC_USERNAME=${TRANSMISSION_RPC_USERNAME:-}
210+
- TRANSMISSION_RPC_PASSWORD=${TRANSMISSION_RPC_PASSWORD:-}
195211
volumes:
196-
- ${USERDIR}/transmission/config:/config
212+
- ${USERDIR}/transmission/data:/data
197213
restart: unless-stopped
198214

199215
# ============ ERROR MONITORING ============

0 commit comments

Comments
 (0)