Network Topology¶
Physical Infrastructure¶
The infrastructure runs inside a Proxmox virtualized environment with multiple network layers between the internet and Docker containers.
Tailscale Network Layer¶
Tailscale provides an overlay network that bypasses traditional port forwarding requirements and enables secure access to services.
Tailscale Architecture¶
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Tailscale Network │
│ │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ Your Device │ │ Docker VM │ │
│ │ 100.x.x.x │ ←── WireGuard Encrypted Tunnel ──→ │ dockerhost │ │
│ │ │ │ 100.98.20.43 │ │
│ └─────────────────┘ └──────────────────┘ │
│ │
│ Direct P2P Connection (when possible) or via DERP Relay │
└─────────────────────────────────────────────────────────────────────────────────┘
Key Benefits¶
- No Port Forwarding Required: Access services without configuring Proxmox iptables
- End-to-End Encryption: All traffic encrypted via WireGuard protocol
- Simplified Access: Use Tailscale IPs or MagicDNS names instead of public IPs
- NAT Traversal: Works behind firewalls and complex network setups
- Zero-Trust Security: Only authorized devices can connect
Access Patterns¶
Via Tailscale (Recommended for Admin Access)¶
Your Device → Tailscale VPN → dockerhost (100.98.20.43) → Docker Services
↓
No Proxmox port forwarding needed!
Traditional Public Access (For End Users)¶
Service Access Examples¶
-
SSH Access:
# Via Tailscale (no port forwarding needed) ssh [email protected] # or ssh [email protected] # Traditional (requires port 22 forwarding) ssh [email protected] -
Traefik Dashboard:
-
Direct Database Access:
Tailscale Integration Points¶
┌──────────────────────────────────────────────────────────────────────────────┐
│ Docker VM with Tailscale │
│ │
│ Network Interfaces: │
│ ├── eth0: 10.10.10.10/24 (Proxmox NAT) │
│ ├── tailscale0: 100.98.20.43 (Tailscale VPN) │
│ └── docker0: 172.17.0.1/16 (Docker Bridge) │
│ │
│ Access Methods: │
│ 1. Public Internet → Proxmox NAT → eth0 (requires port forwarding) │
│ 2. Tailscale VPN → tailscale0 (no port forwarding needed) │
│ │
│ Services accessible via both: │
│ - HTTP/HTTPS (via Traefik) │
│ - SSH │
│ - Any Docker exposed port │
└──────────────────────────────────────────────────────────────────────────────┘
DNS Considerations¶
When using Tailscale with AdGuard:
- MagicDNS: Tailscale provides automatic DNS names (e.g., dockerhost.tail92c8e3.ts.net)
- Custom DNS: You can configure Tailscale to use your AdGuard instance
- Split DNS: Define custom domains that resolve only within your Tailnet
Security Benefits¶
- No Public Port Exposure: Admin interfaces remain completely private
- Device Authorization: Only approved devices can join the Tailnet
- ACLs: Fine-grained access control between Tailscale nodes
- Audit Logs: Track all connections and access attempts
- Key Rotation: Automatic key management and rotation
Complete Network Flow¶
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Internet (Public) │
│ │ │
│ ↓ │
│ Hetzner Public IP: 157.180.63.15 │
└──────────────────────────────────┬─────────────────────────────────────────────┘
│
┌──────────────────────────────────┴─────────────────────────────────────────────┐
│ Proxmox Host (Hetzner Server) │
│ │
│ Network Interfaces: │
│ ├── enp7s0: 157.180.63.15/26 (Public) │
│ └── vmbr0: 10.10.10.1/24 (Internal Bridge) │
│ │
│ NAT & Port Forwarding Rules: │
│ ├── 157.180.63.15:80 → 10.10.10.10:80 (HTTP) │
│ ├── 157.180.63.15:443 → 10.10.10.10:443 (HTTPS) │
│ ├── 157.180.63.15:22 → 10.10.10.10:22 (SSH) │
│ └── 157.180.63.15:51820 → 10.10.10.10:51820 (WireGuard/Optional) │
│ │
└──────────────────────────────────┬─────────────────────────────────────────────┘
│ NAT
↓
┌──────────────────────────────────┴─────────────────────────────────────────────┐
│ Docker VM (10.10.10.10) - Ubuntu │
│ │
│ Network Configuration: │
│ ├── eth0: 10.10.10.10/24 (From Proxmox) │
│ ├── docker0: 172.17.0.1/16 (Docker Default Bridge) │
│ ├── traefik_proxy: 172.18.0.0/16 (External Services) │
│ └── supabase_internal: 172.19.0.0/16 (Database Network) │
│ │
│ Services Running: │
│ ├── Traefik (Reverse Proxy) - Ports 80, 443 │
│ ├── Docker Daemon │
│ └── SSH Server │
└────────────────────────────────────────────────────────────────────────────────┘
Network Hierarchy¶
Internet Traffic Flow:
══════════════════════
1. User → DNS (*.rbnk.uk) → 157.180.63.15
2. Hetzner Server → Proxmox iptables NAT → 10.10.10.10
3. Docker VM → Traefik (ports 80/443) → Docker Networks
4. Docker Networks → Containers (by hostname/labels)
Example: https://supabase.rbnk.uk
────────────────────────────────
User Browser
↓ (DNS resolves to 157.180.63.15)
Hetzner Public IP:443
↓ (iptables DNAT)
Proxmox NAT → 10.10.10.10:443
↓ (VM receives traffic)
Traefik Container
↓ (reads Host header)
↓ (routes based on rules)
Supabase Studio Container
Port Forwarding Requirements¶
Essential Ports (Always Required)¶
| Service | External Port | Internal Port | Protocol | Purpose |
|---|---|---|---|---|
| HTTP | 80 | 80 | TCP | HTTP traffic (redirects to HTTPS) |
| HTTPS | 443 | 443 | TCP | All web services via Traefik |
| SSH | 22 | 22 | TCP | Remote administration |
Optional Ports (Service-Specific)¶
| Service | External Port | Internal Port | Protocol | Purpose |
|---|---|---|---|---|
| WireGuard | 51820 | 51820 | UDP | VPN access (if needed) |
| PostgreSQL | 5432 | 5432 | TCP | Direct DB access (NOT recommended) |
| Traefik Dashboard | 8080 | 8080 | TCP | Internal only (no forward) |
Proxmox NAT Configuration¶
Add to /etc/network/interfaces on Proxmox host:
# NAT for Docker VM
post-up echo 1 > /proc/sys/net/ipv4/ip_forward
post-up iptables -t nat -A POSTROUTING -s '10.10.10.0/24' -o enp7s0 -j MASQUERADE
# Port forwarding for Docker VM
post-up iptables -t nat -A PREROUTING -i enp7s0 -p tcp --dport 80 -j DNAT --to 10.10.10.10:80
post-up iptables -t nat -A PREROUTING -i enp7s0 -p tcp --dport 443 -j DNAT --to 10.10.10.10:443
post-up iptables -t nat -A PREROUTING -i enp7s0 -p tcp --dport 22 -j DNAT --to 10.10.10.10:22
# Cleanup rules on down
post-down iptables -t nat -D POSTROUTING -s '10.10.10.0/24' -o enp7s0 -j MASQUERADE
post-down iptables -t nat -D PREROUTING -i enp7s0 -p tcp --dport 80 -j DNAT --to 10.10.10.10:80
post-down iptables -t nat -D PREROUTING -i enp7s0 -p tcp --dport 443 -j DNAT --to 10.10.10.10:443
post-down iptables -t nat -D PREROUTING -i enp7s0 -p tcp --dport 22 -j DNAT --to 10.10.10.10:22
Docker Networks Overview¶
The infrastructure uses Docker's bridge networks to isolate and connect services appropriately. All Docker networks operate within the VM (10.10.10.10).
┌─────────────────────────────────────────────────────────────────┐
│ Docker VM (10.10.10.10) - Network Context │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Docker Host Network (172.17.0.1) │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ traefik_proxy (172.18.0.0/16) - External │ │ │
│ │ │ Connected to: eth0 for outbound NAT │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────────┐ │ │ │
│ │ │ │ Traefik │ │ LiteLLM │ │ n8n │ │ Open WebUI │ │ │ │
│ │ │ │ .18.2 │ │ .18.3 │ │ .18.4 │ │ .18.5 │ │ │ │
│ │ │ └─────────┘ └──────────┘ └─────────┘ └──────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
│ │ │ │ Gitea │ │ Rclone │ │Prometheus│ │ Grafana │ │ │ │
│ │ │ │ .18.6 │ │ .18.7 │ │ .18.8 │ │ .18.9 │ │ │ │
│ │ │ └─────────┘ └─────┬────┘ └──────────┘ └──────────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ └──→ Cloudflare R2 (External) │ │ │
│ │ │ ↑ ↑ ↑ ↑ │ │ │
│ │ └───────┼────────────┼───────────┼─────────────┼────────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌───────┼────────────┼───────────┼─────────────┼────────┐ │ │
│ │ │ ↓ ↓ ↓ ↓ │ │ │
│ │ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────────┐ │ │ │
│ │ │ │ Auth │ │ Rest │ │ Storage │ │ Studio │ │ │ │
│ │ │ │ .19.3 │ │ .19.4 │ │ .19.5 │ │ .19.6 │ │ │ │
│ │ │ └────┬────┘ └────┬─────┘ └────┬────┘ └──────┬───────┘ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ └───────────┴────────────┴──────────────┘ │ │ │
│ │ │ ↓ │ │ │
│ │ │ ┌─────────────┐ │ │ │
│ │ │ │ PostgreSQL │ │ │ │
│ │ │ │ .19.2 │ │ │ │
│ │ │ └─────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ supabase_internal (172.19.0.0/16) - Internal │ │ │
│ │ │ Isolated: No direct internet access │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ cache_redis_network (172.20.0.0/16) - Redis │ │ │
│ │ │ Cache layer for ephemeral data │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────┐ ┌─────────┐ ┌─────────────┐ │ │ │
│ │ │ │ Cache Redis │───│ Gitea │───│ Stremthru │ │ │ │
│ │ │ │ .20.2 │ │ .20.10 │ │ .20.11 │ │ │ │
│ │ │ └─────────────┘ └─────────┘ └─────────────┘ │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ session_redis_network (172.21.0.0/16) - Redis │ │ │
│ │ │ Session storage for persistent user data │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┐ ┌─────────┐ │ │ │
│ │ │ │Session Redis │───│ Gitea │ │ │ │
│ │ │ │ .21.2 │ │ .21.10 │ │ │ │
│ │ │ └──────────────┘ └─────────┘ │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ VM Host Network Services: │ │
│ │ └── Ollama (0.0.0.0:11434) ← Via 172.17.0.1 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ External Access: All traffic enters via Traefik on ports │
│ 80/443, forwarded from Proxmox NAT (157.180.63.15) │
└─────────────────────────────────────────────────────────────────┘
Network Definitions¶
traefik_proxy (External Network)¶
Purpose: Services that need public internet access
Configuration:
IP Range: 172.18.0.0/16 - Gateway: 172.18.0.1 - Traefik: 172.18.0.2 - Services: 172.18.0.3+
Connected Services: - Traefik (reverse proxy) - All public-facing services - Supabase services (dual-homed) - Rclone (web GUI, cloud sync, and R2 infrastructure backups) - Prometheus and Grafana (monitoring stack)
supabase_internal (Internal Network)¶
Purpose: Database isolation and internal communication
Configuration:
IP Range: 172.19.0.0/16 - Gateway: 172.19.0.1 - PostgreSQL: 172.19.0.2 - Internal services: 172.19.0.3+
Connected Services: - PostgreSQL (database) - All Supabase components - No external access
cache_redis_network (Cache Network)¶
Purpose: Shared Redis cache for ephemeral data across services
Configuration:
IP Range: 172.20.0.0/16 - Gateway: 172.20.0.1 - Cache Redis: 172.20.0.2 - Connected services: 172.20.0.3+
Connected Services: - cache-redis (Redis 7.4 instance) - Gitea (Git object caching) - Stremthru (API response caching) - Any service needing temporary data storage
Cache Characteristics:
- Persistence: Disabled (no disk writes)
- Memory Policy: allkeys-lru (evict least recently used)
- Max Memory: 4GB (configurable)
- Use Cases: API responses, computed results, temporary data
session_redis_network (Session Network)¶
Purpose: Shared Redis storage for persistent session data
Configuration:
IP Range: 172.21.0.0/16 - Gateway: 172.21.0.1 - Session Redis: 172.21.0.2 - Connected services: 172.21.0.3+
Connected Services: - session-redis (Redis 7.4 instance) - Gitea (web session storage) - Future web applications requiring session management
Session Characteristics:
- Persistence: Enabled (AOF + RDB snapshots)
- Memory Policy: volatile-lru (evict expired keys first)
- Max Memory: 1GB (configurable)
- Use Cases: User sessions, authentication tokens, persistent state
Service Communication Patterns¶
1. External User → Service¶
Example: Accessing Open WebUI
https://openwebui.rbnk.uk
↓ (Cloudflare DNS)
157.180.63.15:443
↓ (Proxmox iptables DNAT)
10.10.10.10:443
↓ (Docker VM receives)
Traefik container reads Host header
↓ (Docker network routing)
Routes to open-webui container on port 8080
2. Service → Database¶
Example: Supabase Studio accessing database
3. Container → VM Host Service¶
Example: Open WebUI accessing Ollama (running on VM, not in container)
Note: This is within the VM (10.10.10.10), not the Proxmox host
4. Service → External API¶
Example: LiteLLM calling OpenAI
LiteLLM container → Docker bridge → 10.10.10.10 eth0 → Proxmox vmbr0 → 157.180.63.15 → api.openai.com
5. Service → Redis Cache¶
Example: Gitea caching Git objects
Configuration:
# In service docker-compose.yml
environment:
- REDIS_CACHE_URL=redis://:${REDIS_PASSWORD}@cache-redis:6379/0
networks:
- cache_redis_network
6. Service → Redis Sessions¶
Example: Gitea storing user sessions
Configuration:
# In service docker-compose.yml
environment:
- REDIS_SESSION_URL=redis://:${REDIS_PASSWORD}@session-redis:6379/1
networks:
- session_redis_network
7. Metrics Collection Flow¶
Example: Rclone backup metrics
Rclone script → writes to /var/lib/node_exporter/textfile_collector/rclone_backup.prom
Node Exporter → reads metrics files → exposes on :9100/metrics
Prometheus → scrapes :9100/metrics every 15s
Grafana → queries Prometheus → displays on dashboards
Traefik Routing Rules¶
Host-Based Routing¶
Each service registers itself with Traefik using Docker labels:
labels:
- "traefik.enable=true"
- "traefik.http.routers.SERVICE.rule=Host(`subdomain.rbnk.uk`)"
- "traefik.http.services.SERVICE.loadbalancer.server.port=8080"
Path-Based Routing (Supabase)¶
Supabase uses path prefixes to route to different services:
supabase.rbnk.uk/
├── /auth/v1/* → Auth Service (port 9999)
├── /rest/v1/* → PostgREST API (port 3000)
├── /storage/v1/* → Storage API (port 5000)
└── /* → Studio UI (port 3000)
Implementation:
# Auth service
- "traefik.http.routers.supa-auth.rule=Host(`supabase.rbnk.uk`) && PathPrefix(`/auth/v1`)"
- "traefik.http.middlewares.supa-auth-stripprefix.stripprefix.prefixes=/auth/v1"
# Studio catch-all (must be last)
- "traefik.http.routers.supa-studio.rule=Host(`supabase.rbnk.uk`)"
- "traefik.http.routers.supa-studio.priority=1"
SSL/TLS Configuration¶
Cloudflare DNS Challenge¶
Configuration:
Why DNS Challenge? - No need to open port 80 - Supports wildcard certificates - Works behind NAT/firewall
Certificate Storage¶
Port Forwarding by Service Type¶
Web Services (via Traefik)¶
Most services only need ports 80/443 forwarded to the VM. Traefik handles internal routing.
Required forwards: 80, 443 Services: Supabase, LiteLLM, n8n, Open WebUI, any web app
Direct Database Access¶
WARNING: Not recommended for production. Use Supabase REST API instead.
Required forwards: 5432 Configuration:
# On Proxmox (NOT recommended)
iptables -t nat -A PREROUTING -i enp7s0 -p tcp --dport 5432 -j DNAT --to 10.10.10.10:5432
VPN Services¶
For secure remote access to internal networks.
Required forwards: 51820 (UDP for WireGuard) Configuration:
# On Proxmox
iptables -t nat -A PREROUTING -i enp7s0 -p udp --dport 51820 -j DNAT --to 10.10.10.10:51820
Custom TCP Services¶
For services that can't use HTTP/HTTPS.
Example: Custom game server on port 25565
# On Proxmox
iptables -t nat -A PREROUTING -i enp7s0 -p tcp --dport 25565 -j DNAT --to 10.10.10.10:25565
Port Forwarding Best Practices¶
- Minimize Exposure: Only forward essential ports
- Use Traefik: Prefer HTTP/HTTPS routing over direct port exposure
- Document Changes: Keep track of all forwarded ports
- Firewall Rules: Add iptables rules to Proxmox network config for persistence
- Security: Never expose database or admin ports directly
Network Security¶
1. Network Isolation¶
2. Port Exposure¶
Never expose ports directly:
# BAD - Exposes to internet
ports:
- "5432:5432"
# GOOD - Only Traefik exposed
# No ports: section, use Traefik labels
3. Service Discovery¶
Services find each other by name within same network:
# In app config
DATABASE_URL=postgresql://user:pass@supa-db:5432/dbname
# ^^^^^^^
# Service name = hostname
Common Networking Issues¶
1. Container Can't Reach Internet¶
Symptom: API calls fail from container
Check:
Fix: Add to traefik_proxy network
2. Service Can't Connect to Database¶
Symptom: Connection refused to PostgreSQL
Check:
Fix: Ensure service is on internal network
3. Traefik Can't Route to Service¶
Symptom: 502 Bad Gateway
Check:
# Is service on traefik_proxy network?
docker inspect SERVICE | grep traefik_proxy
# Check Traefik can see service
curl http://localhost:8080/api/http/services
Fix: Add traefik.docker.network label:
Network Debugging Commands¶
VM Network Status¶
# Check VM network interfaces
ip addr show
# Check routing table
ip route
# Test connectivity to Proxmox gateway
ping 10.10.10.1
# Check NAT status
sudo iptables -t nat -L -n -v
Docker Network Configuration¶
# List all networks
docker network ls
# Inspect network details
docker network inspect traefik_proxy
# See connected containers
docker network inspect traefik_proxy | jq '.[0].Containers'
Test Connectivity Chain¶
# 1. From container to another container
docker exec CONTAINER ping OTHER_CONTAINER
# 2. From container to VM host
docker exec CONTAINER ping 172.17.0.1
# 3. From container to internet (tests full NAT chain)
docker exec CONTAINER ping 8.8.8.8
# 4. Check specific port accessibility
docker exec CONTAINER nc -zv SERVICE PORT
# 5. Test from VM to Proxmox
ping 10.10.10.1
# 6. Test from VM to internet
curl -I https://example.com
DNS Resolution Debugging¶
# Check DNS inside container
docker exec CONTAINER nslookup OTHER_SERVICE
# Check external DNS from container
docker exec CONTAINER nslookup google.com
# Check DNS from VM
nslookup google.com
# Test specific DNS server
docker exec CONTAINER nslookup example.com 8.8.8.8
Proxmox-Specific Debugging¶
# On Proxmox host (via SSH to 157.180.63.15)
# Check VM network status
qm list
qm config VMID | grep net
# Check bridge status
brctl show
# Monitor traffic to VM
tcpdump -i vmbr0 host 10.10.10.10
Best Practices¶
1. Network Naming¶
- Use descriptive names
- Suffix with purpose:
_proxy,_internal - Keep consistent across services
2. IP Assignment¶
- Let Docker assign IPs automatically
- Use service names, not IPs
- Document IP ranges for troubleshooting
3. Security¶
- Minimize services on external network
- Use internal networks for databases
- Never expose management ports
4. Documentation¶
- Document custom networks in compose files
- Comment non-obvious routing rules
- Keep network diagram updated
Future Improvements¶
- Network Policies: Implement iptables rules for finer control
- Service Mesh: Consider Consul/Linkerd for complex routing
- Monitoring: Add network metrics to Prometheus
- IPv6: Enable dual-stack networking