Skip to content
Skip to the article
In Containers: 4 articles
Containers

Run a Cloudflare Tunnel in Docker

Run cloudflared in a container with either a dashboard-managed tunnel token or a locally managed config file and credentials.

Updated
Applies to
  • cloudflared 2025+
  • Docker Engine 27+
Tags
  • docker
  • cloudflare
  • cloudflared
  • tunnels
Reading time
4 min

A Cloudflare Tunnel publishes services on a private network, such as a NAS web interface, a printer, or an internal app, through Cloudflare without opening inbound firewall ports. This page shows how to run the cloudflared connector in Docker, either with a token from the Cloudflare dashboard or with a locally managed configuration file.

Choose a Management Mode

  • Remotely managed (recommended). You create the tunnel and its public hostnames in the Cloudflare Zero Trust dashboard, and the container needs only a token. Routing changes don't require touching the host.

  • Locally managed. You define the tunnel's routes in a config.yml on the host. Use this when you want routing kept in version control or managed alongside other host configuration.

Remotely Managed Tunnel

  1. In the Cloudflare Zero Trust dashboard, open the Tunnels page under Networks, create a tunnel of type Cloudflared, and copy the token from the install command it shows.

  2. Save the token to a file only root can read, so it doesn't appear in docker inspect or the process list:

    mkdir -p /opt/cloudflared
    printf '%s' '<TUNNEL_TOKEN>' > /opt/cloudflared/token
    chown 65532:65532 /opt/cloudflared/token
    chmod 400 /opt/cloudflared/token
  3. Start the connector:

    docker run -d --name cloudflared --restart unless-stopped --network host \
      -v /opt/cloudflared/token:/etc/cloudflared/token:ro \
      cloudflare/cloudflared:latest tunnel run --token-file /etc/cloudflared/token
  4. In the tunnel's settings, add a public hostname route (for example app.example.com → http://localhost:8080). Newer dashboards call these Published application routes.

The image runs as the unprivileged user 65532, which is why the token file is owned by that UID.

Locally Managed Tunnel

Create the Tunnel and Credentials

Do the one-time authorization with cloudflared installed on a workstation (not in the container). tunnel login opens a browser and saves an account certificate to ~/.cloudflared/cert.pem. tunnel create writes the tunnel's credentials file:

cloudflared tunnel login
cloudflared tunnel create <TUNNEL_NAME>
cloudflared tunnel route dns <TUNNEL_NAME> app.example.com
Created tunnel <TUNNEL_NAME> with id <TUNNEL_ID>
Tunnel credentials written to ~/.cloudflared/<TUNNEL_ID>.json

Copy only <TUNNEL_ID>.json to the Docker host. The container needs only the credentials file to run the tunnel. cert.pem is for management commands and should stay on the workstation.

Write the Configuration

Create a directory on the host, for example /opt/cloudflared, containing the credentials file and a config.yml:

{
  "AccountTag": "<ACCOUNT_TAG>",
  "TunnelSecret": "<TUNNEL_SECRET>",
  "TunnelID": "<TUNNEL_ID>"
}
tunnel: <TUNNEL_ID>
credentials-file: /etc/cloudflared/<TUNNEL_ID>.json

originRequest:
  connectTimeout: 10s

ingress:
  - hostname: printer.example.com
    service: https://192.0.2.46:631
    originRequest:
      noTLSVerify: true
  - hostname: drive.example.com
    service: http://127.0.0.1:8080
  - service: http_status:404

The credentials-file path is the path inside the container. Ingress rules are matched top to bottom, and the last rule must be a catch-all with no hostname.

Warning

noTLSVerify: true turns off certificate checking between cloudflared and the origin. Limit it to individual devices with self-signed certificates (like the printer above) rather than setting it globally. Where possible, use originServerName or caPool instead.

Make the files readable by the container user:

chown -R 65532:65532 /opt/cloudflared
chmod 600 /opt/cloudflared/<TUNNEL_ID>.json

Start the Container

docker run -d --name cloudflared --restart unless-stopped --network host \
  -v /opt/cloudflared:/etc/cloudflared:ro \
  cloudflare/cloudflared:latest tunnel --config /etc/cloudflared/config.yml run

--network host lets ingress rules reach services on the host at 127.0.0.1 and on the LAN directly. If you'd rather keep cloudflared on a Docker network, attach it to the same network as the services and use their container names in service: URLs, such as http://app:8080.

Docker Compose

The token method as a Compose service:

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    restart: unless-stopped
    network_mode: host
    command: tunnel run --token-file /etc/cloudflared/token
    volumes:
      - /opt/cloudflared/token:/etc/cloudflared/token:ro

Check the Tunnel

docker logs -f cloudflared

Look for Registered tunnel connection lines. The tunnel also shows as Healthy in the dashboard. To publish a static site behind the tunnel on a NAS, see Docker macvlan Networking on Synology.

Sources

This article is in the public domain (CC0 1.0), code samples included. Use it however helps you.