Skip to content

External Access

Osotechie edited this page Jun 22, 2026 · 1 revision

External Access

This page explains how containers are securely published to the internet — accessible from anywhere without exposing the NAS directly.

Overview

Every service published externally goes through a multi-layer chain that provides encryption, authentication, and automatic configuration:

flowchart LR
    User[User / Device] -->|HTTPS| CF[Cloudflare Edge<br/>Zero Trust Access]
    CF -->|Authenticated| Tunnel[Cloudflare Tunnel<br/>cloudflared container]
    Tunnel -->|Origin TLS| Traefik[Traefik<br/>Reverse Proxy]
    Traefik -->|Routes by Host rule| Service[Container Service]
Loading

No ports are exposed to the public internet. The cloudflared container creates an outbound-only tunnel to Cloudflare's edge — traffic flows inbound through that tunnel without needing any port forwards or firewall rules on the home network.

The Layers

1. Cloudflare Zero Trust Access

Before traffic even reaches the NAS, Cloudflare Zero Trust enforces authentication at the edge. Users must authenticate via an identity provider (e.g. Google, GitHub) before they're allowed through to the application.

This provides an additional authentication layer on top of whatever native auth the container itself supports. Even if a service has its own login page, the Zero Trust policy gates access before the request ever reaches it.

💡 This means even misconfigured or accidentally unsecured containers aren't exposed to the internet — Zero Trust blocks unauthenticated requests at the Cloudflare edge before they can reach the NAS.

🔗 Cloudflare Zero Trust docs | Access policies

2. Cloudflare Tunnel (cloudflared)

The cloudflared container runs on the NAS and maintains a persistent outbound connection to Cloudflare's edge network. This tunnel:

  • Requires no inbound ports — it's an outbound-only connection
  • Automatically reconnects if dropped
  • Routes traffic for configured hostnames from Cloudflare's edge into the home network

Traffic arrives at Traefik via the tunnel with full TLS — Cloudflare terminates TLS at the edge, then re-encrypts to the origin (Traefik) using Full (Strict) mode, meaning Traefik also presents a valid origin certificate.

🔗 Cloudflare Tunnel docs

3. Traefik (Reverse Proxy)

Traefik receives the request from the tunnel and routes it to the correct container based on Host rules defined via Docker labels:

labels:
  - traefik.enable=true
  - traefik.http.routers.myapp.rule=Host(`myapp.${DOMAIN}`)
  - traefik.http.routers.myapp.entryPoints=websecure

Traefik terminates origin TLS with its own certificate, providing end-to-end encryption from user → Cloudflare edge → tunnel → Traefik → container.

🔗 Traefik docs | Docker provider

4. Container Service

The target container receives HTTP(S) from Traefik on its internal Docker network. It doesn't need to know anything about tunnels, or external access — it just serves its application.

Full Traffic Flow

sequenceDiagram
    participant User
    participant CF as Cloudflare Edge
    participant ZT as Zero Trust Access
    participant Tunnel as cloudflared (NAS)
    participant Traefik as Traefik (NAS)
    participant App as Container

    User->>CF: HTTPS request (myapp.domain.com)
    CF->>ZT: Check access policy
    ZT->>User: Auth challenge (IdP login)
    User->>ZT: Authenticated
    ZT->>CF: Request approved
    CF->>Tunnel: Forward via tunnel (origin TLS)
    Tunnel->>Traefik: Route to Traefik (HTTPS)
    Traefik->>App: Route by Host rule (HTTP)
    App->>Traefik: Response
    Traefik->>Tunnel: Response
    Tunnel->>CF: Response
    CF->>User: HTTPS response
Loading

Auto-Configuration: How It All Wires Up

One of the key design goals is zero manual configuration when adding a new service. This is achieved through two mechanisms working together:

Traefik: Auto-Discovery via Docker Labels

Traefik watches the Docker socket and automatically creates routing rules for any container with traefik.enable=true. This means:

  • Container starts → Traefik detects the labels → route is created immediately
  • Container stops → Traefik detects the removal → route disappears immediately
  • No config files to edit — routing is defined in the docker-compose.yml alongside the service
labels:
  - traefik.enable=true
  - traefik.http.routers.myapp.rule=Host(`myapp.${DOMAIN}`)
  - traefik.http.routers.myapp.entryPoints=websecure
  - traefik.http.routers.myapp.middlewares=Real-IP@file, Headers@file

💡 If a container is stopped or removed, its Traefik route vanishes with it. No stale routes, no 502 errors pointing at dead containers — the route simply doesn't exist anymore.

Cloudflare-Companion: Auto-DNS from Traefik Labels

Cloudflare-Companion monitors running containers for Traefik Host() labels and automatically creates the corresponding DNS CNAME records in Cloudflare pointing at the tunnel:

  • Container starts with a Host label → Cloudflare-Companion creates the DNS record
  • Container stops → DNS record must be manually removed (auto-cleanup is not supported)

⚠️ Cloudflare-Companion only handles creation of DNS records, not deletion. If you remove a container or change its hostname, you need to manually delete the stale CNAME in the Cloudflare dashboard.

This completes the automation chain — from docker compose up to a fully routable, TLS-secured, Zero Trust-protected service with no manual steps (aside from DNS cleanup on removal).

flowchart TD
    DC[docker-compose.yml<br/>with Traefik labels] -->|Container starts| Docker[Docker Engine]
    Docker -->|Label detection| Traefik[Traefik<br/>creates route]
    Docker -->|Label detection| CFC[Cloudflare-Companion<br/>creates DNS record]
    Traefik -->|Routes traffic| App[Container]
    CFC -->|CNAME → tunnel| CF[Cloudflare DNS]
Loading

LAN Access

Services with a static IP on the IoT network (e.g. Plex at 10.1.11.100, Home Assistant at 10.1.11.10) are also accessible directly on the LAN without going through the tunnel (provided internal firewall rules allow). This provides:

  • Faster local access (no round-trip through Cloudflare)
  • Continued access if the internet goes down
  • Direct device-to-device communication for IoT protocols

The external (tunnel) and internal (LAN IP) access paths coexist — the same container is reachable both ways.

TLS Configuration

Hop TLS Certificate
User → Cloudflare Edge TLS 1.3 Cloudflare edge certificate (automatic)
Cloudflare → Traefik (via tunnel) Origin TLS (Full Strict) Cloudflare origin certificate on Traefik
Traefik → Container HTTP / HTTPS N/A (internal Docker network)

Full (Strict) mode in Cloudflare means it validates Traefik's origin certificate — a self-signed cert won't work. This prevents MITM between Cloudflare and the origin.

Security Summary

Layer Protection
Cloudflare Edge DDoS protection, WAF, bot detection
Zero Trust Access Identity-based authentication (IdP) before reaching the app
Cloudflare Tunnel No exposed ports, outbound-only connection
Origin TLS (Full Strict) Encrypted and validated origin connection
Traefik Middlewares Security headers, real IP forwarding
Container Auth Native application-level authentication

🔗 Cloudflare Zero Trust | Cloudflare Tunnel | Traefik | Cloudflare-Companion

Clone this wiki locally