home

2 min read

Cloudflare Tunnel Health Checks using Docker Compose

Cloudflare Tunnel and Docker Compose work great together for hosting applications without exposing ports directly to the Internet. Sometimes we need services to wait until cloudflared is connected and ready to receive requests before starting, but how?

Health Checks in cloudflared with Kubernetes

The Cloudflare docs describe how to check for readiness when using Kubernetes, but not how to do it using Docker Compose. In Kubernetes, we can add a readiness probe like this:

This is great for Kubernetes, but what about Docker Compose?

Health Checks in Docker Compose

Compose supports health checks by running arbitrary commands within the container (service) to determine when it's ready. Other services in the Compose file can specify dependencies on other services to ensure they don't start until the dependent service is healthy.

Here's a good article showing how to wait to start a service until a mysql service is ready to receive connections:

services:
  web:
    image: alpine:latest
    depends_on:
      db:
        condition: service_healthy
  db:
    image: mysql:5.7
    ports:
      - "3306:3306"  # Exposes port 3306 from the container to port 3306 on the host
    environment:
      MYSQL_ROOT_PASSWORD: root
    healthcheck:
      test: ["CMD", "mysql", "-u", "root", "-proot", "--execute", "SHOW DATABASES;"]
      interval: 3s
      retries: 5
      timeout: 5s

credit: (source)

How Do We Health Check cloudflared with Docker Compose?

Now that we know how to create health checks in Compose, we need to figure out how to add one for cloudflared.

In the past, adding healthchecks required workarounds (e.g. building a custom cloudflared image containing curl). Thankfully, it's gotten much simpler since the cloudflared tunnel ready command was added. Now, all we need to do is add the TUNNEL_METRICS environment variable and a healthcheck to our cloudflare-tunnel service in docker-compose.yml:

services
  cloudflare-tunnel:
    image: cloudflare/cloudflared:2026.2.0
    restart: always
    command: tunnel --no-autoupdate run
    environment:
      - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
      - TUNNEL_METRICS=0.0.0.0:60123 # Enable metrics endpoint
    healthcheck: # enable healthchecks
      test: ['CMD', 'cloudflared', 'tunnel', 'ready']
      interval: 1s
      timeout: 2s
      retries: 60

With the new health checks in place, there is no longer a race condition where Compose would try to pull images from Gitea before the Cloudflare Tunnel was connected. Success!

Conclusion

While Cloudflare Tunnel is one of the most useful tools I've ever used, it wasn't immediately obvious that I should add healthchecks.


Note: this post was updated on 2026-02-22 with latest best practices for cloudflared healthchecks. The original post can be viewed here.