安装cloudflared
# 64-bit AMD/Intel
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -O cloudflared
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/cloudflared
cloudflared -v
目录结构
ls -R .
.
├── docker-compose.yml
├── Dockerfile
├── entrypoint.sh
└── cloudflared/
└── cert.pem
步骤 1:准备 entrypoint.sh(放项目根目录)
这个脚本会:
首次:用 ./cloudflared/cert.pem 自动创建 <TunnelID>.json + 生成 config.yml;
以后:检测到 config.yml 已存在就不覆盖,直接按你改过的配置启动。
把下面内容保存为 entrypoint.sh(确保是 LF 行尾):
#!/bin/sh
# entrypoint.sh - First-run auto-generate tunnel creds & config.yml, then run cloudflared.
# Requires: /etc/cloudflared/cert.pem (obtained via `cloudflared tunnel login` on any machine)
set -euo
###############################################################################
# <<< EDIT DEFAULTS HERE >>> #
###############################################################################
# Cloudflared working directory (mounted from host via docker-compose)
WORKDIR="/etc/cloudflared"
# Your origin service (all hostnames will map to this upstream)
SERVICE_IP="x.x.x.x"
SERVICE_PORT="80"
# Comma-separated hostnames to expose via this tunnel
HOSTS="aaa.com"
# Optional: name for the tunnel when creating on first run (if no creds exist)
# If empty, we'll auto-generate with a timestamp.
TUNNEL_NAME="${TUNNEL_NAME:-}"
# Optional: if you later decide to run with token instead of creds, export TUNNEL_TOKEN before `docker compose up -d`
TUNNEL_TOKEN="${TUNNEL_TOKEN:-}"
###############################################################################
# DO NOT EDIT BELOW #
###############################################################################
CONFIG_YML="$WORKDIR/config.yml"
log() { printf '%s %s\n' "[entrypoint]" "$*"; }
fail() { printf '%s %s\n' "[entrypoint][ERROR]" "$*" >&2; exit 1; }
# Find the first existing creds json (if any)
find_existing_json() { ls "$WORKDIR"/*.json 2>/dev/null | head -n1 || true; }
# Extract TunnelID from creds json
extract_tunnel_id() {
# sed-friendly parse for: "TunnelID":"<uuid>"
sed -n 's/.*"TunnelID"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$1" | head -n1
}
# Generate a config.yml with provided TunnelID + host mappings
generate_config() {
_mode="$1" # "creds" or "token"
_tunnel_id="${2:-}"
log "generating $CONFIG_YML (mode=$_mode${_tunnel_id:+, TunnelID=$_tunnel_id})"
{
if [ "$_mode" = "creds" ]; then
echo "tunnel: $_tunnel_id"
echo "credentials-file: $WORKDIR/${_tunnel_id}.json"
echo
fi
echo "ingress:"
IFS=','
for h in $HOSTS; do
h="$(echo "$h" | awk '{$1=$1;print}')"
[ -n "$h" ] || continue
echo " - hostname: $h"
echo " service: http://$SERVICE_IP:$SERVICE_PORT"
done
echo " - service: http_status:404"
} > "$CONFIG_YML"
log "wrote $CONFIG_YML"
}
log "WORKDIR=$WORKDIR"
log "SERVICE=http://$SERVICE_IP:$SERVICE_PORT"
log "HOSTS=$HOSTS"
# Ensure workdir exists & is accessible
mkdir -p "$WORKDIR"
chmod 700 "$WORKDIR" || true
# If user already has a config.yml, never overwrite it—just run with it
if [ -f "$CONFIG_YML" ]; then
log "$CONFIG_YML exists -> do not overwrite"
if [ -n "$TUNNEL_TOKEN" ]; then
exec cloudflared tunnel --no-autoupdate --config "$CONFIG_YML" run --token "$TUNNEL_TOKEN"
else
exec cloudflared tunnel --no-autoupdate --config "$CONFIG_YML" run
fi
fi
# If user wants token mode on first run, allow it (no creds.json needed)
if [ -n "$TUNNEL_TOKEN" ]; then
log "first-run in TOKEN mode (no creds json needed)"
generate_config "token"
exec cloudflared tunnel --no-autoupdate --config "$CONFIG_YML" run --token "$TUNNEL_TOKEN"
fi
# Try to reuse existing creds if any
EXISTING_JSON="$(find_existing_json || true)"
if [ -n "$EXISTING_JSON" ]; then
log "found existing creds: $EXISTING_JSON"
TUNNEL_ID="$(extract_tunnel_id "$EXISTING_JSON")"
[ -n "$TUNNEL_ID" ] || fail "cannot parse TunnelID from $EXISTING_JSON"
# Rename to <TunnelID>.json if needed
[ "$(basename "$EXISTING_JSON")" = "${TUNNEL_ID}.json" ] || mv "$EXISTING_JSON" "$WORKDIR/${TUNNEL_ID}.json"
generate_config "creds" "$TUNNEL_ID"
exec cloudflared tunnel --no-autoupdate --config "$CONFIG_YML" run
fi
# No config.yml, no creds.json => create via cert.pem (cert mode)
[ -f "$WORKDIR/cert.pem" ] || fail "$WORKDIR/cert.pem not found. Provide it (from 'cloudflared tunnel login')."
log "cloudflared version:"
cloudflared --version || true
log "creating tunnel via cert.pem..."
TMP_CREDS="$WORKDIR/credentials.json"
# Prepare a non-empty tunnel name
if [ -z "$TUNNEL_NAME" ]; then
TUNNEL_NAME="auto-$(date +%s)"
fi
log "TUNNEL_NAME=$TUNNEL_NAME"
# IMPORTANT: name must be the last arg to 'tunnel create'
cloudflared tunnel \
--origincert "$WORKDIR/cert.pem" \
create \
--credentials-file "$TMP_CREDS" \
"$TUNNEL_NAME"
[ -s "$TMP_CREDS" ] || fail "$TMP_CREDS not created or empty"
TUNNEL_ID="$(extract_tunnel_id "$TMP_CREDS")"
if [ -z "$TUNNEL_ID" ]; then
log "file content of $TMP_CREDS:"
cat "$TMP_CREDS" || true
fail "cannot parse TunnelID from $TMP_CREDS"
fi
mv "$TMP_CREDS" "$WORKDIR/${TUNNEL_ID}.json"
log "created creds: $WORKDIR/${TUNNEL_ID}.json"
generate_config "creds" "$TUNNEL_ID"
exec cloudflared tunnel --no-autoupdate --config "$CONFIG_YML" run
步骤 2:准备 Dockerfile(放项目根目录)
# 取官方 cloudflared 二进制
FROM cloudflare/cloudflared:latest AS src
# 运行层:有 /bin/sh 的极简系统
FROM alpine:3.20
COPY --from=src /usr/local/bin/cloudflared /usr/local/bin/cloudflared
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
VOLUME ["/etc/cloudflared"]
ENTRYPOINT ["/entrypoint.sh"]
步骤 3:docker-compose.yml
version: "3.8"
services:
cloudflared:
build: . # ← 用当前目录的 Dockerfile 构建
image: my-cloudflared:local # ← 给本地镜像起个名字
container_name: cloudflared
restart: unless-stopped
volumes:
- ./cloudflared:/etc/cloudflared # 放 cert.pem / 生成的 json 与 config.yml
步骤 4:准备证书 & 启动
1、cloudflared登陆授权, 成功后得到 ~/.cloudflared/cert.pem
cloudflared tunnel login
2、把本机(能开浏览器)上登录生成的证书复制到项目目录:
mkdir -p ./cloudflared
cp ~/.cloudflared/cert.pem ./cloudflared/cert.pem
3、构建运行
docker compose build
docker compose up -d
docker logs -f cloudflared
4、宿主机的 ./cloudflared/ 下会多出
<TunnelID>.json
config.yml