What is it
Using docker to deploy JOAL behind a VPN and accessing the services from a reverse proxy
This file is not a guide on how to use or configure JOAL you must be confident what you are doing and understand what's going on
Requirements
- Docker 18.06.0+
- docker-compose 1.22.0+
- an internet router that supports local loopback
- a DNS name (The DNS provider MUST resolves subdomains as well, see below). If you don't have a DNS you can use DuckDns, it works great and it's free.
Your DNS must resolve subdomain, by this sentence i mean that if your domains is bar.example.com, the address foo.bar.example.com should also resolve to your IP.
Let's talk dirty
This is the folder structure i'm using along that guide.
/home/you/data/
├─ joal/
│ ├─ clients/
│ ├─ torrents/
│ ├─ config.json
├─ openvpn/
│ ├─ vpn.ovpn
├─ traefik/
│ ├─ acme/
│ ├─ dynamics/
│ │ ├─ global.yaml
│ ├─ traefik.yaml
├─ .env
├─ docker-compose.yml
.env
This file is used to store variable automatically resolved by the by the docker-compose.
TZ=Europe/Paris
PUID=1000
PGID=1000
ROOT=/home/you/data
# Traefik config for letsencrypt dns challenge
DUCKDNS_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx
DOMAIN_NAME=xxxxxxxxxx.duckdns.org
#JOAL
JOAL_PATH_PREFIX=XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
JOAL_SECRET_TOKEN=XXXXXXXXXXXXXXXXX
Adapt to your wishes:
ROOT
: The base path for all the configuration (see tree above).DUCKDNS_TOKEN
: Your duckdns token (if using duckdns)DOMAIN_NAME
: Your domain nameJOAL_PATH_PREFIX
: joal path prefixJOAL_SECRET_TOKEN
: joal secret token
openvpn/vpn.ovpn
Your openvpn file, you can most likely have one from your VPN provider, if not... well find a proper VPN provider.
traefik/acme/
An empty folder but please create it yourself
traefik/dynamics/global.yaml
http:
middlewares:
gzip-compress:
compress:
excludedContentTypes:
- text/event-stream
basic-auth:
basicAuth:
users:
- "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
redirect-to-https:
redirectScheme:
scheme: https
permanent: true
tls:
options:
default:
minVersion: VersionTLS12
Replace to your wishes:
http.middleware.basic-auth.basicAuth.users
: This basic auth will be use to protect traefik webui. You must provide a basicauth a basic name:encoded-password pair if you don't know how to generate this couple use thi htpasswd-generator website
traefik/traefik.yaml
#log:
# level: DEBUG
global:
sendAnonymousUsage: false
checknewversion: true
api:
dashboard: true
providers:
docker:
exposedByDefault: false
file:
directory: /etc/traefik/dynamics
watch: false
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
tcp-qbittorrent:
address: ":6881"
udp-qbittorrent:
address: ":6881/udp"
certificatesResolvers:
letsencrypt:
acme:
email: "do-not-email@xxxxxxxxxxxxx.duckdns.org"
storage: /etc/traefik/acme/acme.json
caServer: "https://acme-v02.api.letsencrypt.org/directory"
# caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
dnsChallenge:
provider: duckdns
Adapt to your wishes:
certificatesResolvers.letsencrypt.email
docker-compose.yml
version: '3.7'
services:
vpn:
container_name: vpn
image: dperson/openvpn-client:latest
restart: always
devices:
- /dev/net/tun
cap_add:
- NET_ADMIN
volumes:
- ${ROOT}/openvpn:/vpn:ro
dns:
- "8.8.8.8"
- "8.8.4.4"
logging:
options:
max-size: "2m"
max-file: "3"
environment:
- FIREWALL
- TZ=${TZ}
traefik:
image: traefik:2.5.3
container_name: "traefik"
restart: always
volumes:
- ${ROOT}/traefik/traefik.yaml:/etc/traefik/traefik.yaml:ro
- ${ROOT}/traefik/dynamics:/etc/traefik/dynamics:ro
- ${ROOT}/traefik/acme:/etc/traefik/acme:rw
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- DUCKDNS_TOKEN=${DUCKDNS_TOKEN}
ports:
- "80:80"
- "443:443"
- "6881:6881"
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard-redir.rule=Host(`traefik.${DOMAIN_NAME}`)"
- "traefik.http.routers.dashboard-redir.entrypoints=web"
- "traefik.http.routers.dashboard-redir.middlewares=redirect-to-https@file"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.rule=Host(`traefik.${DOMAIN_NAME}`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.tls=true"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.dashboard.tls.domains[0].main=${DOMAIN_NAME}"
- "traefik.http.routers.dashboard.tls.domains[0].sans=*.${DOMAIN_NAME}"
- "traefik.http.routers.dashboard.middlewares=gzip-compress@file"
- "traefik.http.routers.dashboard.middlewares=basic-auth@file"
joal:
depends_on:
- vpn
- traefik
container_name: joal
image: anthonyraymond/joal:2.1.26
restart: always
network_mode: "service:vpn"
volumes:
- ${ROOT}/joal:/data
command: ["--joal-conf=/data", "--spring.main.web-environment=true", "--server.port=80", "--joal.ui.path.prefix=${JOAL_PATH_PREFIX}", "--joal.ui.secret-token=${JOAL_SECRET_TOKEN}"]
labels:
- "traefik.enable=true"
- "traefik.http.routers.joal-redir.rule=Host(`joal.${DOMAIN_NAME}`)"
- "traefik.http.routers.joal-redir.entrypoints=web"
- "traefik.http.routers.joal-redir.middlewares=redirect-to-https@file"
- "traefik.http.services.joal-service.loadbalancer.server.port=80"
- "traefik.http.routers.joal-router.entrypoints=websecure"
- "traefik.http.routers.joal-router.rule=Host(`joal.${DOMAIN_NAME}`)"
- "traefik.http.routers.joal-router.middlewares=gzip-compress@file"
- "traefik.http.routers.joal-router.service=joal-service"
- "traefik.http.routers.joal-router.tls=true"
qbittorrent:
depends_on:
- vpn
- traefik
container_name: qbittorrent
image: linuxserver/qbittorrent:unstable-version-4.4.0202106140855-7320-2bd5aca3aubuntu20.04.1
restart: always
network_mode: "service:vpn"
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
- WEBUI_PORT=8080
volumes:
- /etc/localtime:/etc/localtime:ro
- ${ROOT}/qbittorrent/config:/config:rw
- ${ROOT}/qbittorrent/downloads:/downloads:rw
- ${ROOT}/joal/torrents:/completed-torrents-files:rw
labels:
- "traefik.enable=true"
- "traefik.http.routers.qbittorrent-ui-redir.rule=Host(`qbittorrent.${DOMAIN_NAME}`)"
- "traefik.http.routers.qbittorrent-ui-redir.entrypoints=web"
- "traefik.http.routers.qbittorrent-ui-redir.middlewares=redirect-to-https@file"
- "traefik.http.services.qbittorrent-service.loadbalancer.server.port=8080"
- "traefik.http.routers.qbittorrent-ui-router.entrypoints=websecure"
- "traefik.http.routers.qbittorrent-ui-router.rule=Host(`qbittorrent.${DOMAIN_NAME}`)"
- "traefik.http.routers.qbittorrent-ui-router.middlewares=gzip-compress@file"
- "traefik.http.routers.qbittorrent-ui-router.service=qbittorrent-service"
- "traefik.http.routers.qbittorrent-ui-router.tls=true"
- "traefik.tcp.services.qbittorrent-tcp-service.loadbalancer.server.port=6881"
- "traefik.tcp.routers.qbittorrent-tcp-router.entrypoints=tcp-qbittorrent"
- "traefik.tcp.routers.qbittorrent-tcp-router.rule=HostSNI(`*`)"
- "traefik.tcp.routers.qbittorrent-tcp-router.service=qbittorrent-tcp-service"
- "traefik.udp.services.qbittorrent-udp-service.loadbalancer.server.port=6881"
- "traefik.udp.routers.qbittorrent-udp-router.service=qbittorrent-udp-service"
- "traefik.udp.routers.qbittorrent-udp-router.entrypoints=udp-qbittorrent"
Nothing to change in this file because every variables are extracted to .env
Done !
⚠️ First launch will take a LONG time because traefik need to chat with letsencrypt to create your certificate. Be patient and dont stop & restart the containers too much before the certificates are generated, otherwise you will hit the letencrypt request limit. Check the traefik logs to follow the progress
After obtaining the certificates you'll be able to reach your services at (assuming you have the domain name example.duckdns.org
):
https:traefik.example.duckdns.org (you'll be presented a basic login form, use the basic auth credential generated earlier)
https:joal.example.duckdns.org (connection settings within webui: serverAddress=joal.example.duckdns.org, serverPort=443, path prefix and secret token are the value you haved defined in the .env file)
https:qbitorrent.example.duckdns.org
Going further
If you want to, adding sonarr
, radarr
, jackett
and flaresolver
is a matter of second. Simply add those to yout docker-compose:
sonarr:
depends_on:
- traefik
- qbittorrent
container_name: sonarr
image: linuxserver/sonarr:version-3.0.6.1265
restart: always
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- /etc/localtime:/etc/localtime:ro
- ${ROOT}/sonarr/config:/config:rw
- /xxxxxxxxxxxxxxxxxxxxxxxxxxx/path/to/your/tv/show/folder:/tv
- ${ROOT}/qbittorrent/downloads:/downloads
labels:
- "traefik.enable=true"
- "traefik.http.routers.sonarr-redir.rule=Host(`sonarr.${DOMAIN_NAME}`)"
- "traefik.http.routers.sonarr-redir.entrypoints=web"
- "traefik.http.routers.sonarr-redir.middlewares=redirect-to-https@file"
- "traefik.http.services.sonarr-service.loadbalancer.server.port=8989"
- "traefik.http.routers.sonarr-router.entrypoints=websecure"
- "traefik.http.routers.sonarr-router.rule=Host(`sonarr.${DOMAIN_NAME}`)"
- "traefik.http.routers.sonarr-router.middlewares=gzip-compress@file"
- "traefik.http.routers.sonarr-router.service=sonarr-service"
- "traefik.http.routers.sonarr-router.tls=true"
radarr:
depends_on:
- traefik
- qbittorrent
container_name: radarr
image: linuxserver/radarr:version-3.2.2.5080
restart: always
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- /etc/localtime:/etc/localtime:ro
- ${ROOT}/radarr/config:/config:rw
- /xxxxxxxxxxxxxxxxxxxxxxxxxxx/path/to/your/movies/folder:/movies
- ${ROOT}/qbittorrent/downloads:/downloads
labels:
- "traefik.enable=true"
- "traefik.http.routers.radarr-redir.rule=Host(`radarr.${DOMAIN_NAME}`)"
- "traefik.http.routers.radarr-redir.entrypoints=web"
- "traefik.http.routers.radarr-redir.middlewares=redirect-to-https@file"
- "traefik.http.services.radarr-service.loadbalancer.server.port=7878"
- "traefik.http.routers.radarr-router.entrypoints=websecure"
- "traefik.http.routers.radarr-router.rule=Host(`radarr.${DOMAIN_NAME}`)"
- "traefik.http.routers.radarr-router.middlewares=gzip-compress@file"
- "traefik.http.routers.radarr-router.service=radarr-service"
- "traefik.http.routers.radarr-router.tls=true"
flaresolverr:
container_name: flaresolverr
image: flaresolverr/flaresolverr:v1.2.9
restart: always
network_mode: "service:vpn"
environment:
- LOG_LEVEL=info
- LOG_HTML=false
- CAPTCHA_SOLVER=none
jackett:
depends_on:
- traefik
- flaresolverr
container_name: jackett
image: linuxserver/jackett:version-v0.18.795
restart: always
network_mode: "service:vpn"
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=${TZ}
volumes:
- /etc/localtime:/etc/localtime:ro
- ${ROOT}/jackett/config:/config:rw
labels:
- "traefik.enable=true"
- "traefik.http.routers.jackett-redir.rule=Host(`jackett.${DOMAIN_NAME}`)"
- "traefik.http.routers.jackett-redir.entrypoints=web"
- "traefik.http.routers.jackett-redir.middlewares=redirect-to-https@file"
- "traefik.http.services.jackett-service.loadbalancer.server.port=9117"
- "traefik.http.routers.jackett-router.entrypoints=websecure"
- "traefik.http.routers.jackett-router.rule=Host(`jackett.${DOMAIN_NAME}`)"
- "traefik.http.routers.jackett-router.middlewares=gzip-compress@file"
- "traefik.http.routers.jackett-router.service=jackett-service"
- "traefik.http.routers.jackett-router.tls=true"