Resolving Docker network isolation between cloudflared and an Astro static site container to restore Cloudflare Zero Trust tunnel functionality.

Docker Network Fix


cloudflared Stuck on the Wrong Container

The old setup had cloudflared tunneling to 172.18.0.1:8000, which was the WordPress container’s address. After deleting WordPress, that port vanished and cloudflared kept trying to hit a dead endpoint. The tunnel definition in /data/config/docker-compose.yml didn’t know about the new Astro container, and the port mapping was hard-coded to an IP that no longer existed. Any request to the tunnel would fail silently, returning 502 errors to visitors.

cloudflared:
  image: cloudflare/cloudflared:latest
  command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TOKEN}
  ports:
    - "172.18.0.1:8000:8000"
  networks:
    - config_default

Watch out: Hard-coded IPs in Docker Compose port mappings are fragile. If the container restarts and gets a new IP (common with Docker’s default bridge network), the mapping breaks silently. Always use container names for service discovery when possible.

Why Docker Isolates Container Networks by Default

Docker creates an internal DNS resolver for each network. Containers can only resolve other containers that share the same network. In my case, cloudflared lived in config_default, while sovereign-blog lived in sovereign-blog_default. The tunnel couldn’t reach the Astro container by name because the networks were isolated.

$ docker exec -it cloudflared ping sovereign-blog
ping: sovereign-blog: Name or service not known

Even 127.0.0.1 wouldn’t work from inside a container because Docker binds ports to the host interface only. The host’s loopback isn’t shared between containers. This isolation is intentional—Docker’s default network model prioritizes security and predictability over convenience.

Gotcha: Docker’s internal DNS only resolves container names within the same network. If you try to ping a container by name from a different network, you’ll get Name or service not known, even if the container exists. This is a common source of confusion when debugging multi-network setups.

Limitation: Docker’s default bridge network assigns IPs dynamically. If you rely on IPs (e.g., 172.18.0.1:8000), your setup is brittle. Container restarts can change IPs, breaking hard-coded references. Use container names and Docker’s internal DNS instead.

Warning: Docker’s network isolation can cause unexpected behavior when mixing legacy setups (like my hard-coded IP) with modern service discovery. Always verify connectivity with docker exec and curl before assuming a tunnel will work.

Attaching cloudflared to the Astro Network

Fixed by editing cloudflared’s compose file to join both networks. Added the external Astro network and kept the default for other services.

cloudflared:
  image: cloudflare/cloudflared:latest
  command: tunnel --no-autupdate run --token ${CLOUDFLARE_TOKEN}
  networks:
    - default
    - sovereign-blog_default

networks:
  default: {}
  sovereign-blog_default:
    external: true

Applied the change with:

sudo docker compose -f /data/config/docker-compose.yml up -d cloudflared

Version note: I’m using cloudflare/cloudflared:latest (as of August 2024, this is 2024.8.1). The --no-autoupdate flag prevents the container from self-updating, which is useful for stability but means you’ll need to manually update the image version when you want to upgrade.

Path note: The compose file is located at /data/config/docker-compose.yml on my host. Your path may differ depending on your Docker setup.

Error handling: If you see network sovereign-blog_default not found, double-check the network name in docker network ls. Networks created by other compose files (e.g., sovereign-blog_default) must be marked as external: true in your compose file.

The tunnel now resolves sovereign-blog via Docker’s internal DNS. The Astro container exposes port 4321, so the tunnel routes traffic there instead of the old WordPress port.

Updating the Cloudflare Zero Trust Tunnel

In the Cloudflare dashboard under Zero Trust → Networks → Tunnels → sparki, I removed the old route:

www.example.com → 172.18.0.1:8000  # WordPress, offline

Then added the new route:

www.example.com → http://sovereign-blog:4321

Cloudflare note: The tunnel name sparki is arbitrary—it’s just the name I gave to the tunnel in the Cloudflare dashboard. You can find your tunnel names under Zero Trust → Access → Tunnels.

DNS propagation: Cloudflare’s Zero Trust tunnels update almost instantly, but DNS changes (e.g., CNAME records) may take longer to propagate. If you’re switching domains, expect a delay of up to 24 hours for full propagation.

The tunnel picked up the change immediately. DNS resolution worked because cloudflared and sovereign-blog were now on the same network.

Debugging tip: If the tunnel doesn’t update, check the Cloudflare dashboard for errors. Common issues include:

  • Incorrect tunnel token (check ${CLOUDFLARE_TOKEN} in your compose file).
  • Missing or misconfigured DNS records in Cloudflare’s dashboard.
  • Firewall rules blocking traffic to the tunnel’s ingress IP.

The sovereign-blog Container Setup

The Astro container runs in its own compose file:

services:
  sovereign-blog:
    image: ghcr.io/your/repo:sovereign-blog:latest
    ports:
      - "127.0.0.1:4321:4321"
    networks:
      - sovereign-blog_default
    environment:
      - NODE_ENV=production

Image note: The Astro image is hosted on GitHub Container Registry (ghcr.io). The :latest tag is convenient but risky for production. Pin to a specific version (e.g., :v1.2.3) to avoid unexpected updates.

Port binding: The Astro container binds to 127.0.0.1:4321 on the host, which restricts access to the local machine only. This is a security best practice, but it means cloudflared must resolve the container by name (sovereign-blog) within the sovereign-blog_default network.

Error message: If you see Error response from daemon: driver failed programming external connectivity, it usually means the port is already in use. Check for conflicts with sudo lsof -i :4321 or sudo netstat -tulnp | grep 4321.

It binds to the host’s loopback on 4321, but the container name sovereign-blog is resolvable within the sovereign-blog_default network. cloudflared now tunnels directly to that name and port.

What’s Left to Do

The plan is to flip the switch for the webshop route once the Astro build is stable. Then update the Amazon Associates account to use the new domain. No more WordPress cruft, just a static site served through a tunnel that actually knows where to send traffic.

Future-proofing: Consider adding health checks to your Astro container (e.g., /healthz endpoint) to ensure the tunnel only routes traffic to healthy services. Cloudflare Zero Trust supports health-based routing, which can prevent downtime during deployments.

Monitoring gap: Currently, I don’t have alerts for tunnel failures. Set up Cloudflare’s built-in monitoring or integrate with a tool like Prometheus to track tunnel status and container health.

Security note: The sovereign-blog_default network is currently isolated, but if you add more services (e.g., a database), ensure they’re only accessible to trusted containers. Use Docker’s network policies or Cloudflare’s Zero Trust to restrict access further.

Performance tip: If you notice high latency, check Docker’s network performance. The default bridge network can introduce overhead. Consider using macvlan or host network mode for high-throughput services, though this reduces isolation.

Backup plan: Always keep the old WordPress container around for a few days after migration. If something goes wrong with the Astro setup, you can quickly revert by updating the tunnel route back to the old container.

Documentation gap: I didn’t document the exact steps for migrating WordPress data to Astro. If you’re doing this yourself, plan for:

  • Exporting WordPress content (use the WordPress export tool).
  • Converting posts to Markdown (tools like wordpress-export-to-markdown can help).
  • Updating internal links and images to use the new domain.

Gotcha: Astro’s static site generator may not handle dynamic content (e.g., comments) out of the box. If you need comments, consider integrating a third-party service like Disqus or Commento.

Version check: Verify your Astro version with astro --version. As of August 2024, the latest stable is 4.10.2. Some plugins or themes may require specific versions, so pin your dependencies in package.json.

What I Actually Use

  • cloudflare/cloudflared:latest (v2024.8.1): Handles the Zero Trust tunnel without exposing the host to the internet.
  • ghcr.io/your/repo:sovereign-blog:latest (v1.2.3): The Astro static site container serving the blog and shop.
  • Docker Compose v2.23.0: Keeps the networking clean and avoids manual port juggling.
Flow

Docker Network Fix

Resolving cloudflared tunnel routing to Astro container

1
Problem Tunnel routes to dead WordPress endpoint
2
Diagnosis Containers isolated in separate networks
3
Fix Attach cloudflared to Astro network
4
Update Replace route to new Astro service
5
Result Tunnel resolves to correct container
Illustration: Docker Network Fix