Teitunnel

Use cases

Cloudflare Tunnel for Docker Compose

Add Teitunnel to a Docker Compose stack and route hostnames to the containers next to it through Cloudflare Tunnel, with the API token kept in a Docker secret.

Add one service to your Compose file and every container in the stack can have its own hostname, with DNS records made for you, without publishing a single port on the host. In this tutorial you put a web app and an API online at app.yourhost.com and api.yourhost.com, check their health, and add a web dashboard behind a login.

You need: Docker with Compose, a Cloudflare account with a domain, and an API token.

1. Create the API token

In the Cloudflare dashboard, create a token (an account-owned token is best for servers) with Account · Cloudflare Tunnel · Edit, Zone · DNS · Edit and Zone · Zone · Read; add the two Access permissions if you want logins. See every permission.

Save it next to the Compose file, and keep it out of git:

printf '%s' 'your-token' > cloudflare_api_token
echo cloudflare_api_token >> .gitignore

2. Add Teitunnel to the Compose file

compose.yaml
services:
  web:
    image: nginx:alpine          # your app; no `ports:` needed

  api:
    image: ghcr.io/you/api:latest

  teitunnel:
    image: teispace/teitunnel:0.2
    environment:
      CLOUDFLARE_API_TOKEN_FILE: /run/secrets/cloudflare_api_token
    secrets: [cloudflare_api_token]
    volumes: [teitunnel:/data]   # the tunnel and its routes
    restart: unless-stopped

secrets:
  cloudflare_api_token:
    file: ./cloudflare_api_token

volumes:
  teitunnel:

The image (also at ghcr.io/teispace/teitunnel) has the teitunnel command and Cloudflare's own cloudflared, runs as a non-root user, and keeps its state in /data. The token is read from the secret for each command and never written to the volume. Pin a minor version (:0.2) so upgrades happen when you choose.

3. Add routes

Start the stack, then add a route per service, pointing at the service name and its container port:

docker compose up -d
docker compose run --rm teitunnel route add app.yourhost.com http://web:80 --yes
docker compose run --rm teitunnel route add api.yourhost.com http://api:8000 --yes
docker compose restart teitunnel

The first route creates the tunnel. The routes live in the teitunnel volume, so they survive restarts and upgrades. Leave out --yes to review each plan first.

4. Check it

docker compose ps                                  # teitunnel shows (healthy)
docker compose run --rm teitunnel routes           # every route and whether it's live
docker compose run --rm teitunnel doctor           # problems, with fixes

The container runs teitunnel up by default, and its health check (teitunnel routes --check) fails unless every route is live, so docker compose ps tells you when something's wrong. Open https://app.yourhost.com.

Things that trip people up

  • localhost is the container itself. Route to http://web:80, never http://localhost:80. To reach a service on the Docker host, use http://host.docker.internal:3000 (add extra_hosts: ["host.docker.internal:host-gateway"] on Linux).
  • Use the container port, not a published host port: http://api:8000, even if ports: maps it to 18000 on the host.
  • Different Compose projects: the Teitunnel container must share a network with the service. Attach it to the other project's network with networks:.
  • A route shows as down after route add: restart the teitunnel container so its connector picks up the new tunnel, as in step 3.

5. A web dashboard behind a login (optional)

Run serve instead of up to add a small dashboard for routes and tunnels, and publish the dashboard through Teitunnel itself with a login:

  teitunnel:
    # …as above
    command: ["serve", "--listen", "0.0.0.0:8765", "--allow-remote", "--secure-cookies"]

Set the dashboard password once (it's kept, hashed, in the teitunnel volume), then route a hostname with a login to it:

docker compose run --rm teitunnel serve --set-password
docker compose up -d
docker compose run --rm teitunnel route add admin.yourhost.com http://teitunnel:8765 \
  --allow team@yourhost.com --yes

--allow-remote lets serve listen beyond the container's loopback, where only the Compose network can reach it; visitors get HTTPS from Cloudflare, so --secure-cookies is right. The same port serves the JSON API with API keys. See web dashboard and API.

From an existing setup

  • A project file: put a teitunnel.yml in the working directory and up applies it first.
  • Rather not run Teitunnel in the stack? teitunnel export docker-compose (export) writes a Compose service running plain cloudflared for a machine's tunnel; its routes stay in Cloudflare, and the run token goes in an .env file, never in the export.

Where to go next

Edit on GitHub

Last updated on

On this page