VM 102 · Docker Stack
Cloudflare Tunnel provides secure external access to all homelab services via*.munyakazi.org- no port forwarding, no dynamic DNS, no router configuration required.
| Tunnel name | vm102-homelab |
| Image | cloudflare/cloudflared:latest |
| Plan | Cloudflare Zero Trust Free |
| Status | Healthy |
Cloudflare Tunnel (cloudflared) runs as a Docker container and creates an outbound-only encrypted connection from VM 102 to Cloudflare's edge network. Incoming requests to *.munyakazi.org arrive at Cloudflare, travel through the tunnel to the cloudflared container, and are forwarded to Nginx Proxy Manager - which routes them to the correct service.
| Port Forwarding | Cloudflare Tunnel | |
|---|---|---|
| Router config needed | Yes - every time | Never |
| Works when moving house | No - reconfigure router | Yes - outbound connection |
| Dynamic DNS needed | Yes | No |
| Exposes ports publicly | Yes | No |
| TLS certificate needed | Yes - manual | Automatic via Cloudflare |
| Works behind CGNAT | No | Yes |
The tunnel makes the homelab genuinely portable - it works identically regardless of ISP, router model, or physical location.
version: "3.8"
services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate run --token [redacted]
networks:
- npm_default
networks:
npm_default:
external: true
The container joins npm_default so it can forward traffic to npm:80 by container name.
User browser
↓ HTTPS request to cloud.munyakazi.org
Cloudflare edge (Amsterdam / Frankfurt)
↓ Encrypted tunnel (QUIC/HTTP2)
cloudflared container (VM 102, 192.168.86.53)
↓ http://npm:80 (internal Docker network)
Nginx Proxy Manager
↓ http://nextcloud:80
Nextcloud container
The tunnel uses QUIC protocol by default (UDP-based, faster than TCP), with HTTP/2 as fallback. The cloudflared logs confirm: suggested_protocol=quic.
| Subdomain | Service URL | Target |
|---|---|---|
| cloud.munyakazi.org | http://npm:80 | Nextcloud |
| portainer.munyakazi.org | http://npm:80 | Portainer |
| wiki.munyakazi.org | http://npm:80 | Wiki.js |
All routes point to http://npm:80 - NPM handles the final routing to each container based on the Host header.
When each route is added in the Cloudflare Zero Trust dashboard, a CNAME record is automatically created in the munyakazi.org DNS zone:
cloud.munyakazi.org CNAME df138a5f-9752-46b1-b566-f6eb5306b876.cfargotunnel.com
portainer.munyakazi.org CNAME df138a5f-9752-46b1-b566-f6eb5306b876.cfargotunnel.com
wiki.munyakazi.org CNAME df138a5f-9752-46b1-b566-f6eb5306b876.cfargotunnel.com
No manual DNS management needed for new subdomains.
The existing Block Direct IP Access WAF rule on munyakazi.org was updated to allow all legitimate subdomains:
(http.host ne "munyakazi.org" and not ends_with(http.host, ".munyakazi.org"))
This rule now correctly blocks raw IP access while allowing all *.munyakazi.org traffic through - including any new subdomains added in future.
| Issue | Cause | Fix |
|---|---|---|
| 502 on portainer.munyakazi.org | Tunnel route set to https://npm:443 - SNI mismatch (tls: unrecognized name) |
Changed route to http://npm:80 |
| Cloudflare blocking all subdomain requests | WAF rule too strict - matched on exact hostname only | Updated WAF expression to use ends_with() |
| cloudflared couldn't reach npm by name | Separate Docker networks - cloudflared on cloudflared_default, npm on npm_default | Updated cloudflared stack to join npm_default as external network |