A fully self-managed home lab built around a Lenovo Y50-70 media server, an Intel NUC7i7DNHE LLM server, and a RackNerd VPS acting as a secure WireGuard gateway. All services are containerised with Docker, exposed via Nginx with Let's Encrypt SSL, and accessible through bheem.pw.
| Device | Specs | Role | IP |
|---|---|---|---|
| Lenovo Y50-70 | Core i7, 16GB RAM, 1TB HDD, GTX 960M | Media server + services | 192.168.1.90 |
| Intel NUC7i7DNHE | Core i7-8650U, 16GB RAM | Local LLM server | 192.168.1.178 |
| RackNerd VPS | 2.5GB RAM, KVM, Ubuntu 24.04 | WireGuard gateway + Nginx reverse proxy | 108.174.61.246 |
bheem.wlp8s0.sudo lvextend -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv
sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv
bheemllm.eno1.sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
Internet
↓
AT&T BGW320 (IP passthrough mode)
↓
ASUS GT-AX11000 (107.206.70.206 · single NAT)
├── Lenovo Y50-70 (192.168.1.90) — media + services
│ ↕ WireGuard tunnel (10.0.0.1 ↔ 10.0.0.2)
│ RackNerd VPS (108.174.61.246) — WireGuard + Nginx
└── Intel NUC7i7DNHE (192.168.1.178) — LLM server
The Y50 maintains a persistent WireGuard tunnel to the RackNerd VPS for remote access and service exposure. All external traffic destined for bheem.pw hits the VPS first, then routes back through the tunnel to the Y50's local services.
10.0.0.110.0.0.2VPS WireGuard config
[Interface]
PrivateKey = <vps_private_key>
Address = 10.0.0.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = <y50_public_key>
AllowedIPs = 10.0.0.2/32
Y50 WireGuard config
[Interface]
PrivateKey = <y50_private_key>
Address = 10.0.0.2/24
[Peer]
PublicKey = <vps_public_key>
Endpoint = 108.174.61.246:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
All exposed via Nginx on the VPS with Let’s Encrypt SSL certificates. Each service has its own site config in /etc/nginx/sites-available/.
| Service | URL | Port |
|---|---|---|
| Dashy | https://dashy.bheem.pw |
4000 |
| Vaultwarden | https://vaultwarden.bheem.pw |
8888 |
| BentoPDF | https://bentopdf.bheem.pw |
8889 |
| Tandoor | https://tandoor.bheem.pw |
8891 |
| BabyBuddy | https://babybuddy.bheem.pw |
8090 |
| Seerr | https://seerr.bheem.pw |
5055 |
| Wiki.js | https://wiki.bheem.pw |
3001 |
server {
listen 80;
server_name service.bheem.pw;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name service.bheem.pw;
ssl_certificate /etc/letsencrypt/live/service.bheem.pw/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/service.bheem.pw/privkey.pem;
location / {
proxy_pass http://10.0.0.2:<port>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
All services run as separate Docker Compose stacks under ~/media/ (Arr stack) or their own home directories. Docker version: 29.4.1.
~/media/)| Service | Port | Purpose |
|---|---|---|
| qBittorrent | 8080 | Download client (behind Gluetun VPN kill switch) |
| Gluetun | — | NordVPN OpenVPN kill switch for qBittorrent |
| Prowlarr | 9696 | Indexer manager |
| FlareSolverr | 8191 | Cloudflare bypass for Prowlarr indexers |
| Radarr | 7878 | Movie automation |
| Sonarr | 8989 | TV show automation |
| Bazarr | 6767 | Automatic subtitle downloads |
| Profilarr | 6868 | Quality profile sync for Radarr/Sonarr |
| Jellyfin | 8096 | Media streaming (VAAPI hardware acceleration) |
| Tdarr | 8265/8266 | Background HEVC transcoding |
| Seerr | 5055 | Media request manager |
| Watchtower | — | Auto container updates (daily at 4am) |
| Service | Directory | Port | Notes |
|---|---|---|---|
| Vaultwarden | ~/vaultwarden/ |
8888/3012 | Argon2 admin token via .env, signups disabled |
| BentoPDF | ~/bentopdf/ |
8889 | Privacy-first PDF toolbox |
| BabyBuddy | ~/babybuddy/ |
8090 | Baby tracking |
| Tandoor | ~/tandoor/ |
8891 | Recipe manager with PostgreSQL backend |
| Dashy | ~/dashy/ |
4000 | Home lab dashboard |
| Pi-hole | ~/pihole/ |
80/admin | Network-wide ad blocking (host network mode); Admin UI at http://192.168.1.90/admin |
Gluetun + qBittorrent kill switch
qbittorrent:
network_mode: service:gluetun
Pi-hole
Pi-hole runs in host network mode and acts as the primary DNS server for all devices via the ASUS router DHCP settings.
v6 password env var:
FTLCONF_webserver_api_password: "yourpassword"
ASUS router DNS config (LAN → DHCP Server):
192.168.1.908.8.8.8 (fallback)Upstream DNS (Pi-hole Settings → DNS):
Known issue: systemd-resolved conflict
Ubuntu's systemd-resolved conflicts with Pi-hole on port 53 and must be disabled. Symptoms: DNS server failure in dashboard, dig timeouts, Pi-hole only on UDP not TCP.
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
sudo rm /etc/resolv.conf
echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf
cd ~/pihole && docker compose restart
Verify with dig google.com @192.168.1.90 — should return SERVER: 192.168.1.90#53.
Troubleshooting:
| Symptom | Cause | Fix |
|---|---|---|
| DNS server failure in dashboard | systemd-resolved conflict | See fix above |
| High load warning in logs | Y50 CPU under load | Check htop, likely Tdarr or Jellyfin transcoding |
| Devices not using Pi-hole | Router DNS not propagated | Reconnect devices to WiFi to pick up new DHCP settings |
| Pi-hole admin unreachable | Container down | cd ~/pihole && docker compose up -d |
Vaultwarden Argon2 token
$ signs in the hash must be escaped as $$ in Docker Compose environment blocks..env file to avoid escaping issues:ADMIN_TOKEN=$argon2id$v=19$...
Tandoor
/opt/recipes/nginx/conf.d/Recipes.conf to use proxy_pass http://localhost:8080 instead of the default container name reference.Jellyfin hardware acceleration
/dev/dri:/dev/dri device mount./dev/dri/renderD128.Tdarr flow
| Service | Port | Purpose |
|---|---|---|
| Ollama | 11434 | Local LLM inference |
| Open WebUI | 3000 | Browser chat interface |
/home/bheem/wikijs.3001 on the host to Wiki.js port 3000 inside the container.https://wiki.bheem.pw.services:
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: wiki
POSTGRES_USER: wikijs
POSTGRES_PASSWORD: bScnB5ih&b7o
volumes:
- ./db-data:/var/lib/postgresql/data
wiki:
image: ghcr.io/requarks/wiki:2
depends_on:
- db
init: true
environment:
DB_TYPE: postgres
DB_HOST: db
DB_PORT: 5432
DB_USER: wikijs
DB_PASS: bScnB5ih&b7o
DB_NAME: wiki
ports:
- 3001:3000
restart: unless-stopped