云上白嫖-we-events-部署篇

上篇发出后,有朋友私信问这玩意自己能不能搭一套试试。我说能啊,而且从服务器到数据库到域名,这一整套基础设施其实都能白嫖——阿里云新人有免费 ECS,Supabase 免费额度够撑个人项目,Cloudflare Tunnel 和 HTTPS 入口也都是免费的。这篇就从头到尾走一遍,配关键截图。
1. 第一步:阿里云 ECS,白嫖 2 个月 2C2G
阿里云对新用户有一个”免费试用中心”,其中最实在的就是云服务器 ECS:2 vCPU、2 GB 内存、40 GB 云盘、3 Mbps 带宽,免费试用 2 个月。
操作路径:
- 打开阿里云免费试用页面(
free.aliyun.com),搜索”云服务器 ECS” - 选香港节点——这个重要,因为 we-events 用 Playwright 抓公众号,网络连通性需要绕开一些限制,香港节点最稳
- 系统镜像选 Ubuntu 22.04,系统盘 40 GB 够用
- 安全组临时放行 22(SSH)、38001(后端 API)、30000(前端) 端口用于调试——Cloudflare Tunnel 跑通后,正式环境建议只保留 22,并限制来源 IP
- 领取后实例几分钟就就绪,记下公网 IP

进服务器第一件事:装 Docker,没有它后面什么也跑不了。
1 | # 用你熟悉的 SSH 客户端连上去 |
装好之后把这台机器当成你的”根据地”——代码、数据库客户端、反向代理全跑在上面。2C2G 对 we-events 绰绰有余,毕竟后端就一个 FastAPI 进程加一个 Playwright 浏览器实例。
2. 第二步:Supabase,白嫖一个 PostgreSQL

we-events 的数据层全在 PostgreSQL 上——公众号列表、文章内容、采集队列、活动记录,还有图片对象存储。Supabase 的免费额度给得很慷慨:
- 500 MB PostgreSQL 数据库
- 1 GB 文件存储(够存几万张文章配图)
- 每月 50 GB 带宽
- 内置 Auth、Row Level Security
2.1. 创建项目
- 注册/登录 Supabase(
supabase.com),GitHub 账号直接进 - Dashboard → New project → 取个名字(比如
we-events) - 数据库密码:自动生成一个,存好(后面要用)
- Region 选靠近用户的区域——香港或新加坡
- 等几分钟初始化完成
2.2. 拿到连接信息
项目建好后,在 Project Settings → API 里找到三个关键值:
- Project URL(
https://<project-ref>.supabase.co) - anon public key
- service_role key(管理级权限,存好别泄露)
这三个值要填到 we-events 的配置里。
2.3. 初始化数据库
项目 supabase/ 目录下有完整的迁移脚本。连到 Supabase 的 SQL Editor,按顺序执行:
supabase/migrations/20241120_initial_schema.sql——建表supabase/migrations/20241120_rls_policies.sql——行级安全策略supabase/config_managements_seed.sql——运行时配置种子数据
也可以用 psql 客户端从服务器直连执行:
1 | # 从 ECS 连 Supabase 的 PostgreSQL |
SQL 跑完后还需要在 Storage 里创建两个存储桶:qr(微信登录二维码)和 article-images(文章配图)。Bucket 权限按项目 README 配:qr 私密、article-images 公开。
3. 第三步:Cloudflare,白嫖 HTTPS 入口 + 反向隧道
这个不是必须的。这里主要是不花钱搞定公网入口:ECS 上仍然用 Docker Compose 跑前端和后端,Cloudflare Tunnel 只负责把前端 nginx 暴露成 HTTPS 域名。

3.1. 前端入口:Docker nginx
we-events 的前端是一个 React + Vite 静态应用,但当前项目没有走 Cloudflare Pages,而是由 frontend/Dockerfile 构建静态资源,再交给 nginx 对外提供服务。这样有一个好处:前端和后端天然同域,浏览器请求 /api/ 时,nginx 会直接转发到 Docker 网络里的后端容器。
关键配置在 frontend/nginx.conf:
1 | server { |
前端代码里 API baseURL 固定为 /api/v1,所以线上不需要再额外配置 VITE_API_BASE。只要用户访问的是前端域名,比如 https://event.phoenine.top,登录请求实际会走:
1 | https://event.phoenine.top/api/v1/wx/auth/token |
这也是我没有把前端单独丢到 Cloudflare Pages 的原因:Pages 上没有这个 nginx 反代,除非额外改前端请求地址或加 Functions/Worker 代理,否则 /api/ 会找不到后端。
3.2. 公网入口:Cloudflare Tunnel
ECS 的公网 IP 暴露端口不安全,而且 3 Mbps 带宽也吃力。Cloudflare Tunnel(cloudflared)可以把你的服务安全地暴露出去,不走公网 IP,还能白嫖 Cloudflare 的 CDN 和 SSL。
在 ECS 上装 cloudflared 并创建隧道:
1 | # 安装 cloudflared |
这样 event.phoenine.top 就指向 ECS 上的前端 nginx:页面访问走 localhost:30000,接口请求再由 nginx 转到 Docker 网络里的 backend:38001。公网只看到一个 HTTPS 域名,后端端口不需要直接暴露给用户。
3.3. 踩坑:被 Google 当成欺诈网站

全部配好之后,访问自定义域名 https://event.phoenine.top/login,Chrome 直接弹了一个大红屏——“Deceptive site ahead”,页面被整页遮蔽,点”详细信息”才能继续访问。这个不是 Cloudflare 本身的错误,也不是证书坏了,而是 Chrome 走 Google Safe Browsing 把这个登录页判成了危险网站。
先按基础链路排了一遍:
1 | curl -I https://event.phoenine.top |
结果很正常:HTTP 能跳到 HTTPS,HTTPS 返回 200,没有异常重定向,也没有跳到奇怪的第三方域名。继续把首页引用的 JS 文件拉下来搜了一遍。注意文件名里的 hash 每次构建都可能变,先从首页 HTML 里找到实际的 /assets/index-xxx.js:
1 | ASSET=$(curl -L https://event.phoenine.top | grep -Eo '/assets/index-[^"]+\.js' | head -n 1) |
这一步主要确认三件事:有没有陌生外链、有没有 iframe / eval 这种高风险脚本、登录接口有没有跨域跳转。结果也没发现恶意注入,登录接口就是正常的:
1 | POST /api/v1/wx/auth/token |
最后去 Google Search Console 看 Security issues,命中的 URL 很明确,就是:
1 | https://event.phoenine.top/login |
类型是 deceptive content / social engineering。也就是说,Google 不是说整套 Cloudflare 部署有问题,而是这个登录页的特征像钓鱼页。
我判断触发原因主要是这几个因素叠在一起:
- 页面路径是
/login - 页面里有用户名、密码、登录按钮
- 项目和文案里出现了”公众号”、”扫码登录”、”当前环境异常”、”完成验证后即可继续访问”这类词
- 域名部署时间短,站点信誉还没积累起来
we-events 当然不是钓鱼站,但机器审核不会理解业务上下文。它看到”登录表单 + 微信/公众号/验证相关词 + 新站点”,就很容易往欺诈网站上靠。
处理上我做了几件事:
- 把登录页文案改得更中性,少出现”微信”、”公众号”、”扫码登录”这类容易误伤的词
- 确认页面 title、logo、favicon 没有仿冒微信公众平台的表达
- 给站点加
robots.txt,当前项目里是Disallow: /,避免搜索引擎收录后台入口 - 补安全响应头,比如
X-Content-Type-Options、X-Frame-Options、Referrer-Policy,有条件的话再加 HSTS - 如果后台只是自己用,最好再加 IP 白名单或 Cloudflare Access,不要把
/login裸露在公网
Search Console 复审流程如下:
打开
search.google.com/search-console添加资源,推荐直接用 Domain 资源:
phoenine.top,这样event.phoenine.top也会覆盖进去Google 会给一条 TXT 记录,在 Cloudflare DNS 里添加:
1
2
3Type: TXT
Name: @
Content: google-site-verification=xxxxxx回 Search Console 点 Verify
进入 Security & Manual Actions → Security issues
如果看到
/login被标记,先确认上面的文案和配置已经改完,再点 Request Review
复审说明可以这么写:
1 | This website is an internal management system for our own operations. |
我的结论是:这次不是 Cloudflare Tunnel、DNS 或 HTTPS 配置错误,而是 Google Safe Browsing 对登录页语义的误判。自定义域名也不能完全避免这个问题,只是老域名会比全新的免费子域名或新子域名更不容易被误伤。后台类项目上线后,最好第一时间把域名加到 Search Console,并且不要让登录页看起来像某个平台的仿冒入口。
4. 第四步:部署 we-events
服务器和数据库都有了,开始部署应用本身。
4.1. 拉代码
1 | cd /opt |
4.2. 配环境变量
从模板创建线上 Supabase 的配置:
1 | cp .env.online.example .env.online |
编辑 docker-compose 使用的环境文件,把前面从 Supabase 拿到的三个值填进去,SSH 登录密码也设好:
1 | USERNAME=admin@your-domain.com |
4.3. 启动
1 | docker compose --env-file .env.online up --build -d |
等几十秒,两个容器跑起来之后:
1 | 后端:http://localhost:38001 |
通过 Cloudflare Tunnel 的话,访问 https://event.phoenine.top 就能看到登录页了。
4.4. 验证
- 浏览器打开前端地址,用上面配置的
USERNAME/PASSWORD登录 - 进”公众号管理”添加几个想跟踪的公众号
- 在”系统”页扫微信登录二维码,建立抓取会话
- 触发采集,等文章入库
测试账号不用我给了——你自己的部署,管理员就是你自己。
5. 给开发者的信息
如果你对技术细节感兴趣,这里列几个部署时值得注意的点:
- Playwright 浏览器:后端 Docker 镜像里内置了 Chromium(
playwright install chromium),首次启动可能多耗十几秒。镜像约 1.2 GB,ECS 的 40 GB 云盘够用 - 采集串行:因为微信 MP 登录态是全局唯一的,文章采集必须串行执行。后端限定了
workers=1,数据库层面还有 advisory lock 兜底 - 存储桶:
article-imagesbucket 可以设成 public,前端展示文章时不需要后端签名 URL,减少请求量。qrbucket 保持 private,后端返回 signed URL - 免费额度余量:Supabase 免费配额的 500 MB 数据库对个人使用绰绰有余——一张文章记录也就几 KB,几万篇文章才几十 MB。存储桶主要是图片占空间,公众号配图一般 100–300 KB/张,1 GB 能存几千张
- 不存在完美的免费方案:ECS 只免 2 个月,到期后按量付费一个月大概 50 块出头。到时要么升级续费,要么迁移到其他便宜 VPS,或者本地起docker也行。Supabase 免费额度用完需要等月度重置或付费。
这篇文章写的每一步我自己都跑通了一遍——从阿里云抢免费 ECS、到 Supabase 建库跑 SQL、到 Cloudflare Tunnel 配置。中间踩的坑都注明了。如果你也想给某件事搭一个小工具,这些羊毛值得薅一薅。项目代码在 github.com/phoenine/we-events。
