Huggingface 其实也是个大善人,有免费的 2c 16g 的 space 让你免费用。

美中不足的是普通版不能自定义域名,那么就用 cf tunnel 给他反代一下。

Cloudflare Workers 也可以反代抱抱脸空间,但是会占你访问额度,也有CPU时间限制。。。
如果你需要 Websocket 也有点麻烦。。。
更新
[!WARNING]
仅仅打包成镜像仍然有被封空间的危险!
可以套一层 nginx ,在7890端口伪装静态页面
main.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| server { listen 7860; listen [::]:7860; server_name _;
location / { root /usr/share/nginx/html; index index.html; }
location /health { return 200 'OK'; add_header Content-Type text/plain; } }
|
再启用 ssl 反代
ssl.conf.template
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| server { listen 443 ssl; listen [::]:443 ssl;
server_name ARGO_DOMAIN_PLACEHOLDER; ssl_certificate /app/cert.pem; ssl_certificate_key /app/cert.key; ssl_session_timeout 1d; ssl_session_cache shared:SSL:10m; ssl_protocols TLSv1.2 TLSv1.3;
underscores_in_headers on;
# WebSocket 支持 location /ws { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Real-IP $http_cf_connecting_ip; proxy_read_timeout 86400; proxy_send_timeout 86400; proxy_buffering off; proxy_pass http://127.0.0.1:8080; }
# API 和主服务 location / { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300s; proxy_send_timeout 300s; proxy_buffering off; proxy_pass http://127.0.0.1:8080; } }
|
加上这个启动脚本,把容器日志输出在hf的容器日志里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
| #!/bin/sh set -e
# ========================= # 环境变量(隐蔽名称) # ========================= ARGO_DOMAIN=${DD_DM:-""} ARGO_AUTH=${DD_DD:-""}
# ========================= # 日志函数 # ========================= log_info() { echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $1"; } log_ok() { echo "[OK] $(date '+%Y-%m-%d %H:%M:%S') $1"; } log_error() { echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $1"; } log_warn() { echo "[WARN] $(date '+%Y-%m-%d %H:%M:%S') $1"; }
# ========================= # 辅助函数 # ========================= wait_for_port() { local port=$1 local timeout=$2 for i in $(seq 1 $timeout); do if curl -s http://127.0.0.1:$port > /dev/null 2>&1; then return 0 fi sleep 1 done return 1 }
start_webui() { log_info "正在启动 Open WebUI..." cd /app/backend # 核心修改:使用 tee 将日志同时输出到文件和控制台,确保 HF 能看到日志 # 使用 stdbuf -oL 减少缓冲,让日志实时显示 stdbuf -oL nohup ./start.sh > /tmp/webui.log 2>&1 & }
# 实时读取日志文件的后台进程(确保日志能显示在 HF 控制台) tail_logs() { touch /tmp/webui.log tail -f /tmp/webui.log & }
echo "===== Application Startup at $(date '+%Y-%m-%d %H:%M:%S') ====="
# ========================= # 步骤 1: 启动 Nginx (健康检查) # ========================= echo "==========================================" echo " 步骤 1: 启动 Nginx (端口 7860)" echo "=========================================="
mkdir -p /var/www/html nginx
sleep 2
if curl -s http://127.0.0.1:7860/health > /dev/null 2>&1; then log_ok "Nginx 端口 7860 已就绪" else log_error "Nginx 端口 7860 检查失败" fi
# ========================= # 步骤 2: 启动 Open WebUI # ========================= echo "==========================================" echo " 步骤 2: 启动 Open WebUI" echo "=========================================="
# 检查目录是否存在 if [ ! -d "/app/backend" ]; then log_error "/app/backend 目录不存在" exit 1 fi
cd /app/backend
# 检查启动脚本是否存在 if [ ! -f "./start.sh" ]; then log_error "start.sh 不存在" exit 1 fi
# 启动 Open WebUI
tail_logs
# 启动 WebUI PORT=8080 HOST=0.0.0.0 start_webui
if wait_for_port 8080 60; then log_ok "OWU 已启动" else log_error "Open WebUI 启动超时或失败,最后 20 行日志:" tail -n 20 /tmp/webui.log # 这里不退出 exit 1,而是进入循环尝试挽救 fi
# ========================= # 步骤 3: 生成 SSL 证书 # ========================= if [ -n "$ARGO_DOMAIN" ]; then echo "==========================================" echo " 步骤 3: 生成 SSL 证书" echo "==========================================" log_info "生成证书: $ARGO_DOMAIN" mkdir -p /app openssl genrsa -out /app/cert.key 2048 2>/dev/null openssl req -new -subj "/CN=$ARGO_DOMAIN" -key /app/cert.key -out /app/cert.csr 2>/dev/null openssl x509 -req -days 36500 -in /app/cert.csr -signkey /app/cert.key -out /app/cert.pem 2>/dev/null sed "s/ARGO_DOMAIN_PLACEHOLDER/$ARGO_DOMAIN/g" /etc/nginx/ssl.conf.template > /etc/nginx/conf.d/ssl.conf nginx -s reload sleep 1 log_ok "证书生成完成,443 端口已启用" fi
# ========================= # 步骤 4: 启动隧道(进程名伪装) # ========================= if [ -n "$ARGO_AUTH" ]; then echo "==========================================" echo " 步骤 4: 启动辅助服务" echo "==========================================" # 使用重命名后的二进制 /usr/local/bin/dd-dd tunnel --no-autoupdate run --protocol http2 --token "$ARGO_AUTH" > /tmp/tunnel.log 2>&1 & sleep 5 if pgrep -f "dd-dd" >/dev/null; then log_ok "辅助服务启动成功" else log_error "辅助服务启动失败" cat /tmp/tunnel.log fi fi
# ========================= # 完成 # ========================= echo "==========================================" echo " 所有服务已启动" echo "==========================================" [ -n "$ARGO_DOMAIN" ] && log_ok "访问地址: https://$ARGO_DOMAIN" log_info "HTTP: http://localhost:7860" log_info "WebUI: http://localhost:8080"
# ========================= # 健康检查循环 # ========================= while true; do # 检查隧道 if [ -n "$ARGO_AUTH" ] && ! pgrep -f "dd-dd" >/dev/null; then log_warn "隧道进程丢失,正在重启..." /usr/local/bin/dd-dd tunnel --no-autoupdate run --protocol http2 --token "$ARGO_AUTH" > /tmp/tunnel.log 2>&1 & fi # 检查 Nginx if ! pgrep -x "nginx" >/dev/null; then log_warn "Nginx 进程丢失,正在重启..." nginx fi
if ! curl -s http://127.0.0.1:8080/health > /dev/null 2>&1; then sleep 5 if ! curl -s http://127.0.0.1:8080/health > /dev/null 2>&1; then log_warn "OWU (端口 8080) 无响应,尝试重启..." pkill -f "uvicorn" || true pkill -f "start.sh" || true # 重启 PORT=8080 HOST=0.0.0.0 start_webui fi fi sleep 60 done
|
打包好的镜像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| FROM ghcr.io/open-webui/open-webui:main
USER root
RUN apt-get update && apt-get install -y \ nginx \ openssl \ curl \ procps \ && rm -rf /var/lib/apt/lists/*
COPY --from=cloudflare/cloudflared:latest /usr/local/bin/cloudflared /usr/local/bin/dd-dd
COPY main.conf /etc/nginx/conf.d/main.conf RUN rm -f /etc/nginx/conf.d/default.conf && \ rm -rf /etc/nginx/sites-enabled/* && \ rm -rf /etc/nginx/sites-available/* COPY ssl.conf.template /etc/nginx/ssl.conf.template
COPY entrypoint.sh /entrypoint.sh COPY index.html /usr/share/nginx/html/index.html
RUN chmod +x /entrypoint.sh && \ sed -i 's/\r$//' /entrypoint.sh
EXPOSE 8080
ENV DD_DM="" \ DD_DD="" \ PORT=8080 \ HOST=0.0.0.0
CMD ["/entrypoint.sh"]
|
前置条件
- 一个 Cloudflare 账号
- 一个已托管在 Cloudflare 的域名
- 一个 dockerhub 账号
- 一个 github 账号
- 一个运行中的 Huggingface Space
开始
创建 Cloudflare Tunnel
- 登录 Cloudflare Dashboard
- 选择你的域名
- 进入 Zero Trust > Networks > Tunnels
- 点击 Create a tunnel
- 选择 Cloudflared,输入隧道名称(如
huggingface-space)
- 点击 Save tunnel
配置 Tunnel
创建完成后,你会看到安装 cloudflared 的命令,但我们不需要安装,因为我们只用它做反向代理。
复制那个 ey开头的 base64 token 备用。
配置公共主机名
- 在 Tunnel 详情页面,切换到 Public Hostname 标签
- 点击 Add a public hostname
- 填写配置:
具体配置看你的容器,需要监听什么端口,什么协议,以open-webui为例
- Subdomain: 输入子域名(如
chat)
- Domain: 选择你的域名
- Type: 选择
HTTP
- URL: 由于和 openwebui 镜像打包在一起,填
localhost:7860
- 点击 Save hostname

配置保存后,Cloudflare 会自动创建 DNS 记录并配置 Tunnel。通常在几分钟内即可生效。
使用 Github Actions 自动构建镜像
直接在 hf space 的 Dockerfile 里把 cloudflared 镜像是不行的,你的 space 会被秒封(pause)。于是,你必须预先打包好镜像,把想要部署的镜像和 cloudflared 打包在一起。
你可以直接 Fork 我的仓库。
以下是自己操作的流程。
新建一个仓库,仓库根目录创建Dockerfile。
这里以 open-webui 为例,镜像、端口和依赖安装自己改一下。
1 2 3 4 5 6 7 8 9 10 11 12 13
| FROM {你想部署的镜像}
USER root RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=cloudflare/cloudflared:latest /usr/local/bin/cloudflared /usr/local/bin/cloudflared
ENV PORT=7860 ENV HOME=/tmp
EXPOSE 7860
CMD cloudflared tunnel --no-autoupdate run --token ${ARGO_AUTH} & bash start.sh
|
创建 Github Actions Workflow
在仓库 .github/workflows下创建 docker-build.yml
写入以下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| name: Build and Push Open-WebUI-Argo
on: push: branches: [ "main" ] workflow_dispatch:
env: DOCKER_IMAGE: {你的用户名}/{你的镜像名}
jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4
- name: Set up QEMU uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: | ${{ env.DOCKER_IMAGE }}:latest ${{ env.DOCKER_IMAGE }}:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max
|
把镜像推送到 Dockerhub
去 Dockerhub 的 Account Settings > Settings > Personal Access Tokens 拿到你账号的token。
你想推到 GHCR 也可以的,记得把你的 GHCR 镜像设为公开

创建新 token,填好名称,例如github actions。
类型至少选 Read & Write。
token只会展示一次,复制形如 dcr_xxx 的 token 备用。
添加 Github 仓库环境变量
进入你仓库的 Settings > Secrets and variables
如果让你创建环境,随便创建一个就行。
填入如下两个 secrets:
注意:必须是 secrets 而不是 variables
DOCKERHUB_TOKEN,填入你上一步获得的 token
DOCKERHUB_USERNAME,填入你dockerhub的用户名
如果是 非public 的 GHCR 镜像仓库,你需要填的是 github user token,workflows配置文件也需要修改推送和登录部分

等待 Actions 跑完成功即可

修改你的 HF Space
把你 Huggingface Space 仓库的 Dockerfile 修改为拉你自己打包的新镜像。
还是以我的open-webui为例
1
| FROM docker.io/你的用户名/镜像名:标签
|
如果是 GCHR 仓库:
1
| FROM ghcr.io/你的用户名/镜像名:标签
|
在你的 hf space 环境变量添加一个 secret 变量,填入你的 cloudflared token。
ARGO_AUTH=eyJxxxxx

等待生效
启动你的 HF Space,去你的 cf tunnel 查看状态,为正常 tunnel 就没问题

访问你配置的域名(如 https://demo.yourdomain.com),就能看到你的 Huggingface Space 了!
添加访问控制
你可以使用 Cloudflare Access 为你的 Space 添加身份验证:
- 在 Tunnel 的 Public Hostname 设置中
- 找到 Access 部分
- 创建访问策略,可以设置:
- 邮箱白名单
- GitHub OAuth
- Google OAuth
- auth0, keyloack…
自定义缓存规则
既然用了 cf,那就不得不说他的 CDN 服务了,静态资源多的可以试试。
在 Cloudflare Rules 中可以为你的域名设置:
其他
如果使用 HTTPS ,一般都是 nginx 反代的,记得在 tunnel 设置里关闭 TLS 验证,开启 **HTTP/2**访问。
gRPC 的话要去你的域名设置页面开启 通过gRPC访问你的域名。
通过 Cloudflare Tunnel (Cloudflare Workers也是可以的),我们可以轻松为 Huggingface Space 绑定自定义域名,无需任何服务器,完全免费。这个方法同样适用于其他需要反向代理的服务。
关于优选加速访问,cf tunnel 可以通过 SaaS 优选哦。
[!CAUTION]
谨慎使用,千万不要在 Space 明文使用 cf tunnel,否则你的 space 会喜提秒封。
[!CAUTION]
也不要在 Huggingface Space 部署 AList,也会秒封。
相关链接: