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 >> .gitignore2. Add Teitunnel to the Compose file
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 teitunnelThe 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 fixesThe 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
localhostis the container itself. Route tohttp://web:80, neverhttp://localhost:80. To reach a service on the Docker host, usehttp://host.docker.internal:3000(addextra_hosts: ["host.docker.internal:host-gateway"]on Linux).- Use the container port, not a published host port:
http://api:8000, even ifports:maps it to18000on 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 theteitunnelcontainer 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.ymlin the working directory andupapplies it first. - Rather not run Teitunnel in the stack?
teitunnel export docker-compose(export) writes a Compose service running plaincloudflaredfor a machine's tunnel; its routes stay in Cloudflare, and the run token goes in an.envfile, never in the export.
Where to go next
Last updated on
SSH, RDP and databases through Cloudflare Tunnel
Reach a machine's SSH, Remote Desktop, file shares or database from anywhere through Cloudflare Tunnel, with no inbound port open, and lock it down with Cloudflare Access.
What is Cloudflare Tunnel?
How Cloudflare Tunnel and cloudflared publish services without opening ports, what a tunnel, connector, route, DNS record and Quick Tunnel are, what it costs, and the limits worth knowing before you rely on it.