Skip to content

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

  1. No Port Forwarding Required: Access services without configuring Proxmox iptables
  2. End-to-End Encryption: All traffic encrypted via WireGuard protocol
  3. Simplified Access: Use Tailscale IPs or MagicDNS names instead of public IPs
  4. NAT Traversal: Works behind firewalls and complex network setups
  5. Zero-Trust Security: Only authorized devices can connect

Access Patterns

Your Device → Tailscale VPN → dockerhost (100.98.20.43) → Docker Services
                              No Proxmox port forwarding needed!

Traditional Public Access (For End Users)

User Browser → Cloudflare → 157.180.63.15 → Proxmox NAT → 10.10.10.10 → Traefik

Service Access Examples

  1. SSH Access:

    # Via Tailscale (no port forwarding needed)
    ssh [email protected]
    # or
    ssh [email protected]
    
    # Traditional (requires port 22 forwarding)
    ssh [email protected]
    

  2. Traefik Dashboard:

    # Via Tailscale (direct access)
    http://100.98.20.43:8080
    
    # Traditional (blocked without port forward)
    # Would need port 8080 forwarded, which is not recommended
    

  3. Direct Database Access:

    # Via Tailscale (secure, no public exposure)
    psql -h 100.98.20.43 -p 5432 -U postgres
    
    # Traditional (requires port 5432 forward - NOT recommended)
    

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:

  1. MagicDNS: Tailscale provides automatic DNS names (e.g., dockerhost.tail92c8e3.ts.net)
  2. Custom DNS: You can configure Tailscale to use your AdGuard instance
  3. Split DNS: Define custom domains that resolve only within your Tailnet

Security Benefits

  1. No Public Port Exposure: Admin interfaces remain completely private
  2. Device Authorization: Only approved devices can join the Tailnet
  3. ACLs: Fine-grained access control between Tailscale nodes
  4. Audit Logs: Track all connections and access attempts
  5. 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:

networks:
  traefik_proxy:
    external: true

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:

networks:
  internal:
    driver: bridge
    internal: true  # No external routing

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:

networks:
  cache_redis_network:
    name: cache_redis_network
    driver: bridge

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:

networks:
  session_redis_network:
    name: session_redis_network
    driver: bridge

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

User Request → Cloudflare → 157.180.63.15:443 → Proxmox NAT → 10.10.10.10:443 → Traefik → 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

Service → supabase_internal network → PostgreSQL:5432

Example: Supabase Studio accessing database

Studio (172.19.0.6) → PostgreSQL (172.19.0.2:5432)

3. Container → VM Host Service

Container → Docker Gateway (172.17.0.1) → VM Host Service

Example: Open WebUI accessing Ollama (running on VM, not in container)

Open WebUI container → http://172.17.0.1:11434 → Ollama API (VM process)

Note: This is within the VM (10.10.10.10), not the Proxmox host

4. Service → External API

Service → traefik_proxy network → Docker NAT → VM eth0 → Proxmox NAT → Internet

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

Service → cache_redis_network → Cache Redis:6379

Example: Gitea caching Git objects

Gitea (172.20.0.10) → cache_redis_network → cache-redis (172.20.0.2:6379)

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

Service → session_redis_network → Session Redis:6379

Example: Gitea storing user sessions

Gitea (172.21.0.10) → session_redis_network → session-redis (172.21.0.2:6379)

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

Services → Node Exporter → Prometheus → Grafana

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

Traefik → Cloudflare API → DNS TXT Record → Let's Encrypt → Certificate

Configuration:

# In Traefik config
certificatesResolvers:
  cf:
    acme:
      dnsChallenge:
        provider: cloudflare

Why DNS Challenge? - No need to open port 80 - Supports wildcard certificates - Works behind NAT/firewall

Certificate Storage

/srv/dockerdata/traefik/
└── letsencrypt/
    └── acme.json  # Contains all certificates

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

  1. Minimize Exposure: Only forward essential ports
  2. Use Traefik: Prefer HTTP/HTTPS routing over direct port exposure
  3. Document Changes: Keep track of all forwarded ports
  4. Firewall Rules: Add iptables rules to Proxmox network config for persistence
  5. Security: Never expose database or admin ports directly

Network Security

1. Network Isolation

# Internal network - no internet access
networks:
  internal:
    internal: true

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:

# Is container on external network?
docker inspect CONTAINER | jq '.[0].NetworkSettings.Networks'

Fix: Add to traefik_proxy network

2. Service Can't Connect to Database

Symptom: Connection refused to PostgreSQL

Check:

# Are both on same network?
docker network inspect supabase_internal

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:

- "traefik.docker.network=traefik_proxy"

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

  1. Network Policies: Implement iptables rules for finer control
  2. Service Mesh: Consider Consul/Linkerd for complex routing
  3. Monitoring: Add network metrics to Prometheus
  4. IPv6: Enable dual-stack networking