# Running Cloudflare Tunnel with Docker
If you want to expose an internal service to the internet **without opening inbound ports**, **without port-forwarding**, and without maintaining a complex reverse-proxy stack, **Cloudflare Tunnel** is one of the cleanest options.
In this post, I’ll show a **token-based** Cloudflare Tunnel setup using **Docker**. The goal is a setup that is:
- **Configless** (no `config.yml` required)
- **Stateless** (easy to recreate anywhere)
- **Restart-safe** (survives reboots)
- **CI/CD friendly** (works great with secrets)
---
## What you’ll build
By the end, you’ll have a `cloudflared` container running that:
- Authenticates to your Cloudflare account using a **tunnel token**
- Runs continuously on your server
- Can reach your internal services via a Docker network (e.g., `app-net`)
---
## Prerequisites
Before you start:
- Docker is installed on your machine/server
- You have access to **Cloudflare Zero Trust**
- You created a **Tunnel** in Zero Trust
- You have the **Tunnel Token** for that tunnel
> You can find the token in Cloudflare Zero Trust → **Tunnels** → select your tunnel → **Run connector** (or equivalent).
---
## Why token-based (configless) setup?
Using a token directly:
- avoids managing `config.yml` and credentials files
- makes the container **stateless** and reproducible
- fits nicely into infrastructure automation and CI/CD pipelines
In production, you should store the token as a secret (e.g., `.env`, Docker secrets, CI/CD secrets) instead of hardcoding it.
---
## Step-by-step: the script structure
### 1) Run Bash in “safe mode”
We start with strict Bash flags:
- exit on error
- fail on undefined variables
- fail on pipeline errors
This prevents silent failures and makes the script more reliable.
### 2) Define the core variables
We keep three things configurable:
- container name
- image version (pinned for reproducibility)
- tunnel token
### 3) Start the Docker container
We run `cloudflared` in detached mode and ensure it restarts automatically.
We also attach it to a Docker network (`app-net`) so it can reach internal services by container name.
### 4) Run the tunnel with `--no-autoupdate`
When using Docker, auto-updating inside the container is not desirable. You should update by changing the image tag.
---
## Full script (copy/paste)
> Save as `cloudflare-tunnel.sh`, then run: `chmod +x cloudflare-tunnel.sh && ./cloudflare-tunnel.sh`
```bash
#!/usr/bin/env bash
set -euo pipefail
# ============================================================
# Cloudflare Tunnel setup script (bash)
#
# This script:
# - starts a cloudflared container
# - runs a Cloudflare Tunnel using a token
# ============================================================
# ------------------------------------------------------------
# 0) Base variables
# ------------------------------------------------------------
CONTAINER="cloudflared"
IMAGE="cloudflare/cloudflared:2025.11.1"
TUNNEL_TOKEN="eyJhIjo..." # <-- replace with your real token
# ------------------------------------------------------------
# 1) Start cloudflared container
# ------------------------------------------------------------
docker run -d \
--name "$CONTAINER" \
--restart unless-stopped \
--network app-net \
"$IMAGE" \
tunnel --no-autoupdate run --token "$TUNNEL_TOKEN"