From b476b2578c00bca03b34dc678e31b0de6df21388 Mon Sep 17 00:00:00 2001 From: Jay Date: Fri, 6 Nov 2026 14:43:05 +0100 Subject: [PATCH 1/2] [Repo] refactor: Remove the cloudflare-exposed network The tunnel sends every hostname to Traefik over dokploy-network, so cloudflare-exposed never carried traffic and did not reflect what is exposed (qBittorrent was public without it). Drop it from cloudflared, ASF, Homepage, Pi-hole and Speedtest, and document that exposure is decided by tunnel hostnames, Dokploy domains and Cloudflare Access. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 15 +++++----- README.md | 3 +- archisteamfarm/README.md | 5 ---- archisteamfarm/docker-compose.yml | 6 ---- cloudflared/README.md | 48 ++++++++++++++----------------- cloudflared/docker-compose.yml | 6 ---- homepage/README.md | 5 ---- homepage/docker-compose.yml | 6 ---- pihole/README.md | 5 ---- pihole/docker-compose.yml | 6 ---- speedtest/README.md | 5 ---- speedtest/docker-compose.yml | 6 ---- 12 files changed, 29 insertions(+), 87 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index fb3eeef..f941a45 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,14 +27,13 @@ exactly. ## Architecture -**Network model:** A single Cloudflare Tunnel -(`cloudflared/`) creates and owns the `cloudflare-exposed` -external bridge network. Services that need internet -exposure declare and connect to this network. Services -that don't need Cloudflare exposure should **not** declare -any network in their compose file — Dokploy attaches its -own `dokploy-network` automatically on deploy (e.g. `n8n`, -`plex`, `traccar`, `watchtower`). +**Network model:** Services don't declare networks for +exposure. Dokploy attaches its own `dokploy-network` +automatically on deploy, and Traefik routes from there. The +Cloudflare Tunnel (`cloudflared/`) sends every public +hostname to `http://dokploy-traefik`. What is exposed is +decided by the tunnel's hostnames, the Dokploy domains and +Cloudflare Access, not by Docker networks. **Container management:** Watchtower handles automatic image updates using label-based opt-in diff --git a/README.md b/README.md index 399043a..caf1654 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,7 @@ this repository. 4. Run `docker compose up -d`. > Each service is self-contained. You can deploy them independently -> in any order, except that **cloudflared** should be running first -> if the service connects to the `cloudflare-exposed` network. +> in any order. ## Deployment via Dokploy diff --git a/archisteamfarm/README.md b/archisteamfarm/README.md index 096127f..7435c14 100644 --- a/archisteamfarm/README.md +++ b/archisteamfarm/README.md @@ -34,11 +34,6 @@ Named Docker volumes, so they can be backed up with Dokploy volume backups. With Docker Compose the real names are prefixed with the project name (for example `_archisteamfarm-config`). -## Networks - -Connects to `cloudflare-exposed` -(must be created by the `cloudflared` service first). - ## Updates Watchtower updates the `archisteamfarm` container automatically, diff --git a/archisteamfarm/docker-compose.yml b/archisteamfarm/docker-compose.yml index 919bbbf..d60fc76 100644 --- a/archisteamfarm/docker-compose.yml +++ b/archisteamfarm/docker-compose.yml @@ -5,8 +5,6 @@ services: restart: unless-stopped security_opt: - no-new-privileges:true - networks: - - cloudflare-exposed volumes: - /etc/localtime:/etc/localtime:ro - archisteamfarm-config:/app/config @@ -31,7 +29,3 @@ volumes: archisteamfarm-config: archisteamfarm-plugins: archisteamfarm-logs: - -networks: - cloudflare-exposed: - external: true diff --git a/cloudflared/README.md b/cloudflared/README.md index 2c1a64b..d492ab7 100644 --- a/cloudflared/README.md +++ b/cloudflared/README.md @@ -4,25 +4,22 @@ Runs a [Cloudflare Tunnel][tunnel-docs] to securely expose internal Docker services to the internet without opening ports on the host. -This service creates the shared `cloudflare-exposed` bridge -network. Any service that needs external access must join -this network. - -The tunnel also attaches to the `dokploy-network` so it can -reach services that are only published on that network -(those that don't declare `cloudflare-exposed`). Dokploy -provides `dokploy-network` automatically on deploy. +The tunnel joins `dokploy-network`, which Dokploy provides +automatically on deploy, and sends every public hostname to +Traefik (`http://dokploy-traefik`). Traefik then routes the +request using the domain configured in Dokploy. ## How It Works 1. The `cloudflared` container establishes a tunnel using your Cloudflare token. -2. The `cloudflare-exposed` Docker network is created - and shared across services. -3. Services attached to this network become reachable - through Cloudflare. -4. Routes and access policies are managed in the - [Cloudflare Zero Trust dashboard][zero-trust]. +2. Each public hostname is added to the tunnel in the + [Cloudflare Zero Trust dashboard][zero-trust], pointing + to `http://dokploy-traefik`. +3. Traefik forwards the request to the service that has + that domain in Dokploy (HTTPS off, certificate None: + Cloudflare terminates TLS). +4. Access policies are managed in Cloudflare Zero Trust. ## Environment Variables @@ -30,22 +27,19 @@ provides `dokploy-network` automatically on deploy. |--------------------------|----------------------------------------| | `CLOUDFLARE_TUNNEL_TOKEN`| Tunnel token from Cloudflare Zero Trust| -## Connecting a Service +## Exposing a Service -Add the `cloudflare-exposed` network to any service -you want to expose: +A service is exposed by configuration, not by Docker +networks: -```yaml -services: - my-service: - image: example/my-service:latest - networks: - - cloudflare-exposed +1. Add the domain to the service in Dokploy. +2. Add the hostname to the tunnel, pointing to + `http://dokploy-traefik`. +3. Protect it with a Cloudflare Access application + unless it must be public. -networks: - cloudflare-exposed: - external: true -``` +No network needs to be declared in the service's compose +file. ## Useful Resources diff --git a/cloudflared/docker-compose.yml b/cloudflared/docker-compose.yml index b38b747..72a9312 100644 --- a/cloudflared/docker-compose.yml +++ b/cloudflared/docker-compose.yml @@ -11,7 +11,6 @@ services: - 1.0.0.1 dns_search: . networks: - - cloudflare-exposed - dokploy-network environment: - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN} @@ -22,8 +21,3 @@ services: max-file: "3" labels: - com.centurylinklabs.watchtower.enable=true - -networks: - cloudflare-exposed: - driver: bridge - name: cloudflare-exposed diff --git a/homepage/README.md b/homepage/README.md index 4899d8e..31e1954 100644 --- a/homepage/README.md +++ b/homepage/README.md @@ -60,11 +60,6 @@ with the named volume: `services.yaml`, and remove the socket-based server from `docker.yaml`. Without the socket, label-discovered services no longer appear. -## Networks - -Connects to `cloudflare-exposed` -(must be created by the `cloudflared` service first). - ## Updates Watchtower updates the `homepage` container automatically, following the diff --git a/homepage/docker-compose.yml b/homepage/docker-compose.yml index 1e29739..afbec7c 100644 --- a/homepage/docker-compose.yml +++ b/homepage/docker-compose.yml @@ -11,8 +11,6 @@ services: start_period: 30s security_opt: - no-new-privileges:true - networks: - - cloudflare-exposed volumes: - /etc/localtime:/etc/localtime:ro - homepage-config:/app/config @@ -37,7 +35,3 @@ services: volumes: homepage-config: - -networks: - cloudflare-exposed: - external: true diff --git a/pihole/README.md b/pihole/README.md index 9a13811..c9935d3 100644 --- a/pihole/README.md +++ b/pihole/README.md @@ -27,11 +27,6 @@ No volume backup on purpose: most of the space is the query log Teleporter or replicated to a second Pi-hole (for example with nebula-sync). -## Networks - -Connects to `cloudflare-exposed` -(must be created by the `cloudflared` service first). - ## Updates Watchtower updates the `pihole` container automatically, following the diff --git a/pihole/docker-compose.yml b/pihole/docker-compose.yml index ef6d9d3..5408856 100644 --- a/pihole/docker-compose.yml +++ b/pihole/docker-compose.yml @@ -19,8 +19,6 @@ services: timeout: 10s retries: 3 start_period: 30s - networks: - - cloudflare-exposed ports: - "53:53/tcp" - "53:53/udp" @@ -47,7 +45,3 @@ services: volumes: pihole-etc: pihole-dnsmasq: - -networks: - cloudflare-exposed: - external: true diff --git a/speedtest/README.md b/speedtest/README.md index 160e0bf..284129c 100644 --- a/speedtest/README.md +++ b/speedtest/README.md @@ -24,11 +24,6 @@ Named Docker volume, so it can be backed up with Dokploy volume backups. With Docker Compose the real name is prefixed with the project name (for example `_speedtest-config`). -## Networks - -Connects to `cloudflare-exposed` -(must be created by the `cloudflared` service first). - [speedtest-tracker]: https://github.com/alexjustesen/speedtest-tracker ## Updates diff --git a/speedtest/docker-compose.yml b/speedtest/docker-compose.yml index 675ecf4..d90ca28 100644 --- a/speedtest/docker-compose.yml +++ b/speedtest/docker-compose.yml @@ -12,8 +12,6 @@ services: timeout: 10s retries: 3 start_period: 30s - networks: - - cloudflare-exposed volumes: - speedtest-config:/config environment: @@ -46,7 +44,3 @@ services: volumes: speedtest-config: - -networks: - cloudflare-exposed: - external: true From db01092264c568b63f97ddf39549c39190ff9caf Mon Sep 17 00:00:00 2001 From: Jay Date: Fri, 6 Nov 2026 15:03:49 +0100 Subject: [PATCH 2/2] [Repo] docs: Document networks and the safe exposure order Address the review on #76: each affected README gets a Networks section, Pi-hole and Speedtest note that they must keep a Dokploy domain so Homepage can reach them, and the cloudflared README now creates the Access app before publishing the hostname and requires Traefik's 80/443 to be closed to the internet. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 5 +++-- archisteamfarm/README.md | 5 +++++ cloudflared/README.md | 13 ++++++++++--- homepage/README.md | 7 +++++++ pihole/README.md | 8 ++++++++ speedtest/README.md | 8 ++++++++ 6 files changed, 41 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f941a45..d1eb497 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,8 +28,9 @@ exactly. ## Architecture **Network model:** Services don't declare networks for -exposure. Dokploy attaches its own `dokploy-network` -automatically on deploy, and Traefik routes from there. The +exposure. Dokploy attaches its own `dokploy-network` on +deploy to every service that has a domain, and Traefik routes +from there. The Cloudflare Tunnel (`cloudflared/`) sends every public hostname to `http://dokploy-traefik`. What is exposed is decided by the tunnel's hostnames, the Dokploy domains and diff --git a/archisteamfarm/README.md b/archisteamfarm/README.md index 7435c14..9c214d6 100644 --- a/archisteamfarm/README.md +++ b/archisteamfarm/README.md @@ -34,6 +34,11 @@ Named Docker volumes, so they can be backed up with Dokploy volume backups. With Docker Compose the real names are prefixed with the project name (for example `_archisteamfarm-config`). +## Networks + +Declares no networks. Dokploy attaches `dokploy-network` on deploy +because the service has a domain; Traefik reaches it there. + ## Updates Watchtower updates the `archisteamfarm` container automatically, diff --git a/cloudflared/README.md b/cloudflared/README.md index d492ab7..723014a 100644 --- a/cloudflared/README.md +++ b/cloudflared/README.md @@ -33,14 +33,21 @@ A service is exposed by configuration, not by Docker networks: 1. Add the domain to the service in Dokploy. -2. Add the hostname to the tunnel, pointing to +2. Unless it must be public, create its Cloudflare Access + application first, so the hostname is never reachable + without it. +3. Add the hostname to the tunnel, pointing to `http://dokploy-traefik`. -3. Protect it with a Cloudflare Access application - unless it must be public. No network needs to be declared in the service's compose file. +Traefik's ports 80 and 443 must not be reachable from the +internet. Otherwise a client could send a protected hostname +straight to Traefik and skip Cloudflare Access. On +tuckfow-cloud-01 the Hetzner firewall blocks them. At home +the router forwards no ports to the server. + ## Useful Resources - [Cloudflare Tunnel docs][tunnel-docs] diff --git a/homepage/README.md b/homepage/README.md index 31e1954..a3d50fc 100644 --- a/homepage/README.md +++ b/homepage/README.md @@ -60,6 +60,13 @@ with the named volume: `services.yaml`, and remove the socket-based server from `docker.yaml`. Without the socket, label-discovered services no longer appear. +## Networks + +Declares no networks. Dokploy attaches `dokploy-network` on deploy +because the service has a domain; Traefik reaches it there. +Homepage reaches the Pi-hole and Speedtest widgets by container name +(`http://pihole`, `http://speedtest`) over that network. + ## Updates Watchtower updates the `homepage` container automatically, following the diff --git a/pihole/README.md b/pihole/README.md index c9935d3..da32409 100644 --- a/pihole/README.md +++ b/pihole/README.md @@ -27,6 +27,14 @@ No volume backup on purpose: most of the space is the query log Teleporter or replicated to a second Pi-hole (for example with nebula-sync). +## Networks + +Declares no networks. Dokploy attaches `dokploy-network` on deploy +because the service has a domain; Traefik reaches it there. +Keep a Dokploy domain on this service: without one, Dokploy does not +attach `dokploy-network` and the Homepage widget (`http://pihole`) +can no longer reach it. + ## Updates Watchtower updates the `pihole` container automatically, following the diff --git a/speedtest/README.md b/speedtest/README.md index 284129c..edbddf1 100644 --- a/speedtest/README.md +++ b/speedtest/README.md @@ -26,6 +26,14 @@ With Docker Compose the real name is prefixed with the project name [speedtest-tracker]: https://github.com/alexjustesen/speedtest-tracker +## Networks + +Declares no networks. Dokploy attaches `dokploy-network` on deploy +because the service has a domain; Traefik reaches it there. +Keep a Dokploy domain on this service: without one, Dokploy does not +attach `dokploy-network` and the Homepage widget (`http://speedtest`) +can no longer reach it. + ## Updates Watchtower updates the `speedtest` container automatically, following the