I have tailscale installed as root on debian xfce server with a docker compose container area including an arr stack qbittorent etc. I want to set up nginx proxy manager to give me access outside of my house from a domain that I have bought through namecheap. Walk me through installation and explain best practises
You already have the hard parts in place: Debian + Docker Compose + Tailscale. The cleanest and safest setup is:
I strongly recommend this architecture:
Internet ↓Domain (Cloudflare optional but recommended) ↓Router Port Forward 80/443 ↓Nginx Proxy Manager ↓Docker services
NOT:
For those:
You have two good options.
Example:
sonarr.yourdomain.comradarr.yourdomain.comjellyfin.yourdomain.com
These point directly to your home IP.
Pros:
Cons:
Use:
Pros:
I strongly recommend this.
Flow:
Namecheap domain ↓ nameserversCloudflare DNS ↓Your home server
You still use NPM locally.
Forward ONLY:
| External | Internal |
|---|---|
| 80 | server-ip:80 |
| 443 | server-ip:443 |
Do NOT expose:
For SSH:
Create a shared proxy network.
docker network create proxy
Every container that NPM should reach joins this network.
Create:
mkdir -p ~/docker/npmcd ~/docker/npm
Create:
docker-compose.yml
services: npm: image: 'jc21/nginx-proxy-manager:latest' container_name: npm restart: unless-stopped ports: - "80:80" - "81:81" - "443:443" volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt networks: - proxynetworks: proxy: external: true
Start:
docker compose up -d
Open:
http://YOUR_SERVER_IP:81
Default login:
Email: admin@example.comPassword: changeme
Change immediately.
Example Sonarr compose:
services: sonarr: image: lscr.io/linuxserver/sonarr:latest container_name: sonarr networks: - proxynetworks: proxy: external: true
Now NPM can reach it internally.
In Namecheap or Cloudflare:
A record:sonarr -> YOUR_HOME_IPA record:jellyfin -> YOUR_HOME_IP
Or wildcard:
*.yourdomain.com
Inside NPM:
Example:
sonarr.yourdomain.com
sonarr
(Docker container name)
8989
Enable:
SSL tab:
NPM automatically gets and renews certificates.
Do not expose default logins.
For apps lacking auth:
Eventually this becomes:
Internet ↓NPM ↓Authentik/Authelia SSO ↓Services
Very common homelab architecture.
Best practice:
qBittorrent → Tailscale only
Do NOT expose publicly unless absolutely necessary.
Advanced but ideal:
proxy networkinternal networkvpn network
Do not put everything on one giant network.
If using Cloudflare:
Highly recommended on public servers.
Install:
sudo apt install fail2ban
Protects against brute force attacks.
On your router:
This is the “good homelab” setup:
Internet ↓Cloudflare ↓Router 80/443 ↓Nginx Proxy Manager ↓Authentik/Authelia ↓Apps
Private apps:
Public apps:
Tailscale is still extremely valuable.
Use it for:
Use public reverse proxy ONLY for services that benefit from browser access externally.
If your qBittorrent container uses a VPN container (Gluetun, Wireguard, OpenVPN), DO NOT put NPM on that same network stack.
Instead:
NPM → normal bridge networkqBittorrent → VPN network
Otherwise:
jellyfin.domain.com PUBLICrequest.domain.com PUBLICdash.domain.com PUBLICsonarr.domain.com TAILSCALE ONLYradarr.domain.com TAILSCALE ONLYqb.domain.com TAILSCALE ONLYportainer.domain.com TAILSCALE ONLY
That is very close to industry best practice for homelabs.
This can expose services publicly without port forwarding.
Pros:
Cons:
For your setup, NPM + domain is the better long-term architecture.
In order:
proxy networkGiven:
I would use:
That setup scales extremely well and stays manageable.
If you want, I can also walk you through:
Good question — this is exactly where people either overcomplicate things or accidentally break their whole stack.
You’re thinking about it the right way: only some containers should be on the proxy network.
You define the network once at the bottom of the compose file, like this:
networks: proxy: external: true
Then you decide per container whether it joins that network.
These need:
networks: - proxy
Examples:
These should NOT be on proxy network at all.
Example:
qbittorrent: image: lscr.io/linuxserver/qbittorrent:latest container_name: qbittorrent environment: - PUID=1000 - PGID=1000 - TZ=Europe/Paris volumes: - ./qbittorrent:/config - /home/tgillard/media-stack:/downloads ports: - 8090:8090 networks: - default restart: unless-stopped
| Service type | Networks |
|---|---|
| Public web apps | proxy + default |
| Internal apps | default only |
| qBittorrent | default only |
Because:
Nothing breaks immediately — but:
So just don’t.
Think of Docker networks like rooms:
default = private house network (everything lives here)proxy = front door for web apps
So:
networks: - proxy - default
networks: - default
You already added:
networks: proxy: external: true
That is CORRECT — and stays ONLY once at the bottom.
Then:
proxy under services that need public accessIf you want next step, I can show you a perfect “final homelab compose layout” where:
That’s the stage where your setup becomes really solid.
does that mean I should have two different docker compose files?
Not necessarily — and in most homelabs, you don’t need two compose files.
You have 3 valid patterns, but only 1 is ideal for your stage.
You keep:
proxy networkExample structure:
docker-compose.yml
Inside it:
This is the simplest and most common homelab setup.
You define networks ONCE:
networks: proxy: external: true
Then each service chooses:
networks: - proxy - default
networks: - default
media-stack.ymlpublic-stack.ymlBoth share the same external proxy network.
✔ Cleaner mental separation
❌ More annoying to manage
❌ More duplication risk
Only worth it if:
Not needed for you.
Even with ONE compose file:
👉 You are NOT mixing everything together in a bad way
Because:
So it's still secure and structured.
Keep ONE file, but logically grouped:
services: # ---- PUBLIC ---- jellyfin: immich: wiki: # ---- PRIVATE ---- qbittorrent: sonarr: radarr: prowlarr:networks: proxy: external: true
This gives you:
Think of Docker like this:
ONE compose file = your house blueprintnetworks = who can talk to the front doorproxy = public entrance (NPM)default = internal rooms
You don’t need multiple houses yet.
Only if:
You’re not there yet.
👉 No, you do NOT need two compose files.
👉 Use ONE compose file + multiple networks.
That is the cleanest and most standard homelab approach for your setup right now.
If you want, next I can help you:
so if I put in networks:Proxy external : true at the bottom of the .yml but for qbittorent I leave networks : default it won't run through proxy?
Correct — but there’s a subtle misunderstanding to clear up.
Adding a proxy network does not automatically “route traffic through a proxy.” It only controls which containers can talk to which other containers.
Let’s make this crystal clear.
proxy network actually doesWhen you define:
networks: proxy: external: true
you are simply creating a shared Docker network that allows:
to see and reach each other internally
❌ Traffic is forced through NPM
❌ Internet traffic is redirected
❌ qBittorrent becomes “proxied”
❌ anything is automatically exposed publicly
default:Example qBittorrent:
networks: - default
👉 It means:
8090:8090)So yes — it stays outside your reverse proxy setup.
proxy:Example Jellyfin:
networks: - proxy - default
👉 It means:
jellyfin.yourdomain.comThink of it like this:
proxy network = "NPM can see this service"default network = "normal Docker internal networking"
NOT:
proxy network = "traffic goes through proxy"
“If qbittorrent is only on default, will it run through proxy?”
👉 No — and that is exactly what you want.
qBittorrent will:
jellyfin → proxy + defaultimmich → proxy + defaultwiki → proxy + default
Accessible via:
https://jellyfin.yourdomain.com
qbittorrent → default onlysonarr → default onlyradarr → default only
Accessible via:
http://192.168.x.x:8090orTailscale IP
Docker networks do NOT:
They only control:
👉 container-to-container visibility
Yes:
proxy: external: true at bottom = correctdefault only = correctIf you want next step, I can help you design the clean “final architecture” where:
That’s where your setup becomes really solid and secure.