Back to Articles
15 min read

Architecting Zero-Exposure Ingress: Migrating Kubernetes from Public Listener to Cloudflare Tunnels

A case study on migrating from a traditional "Edge Ingress" model to a Zero Trust architecture using Cloudflare Tunnels, Traefik, and GitOps principles.

CloudflareKubernetesZero TrustTraefikGitOpsSecurity

Exposing a Kubernetes cluster to the internet has traditionally involved a trade-off between accessibility and security. The standard approach (port forwarding, Dynamic DNS, and handling TLS certificates on the cluster) leaves the network perimeter vulnerable to scanning and exploitation.

In this case study, I detail the migration from a traditional "Edge Ingress" model using Cert-Manager and Let's Encrypt to a Zero Trust model using Cloudflare Tunnels, Traefik, and GitOps principles.

The Challenge: The Open Port Problem

The initial infrastructure relied on a classic ingress pattern:

  1. Network: Ports 80 and 443 exposed on the physical router.
  2. Resolution: Dynamic DNS pointing to the residential IP.
  3. Termination: Traefik handling SSL/TLS via Cert-Manager (Let's Encrypt).

Friction Points

Attack Surface

The IP address was public, inviting port scanners and DDoS attempts.

Maintenance Overhead

Managing ClusterIssuers, certificate renewals, and ACME challenges added complexity to the Kubernetes manifest surface area.

Dependency

The cluster required direct knowledge of its public IP state.

The Solution: Tunnel-First Architecture

The core solution was a migration to a Cloudflare Tunnel architecture. This inverts the connection model: instead of the internet connecting *in* to the cluster, a lightweight daemon (`cloudflared`) connects *out* to Cloudflare's edge.

1. Architectural Topology

I chose to retain Traefik as the internal router while delegating the "transport" layer to Cloudflare. This "Gateway" pattern allows the Kubernetes Ingress manifests to remain mostly unchanged while significantly hardening the transport layer.

Zero-Exposure Request Flow

Secure Tunnel Architecture

👤
User
HTTPS Request
☁️
Cloudflare Edge
DDoS Protection & TLS
Secure Tunnel (HTTP/2)
KUBERNETES CLUSTER
pod/cloudflared
Tunnel Daemon
svc/traefik
Internal Router
svc/application
Target Microservice

2. Infrastructure as Code & Secret Management

A critical requirement was maintaining a strict GitOps workflow via ArgoCD. This presented a challenge: the Cloudflare Tunnel requires sensitive authentication tokens (`TUNNEL_TOKEN`).

To solve this without compromising the Git repository, I utilized Bitnami Sealed Secrets.

  1. The raw token is encrypted locally using the controller's public key.
  2. The resulting `SealedSecret` CRD is committed to Git.
  3. Inside the cluster, the operator decrypts the token into a standard Kubernetes Secret, which is then injected into the `cloudflared` Deployment as an environment variable.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cloudflared
spec:
  template:
    spec:
      containers:
      - name: cloudflared
        image: cloudflare/cloudflared:latest
        args:
        - tunnel
        - run
        env:
        - name: TUNNEL_TOKEN
          valueFrom:
            secretKeyRef:
              name: cloudflare-tunnel-token
              key: token

This ensures the repository remains public-safe while the infrastructure remains fully declarative.

3. The Hybrid DNS Strategy (Split-Horizon)

A unique constraint of this deployment was the existence of a decoupled frontend hosted on Vercel, while the backend API resides in the Kubernetes Home Lab. This required a precise DNS strategy to prevent routing conflicts.

Why this Hybrid Approach?

This architecture is a deliberate trade-off. Hosting the frontend on Vercel provides global Edge Network caching and High Availability (HA) out of the box, ensuring the site remains fast and accessible even if my home lab goes dark. Conversely, hosting the backend and data services on my own infrastructure keeps running costs minimal for the "heavy lifting" components that don't require the same strict 99.99% uptime SLAs.

A split-horizon DNS configuration was implemented at the authoritative level (Cloudflare DNS):

Split-Horizon DNS Strategy

Handling Frontend & Backend Routing

Frontend
Vercel
benbosco.dev
RecordA / CNAME
CloudflareGrey Cloud (DNS Only)
TLSHandled by Vercel
Backend
Kubernetes
*.benbosco.dev
RecordCNAME (Tunnel)
CloudflareOrange Cloud (Proxied)
TLSHandled by Cloudflare

This setup allows Vercel to serve the UI with low latency, while the UI makes fetch requests to `https://api.benbosco.dev`, which securely tunnels into the cluster.

4. Hardening the Gateway

With the tunnel active, Traefik no longer needs to handle certificate generation. The `cert-manager` annotations were stripped from the Ingress resources, simplifying the manifests.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  # No more cert-manager annotations!
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
  rules:
  - host: api.benbosco.dev
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-gateway
            port:
              number: 80

Furthermore, the Traefik Dashboard, previously exposed via Ingress, was identified as a security risk. The external route for the dashboard was disabled entirely. Operational observability is now achieved securely via `kubectl port-forward` on an as-needed basis, adhering to the principle of least privilege.

Conclusion

By decoupling the transport layer (Cloudflare) from the routing layer (Traefik), the result is a robust, production-grade ingress architecture.

Key Wins

  • Zero Open Ports:The firewall now blocks all incoming traffic.
  • Simplified Manifests:No more ClusterIssuer or ACME challenge configurations.
  • High Availability:The cloudflared deployment runs with multiple replicas, ensuring tunnel resilience.

The infrastructure is now prepared for the next phase: deploying a Go-based microservices API that acts as the logic core for the Vercel frontend.