VOOZH about

URL: https://dev.to/imrhlrvndrn/cloudflare-tunnel-advanced-guide-docker-zero-trust-and-ha-setup-2381

⇱ Cloudflare Tunnel Advanced Guide: Docker, Zero Trust and HA Setup - DEV Community


In Part 1, we built a Cloudflare Tunnel capable of exposing multiple local services through a single secure connection.

We successfully created:

app.example.com
api.example.com
grafana.example.com

without opening a single port on our router.

That's already a significant improvement over traditional port forwarding. However, most developers and homelab operators quickly discover that exposing a few applications is only the beginning.

Questions start appearing:

  • How do I expose Docker containers?

  • Can I secure services behind authentication?

  • What happens if my server reboots?

  • How do I scale beyond a few applications?

  • Can multiple machines share a tunnel?

  • How do production teams manage this?

This article answers those questions and takes Cloudflare Tunnel from a useful tool to a complete infrastructure component.


Why Most Tunnel Setups Eventually Become Difficult to Manage

Let's look at a realistic self-hosted environment.

Many developers start with:

Frontend
Backend API
Database Admin Tool

A few months later:

Frontend
Backend API
Grafana
Prometheus
Portainer
Jenkins
Home Assistant
Gitea
MinIO
Uptime Kuma

Now you're managing:

  • Multiple ports

  • Multiple containers

  • Multiple services

  • Different authentication requirements

  • Internal-only applications

  • Public-facing applications

Without structure, your configuration becomes difficult to maintain.

The goal is not simply exposing services.

The goal is building an architecture that scales.


Designing a Multi-Service Gateway

A common mistake is assigning random names.

For example:

app1.example.com
app2.example.com
app3.example.com

This quickly becomes confusing.

Instead, use service-oriented naming.

api.example.com
grafana.example.com
portainer.example.com
git.example.com
status.example.com
storage.example.com

Anyone can immediately understand the purpose of each endpoint.

For development environments:

dev-api.example.com
dev-web.example.com
staging-api.example.com
staging-web.example.com

This naming convention scales much better.


Advanced Ingress Routing

Part 1 introduced hostname routing. Cloudflared also supports more advanced patterns.

Basic hostname rule:

ingress:

 - hostname: api.example.com
 service: http://localhost:8080

 - service: http_status:404

Simple. But what if multiple applications share the same hostname?


Path-Based Routing

Suppose you want:

example.com/api
example.com/admin
example.com/dashboard

instead of separate subdomains.

Configuration:

ingress:

 - hostname: example.com
 path: /api/*
 service: http://localhost:8080

 - hostname: example.com
 path: /admin/*
 service: http://localhost:5000

 - hostname: example.com
 path: /dashboard/*
 service: http://localhost:3001

 - service: http_status:404

Requests are routed based on both hostname and URL path. This can simplify DNS management when dozens of services exist.


Wildcard Routing

Sometimes development environments change frequently. Instead of creating many DNS entries:

feature1.example.com
feature2.example.com
feature3.example.com

use a wildcard.

DNS:

*.example.com

Configuration:

ingress:

 - hostname: "*.example.com"
 service: http://localhost:3000

 - service: http_status:404

Useful for preview deployments and temporary environments.


Running Cloudflared as a System Service

Production deployments should never rely on manually running:

cloudflared tunnel run homelab

Use systemd instead.

Install service:

sudo cloudflared service install

Enable automatic startup:

sudo systemctl enable cloudflared

Start immediately:

sudo systemctl start cloudflared

Verify:

sudo systemctl status cloudflared

Example output:

Active: active (running)

Now the tunnel automatically recovers after:

  • Reboots

  • Crashes

  • Power failures

  • Maintenance windows

This is essential in production.


Docker Integration

Most modern self-hosted services run inside containers. Cloudflared works exceptionally well with Docker.

Consider this stack:

React Frontend
ExpressJS Backend
Grafana
Prometheus

All running in containers.


Running Cloudflared Inside Docker

Create a dedicated network.

docker network create cloudflare


Create docker-compose.yml:

version: "3.9"

services:
 cloudflared:
 image: cloudflare/cloudflared:latest
 command: tunnel --config /etc/cloudflared/config.yml run
 volumes:
 - ./cloudflared:/etc/cloudflared
 restart: unless-stopped
 networks:
 - cloudflare

networks:
 cloudflare:
 external: true

Start:

docker compose up -d

Cloudflared now runs entirely inside Docker.


Routing to Containers Directly

Instead of localhost:

service: http://localhost:3000

use container names.

Example:

ingress:

 - hostname: app.example.com
 service: http://frontend:3000

 - hostname: api.example.com
 service: http://backend:8080

 - hostname: grafana.example.com
 service: http://grafana:3000

 - service: http_status:404

Docker DNS automatically resolves container names. This creates a clean architecture.


Example Production Docker Stack

A realistic setup:

services:

 frontend:
 image: myapp/frontend

 backend:
 image: myapp/api

 postgres:
 image: postgres:16

 grafana:
 image: grafana/grafana

 prometheus:
 image: prom/prometheus

 cloudflared:
 image: cloudflare/cloudflared

Everything communicates internally. Only Cloudflare exposes services externally.


Protecting Internal Applications with Zero Trust

This is arguably Cloudflare Tunnel's strongest feature.

Consider:

grafana.example.com

Should Grafana be publicly accessible?

Probably not.

Instead:

Internet
↓
Cloudflare Access
↓
Authenticated User
↓
Grafana


Setting Up Cloudflare Access

Navigate to:

Zero Trust Dashboard
→ Access
→ Applications
→ Add Application

Select:

Self Hosted

Enter:

grafana.example.com


Restrict Access by Email

Example policy:

Allow
Email ends with:
@company.com

Only approved users gain access.

Everyone else receives:

Access Denied

before traffic reaches Grafana.


Restrict Access to Specific Users

Example:

alice@example.com
bob@example.com
charlie@example.com

Only these users can authenticate. Excellent for administrative dashboards. Even if someone discovers the URL, they cannot reach the application.


Exposing Grafana Securely

Without Access:

Internet
↓
Grafana Login Screen

Attackers can see the application.

With Access:

Internet
↓
Cloudflare Access Login
↓
Grafana

Grafana itself becomes invisible to unauthorized users. This significantly improves security.


High Availability Tunnels

A common question:

What happens if cloudflared crashes?

Cloudflare supports multiple tunnel connectors.

One tunnel.

Several cloudflared instances.

Example:

Server A
 |
Tunnel

Server B
 |
Tunnel

Server C
 |
Tunnel

Cloudflare automatically balances traffic. If one connector disappears:

Traffic continues

No downtime.


Multi-Host Architecture

Imagine:

VM1 → Grafana
VM2 → Prometheus
VM3 → Jenkins

All can participate in the same tunnel.

This enables distributed homelab architectures.


Monitoring Tunnel Health

Cloudflare provides metrics inside the dashboard.

Useful indicators:

  • Active connectors

  • Tunnel availability

  • Request volume

  • Errors

  • Traffic trends

Monitor regularly.


Viewing Logs

Systemd:

journalctl -u cloudflared -f

Docker:

docker logs -f cloudflared

Common troubleshooting begins here.


Detecting Misconfigured Services

Example symptom:

502 Bad Gateway

Often means:

Cloudflared works
Service doesn't

Verify local connectivity first.

curl http://localhost:8080

or

curl http://backend:8080

depending on deployment style.


Real-World Developer Workstation Setup

Many engineers expose local applications during development.

Example:

web-dev.example.com
api-dev.example.com

Cloudflared configuration:

ingress:

 - hostname: web-dev.example.com
 service: http://localhost:3000

 - hostname: api-dev.example.com
 service: http://localhost:8080

 - service: http_status:404

Useful for:

  • QA reviews/Webhook testing

  • Mobile testing

  • Client demonstrations

  • Remote collaboration

No VPN required.


Real-World Homelab Setup

A common self-hosting stack:

Jellyfin
Grafana
Prometheus
Portainer
Home Assistant
Uptime Kuma
Gitea

Configuration:

ingress:

 - hostname: media.example.com
 service: http://localhost:8096

 - hostname: grafana.example.com
 service: http://localhost:3000

 - hostname: prometheus.example.com
 service: http://localhost:9090

 - hostname: portainer.example.com
 service: https://localhost:9443

 - hostname: home.example.com
 service: http://localhost:8123

 - hostname: status.example.com
 service: http://localhost:3001

 - hostname: git.example.com
 service: http://localhost:3005

 - service: http_status:404

One tunnel.

Many services.

No port forwarding.


Common Mistakes

Exposing Everything Publicly

Avoid:

Public Grafana
Public Portainer
Public Jenkins

Use Zero Trust.

Always.


Missing Catch-All Rule

Incorrect:

ingress:

 - hostname: api.example.com
 service: http://localhost:8080

Correct:

ingress:

 - hostname: api.example.com
 service: http://localhost:8080

 - service: http_status:404


Wrong Service Port

Example:

service: http://localhost:8080

when the application actually runs on:

localhost:8000

Always verify locally first.


Mixing Container and Host Networking

Bad:

service: http://localhost:3000

inside Docker when service exists on another container.

Use:

service: http://frontend:3000

instead.


Cloudflared vs Alternatives

Cloudflared

Best for:

  • Homelabs

  • Internal applications

  • Zero Trust

  • Long-running services


ngrok

Best for:

  • Temporary demos

  • Short-lived testing

  • Quick sharing


Reverse Proxy + Port Forwarding

Best for:

  • Full infrastructure control

  • Existing network expertise

But requires:

  • Open ports

  • Firewall management

  • SSL management

Cloudflared removes most of that complexity.


Recommended Production Architecture

For most teams and homelab operators:

Docker Containers
↓
Cloudflared
↓
Cloudflare Tunnel
↓
Cloudflare Access
↓
Internet

Benefits:

  • No inbound ports

  • Automatic TLS

  • Identity-based authentication

  • Centralized access control

  • Easy scaling

  • Reduced attack surface

This architecture has become increasingly popular because it combines simplicity with strong security.


Final Thoughts

Cloudflare Tunnel starts as a convenient way to expose a local application. But its real value appears when you begin managing many services.

Instead of configuring:

  • Routers

  • Firewalls

  • Reverse proxies

  • Certificates

  • Public IPs

you create a secure outbound connection and let Cloudflare handle the rest.

The combination of:

  • Named tunnels

  • Ingress routing

  • Docker integration

  • Zero Trust Access

  • Automatic HTTPS

  • High availability connectors

creates a surprisingly powerful platform for self-hosting, development environments, internal tooling, and homelab infrastructure.

Whether you're exposing a single application for testing or managing dozens of internal services, cloudflared provides a clean, scalable, and secure solution that avoids many of the operational headaches traditionally associated with publishing local services to the internet.