Network Architecture¶
How traffic flows from the internet to internal services.
Overview¶
┌─────────────────────────────────────────────────────────────┐
│ Cloudflare Edge │
│ (SSL termination, DDoS, WAF, caching) │
│ │
│ atlantis.minnova.io ──┐ │
│ zitadel.minnova.io ───┼── CNAME to tunnel │
│ grafana.minnova.io ───┘ │
└─────────────────────────────┬───────────────────────────────┘
│ Tunnel (outbound connection)
▼
┌─────────────────────────────────────────────────────────────┐
│ Hetzner Private Network │
│ │
│ ┌─────────────────┐ │
│ │ Ingress Node │ Cloudflared + Reverse Proxy │
│ │ (+ Tailscale) │ (Caddy now, Traefik with Nomad) │
│ └────────┬────────┘ │
│ │ Private network (10.0.x.x) │
│ │ │
│ ┌─────┴─────┬─────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────┐ ┌───────┐ ┌───────────┐ │
│ │Worker1│ │Worker2│ │ Worker3 │ No public IP │
│ │ │ │ │ │ │ No cloudflared │
│ └───────┘ └───────┘ └───────────┘ No Tailscale │
│ │
└─────────────────────────────────────────────────────────────┘
Key Decisions¶
| Decision | Choice | Why |
|---|---|---|
| Edge/CDN | Cloudflare | Free tier, DDoS, WAF, tunnel support |
| Ingress | Cloudflare Tunnel | No public IPs needed, outbound-only |
| Reverse proxy | Caddy (now), Traefik (Nomad) | Auto-discovery, simple config |
| SSH access | Tailscale on ingress node | Zero trust, no exposed SSH |
| Private network | Hetzner vSwitch | Free, isolates workers |
Traffic Flow¶
HTTP/HTTPS (Application Traffic)¶
User → Cloudflare Edge → Tunnel → Caddy/Traefik → Service
- User visits
atlantis.minnova.io - DNS resolves to Cloudflare (CNAME to tunnel)
- Cloudflare terminates SSL, applies WAF rules
- Traffic flows through tunnel to your infrastructure
- Reverse proxy routes by hostname to correct service
SSH (Admin Access)¶
Admin → Tailscale → Ingress Node → SSH jump → Workers
- Admin connects via Tailscale
- SSH to ingress node (has Tailscale)
- SSH jump to private workers (no public IP)
Components¶
Cloudflare Tunnel¶
Outbound-only connection from your infrastructure to Cloudflare edge. Eliminates need for: - Public IPs on services - Firewall port management - SSL certificate management
One tunnel, multiple hostnames:
tunnel: <tunnel-id>
ingress:
- hostname: "*.minnova.io"
service: http://localhost:80 # Reverse proxy
- service: http_status:404
All DNS records CNAME to same tunnel. Routing happens inside your infrastructure.
Reverse Proxy¶
Routes traffic by hostname to internal services.
Current (Caddy with Docker/Podman):
# Labels on container auto-configure routing
services:
atlantis:
labels:
caddy: atlantis.minnova.io
caddy.reverse_proxy: "{{upstreams 4141}}"
Future (Traefik with Nomad):
# Nomad service block auto-registers with Consul
service {
name = "atlantis"
port = "http"
tags = [
"traefik.enable=true",
"traefik.http.routers.atlantis.rule=Host(`atlantis.minnova.io`)",
]
}
Tailscale (Ingress Node Only)¶
Provides secure admin access without exposing SSH publicly.
- Installed only on ingress node
- Other nodes accessible via SSH jump
- Integrates with Zitadel OIDC for authentication
Hetzner Private Network¶
resource "hcloud_network" "internal" {
name = "internal"
ip_range = "10.0.0.0/16"
}
resource "hcloud_network_subnet" "services" {
network_id = hcloud_network.internal.id
type = "cloud"
network_zone = "eu-central"
ip_range = "10.0.1.0/24"
}
Ingress node: Public IP + private IP + Tailscale Workers: Private IP only
Scaling Path¶
| Phase | Architecture |
|---|---|
| 1 VPS | Everything on one machine (current) |
| 2-3 VPS | Ingress node + workers on private network |
| 4+ VPS | Dedicated ingress, Nomad cluster on workers |
Single Node (Current)¶
┌─────────────────────────────┐
│ Single VPS │
│ │
│ - Cloudflared │
│ - Caddy │
│ - Tailscale │
│ - Atlantis │
│ - (future: Zitadel, etc.) │
└─────────────────────────────┘
Multi-Node (Future)¶
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Ingress │ │ Worker 1 │ │ Worker 2 │
│ │ │ │ │ │
│ Cloudflared │ │ Nomad client │ │ Nomad client │
│ Traefik │────▶│ Atlantis │ │ Zitadel │
│ Tailscale │ │ Prometheus │ │ Grafana │
│ Nomad server │ │ │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
└────────────────────┴────────────────────┘
Private Network
Nomad/Kubernetes Compatibility¶
This architecture translates directly:
| Component | Ansible+Podman | Nomad | Kubernetes |
|---|---|---|---|
| Cloudflared | Systemd service | Job | Deployment |
| Reverse proxy | Caddy container | Traefik job | Ingress controller |
| Services | Systemd services | Jobs | Deployments |
| Discovery | Manual config | Consul | K8s Services |
The pattern stays the same, only orchestration changes.
Related Documentation¶
- Identity & Access - Authentication architecture
- Orchestration - Container orchestration strategy
- VPN Strategy - Tailscale vs alternatives