Skip to content

feat(deploy): 让 main 上的后端可直接部署并被浏览器访问 - #115

Open
johnnyzhang-eng wants to merge 1 commit into
1024XEngineer:mainfrom
johnnyzhang-eng:feat/deployable-backend
Open

feat(deploy): 让 main 上的后端可直接部署并被浏览器访问#115
johnnyzhang-eng wants to merge 1 commit into
1024XEngineer:mainfrom
johnnyzhang-eng:feat/deployable-backend

Conversation

@johnnyzhang-eng

Copy link
Copy Markdown

变更内容

三个文件,让 main 上的后端可以一条命令起起来并被前端连上:

  • 新增 backend/Dockerfile
  • 新增 docker-compose.yml
  • bootstrap/app.py 挂 CORS 中间件

不依赖任何未合入的分支,直接基于当前 main

为什么单独提这个 PR

main 上目前没有 Dockerfile 也没有 docker-compose.yml,后端无法部署;而 CORS 缺失会让浏览器把前端所有请求拦在预检那一步。这三样是「服务能起来、前端能连上」的最小集合,与后端业务功能正交,因此单独提,不必等更大的后端 PR。

三条不是照搬模板的实现细节

都是在一台真实云主机上部署时踩出来的。

1. builder 与 runtime 必须同路径。

uv 装出来的 venv 里,可执行脚本的 shebang 与 workspace 包的 .pth 都是绝对路径。builder 在 /build、runtime 在 /app 时,容器起来会反复重启并报:

exec /app/.venv/bin/uvicorn: no such file or directory

这条最不直观 —— 那个文件其实存在,报的是它 shebang 里指向的 /build/.venv/bin/python;同时 workspace 包也 import 不到。两边都用 /app 即可,不需要任何 relocate 技巧。

2. compose 网络要显式设 MTU。

云主机链路 MTU 常小于 1500(实测某部署机 eno1 为 1480)。而 compose 自建网络不继承 daemon.json 里的 mtu 设置,默认仍是 1500 → 大包被丢,表现为 TLS 握手超时(对象存储上传挂死、pip 下载卡死)而不是明确报错。

这条是分两次才定位到的:先改了 daemon 以为好了,重建后容器里仍是 1500。所以必须写在 compose 的 networks.driver_opts 里。

3. 国内网络要换源并拉长超时。

实测宿主机访问 pypi.org 需 8s,构建容器内默认超时会在下载大包(uvloop)时 operation timed out 直接失败。

另外 POSTGRES_PASSWORD${VAR:?} 强制显式提供,不给默认值 —— 避免默认弱口令跟着编排一起进生产。

验证

本地(基于本 PR 的分支):

结果
uv run ruff check . All checks passed
uv run pytest -q 1 passed
TestClient 验 CORS OPTIONS 预检 200,access-control-allow-origin 正确

在真实服务器上,用本 PR 的代码树完整验过一遍(验证用的容器与镜像已清除):

docker build ./backend            → 成功
docker run -p 8099:8000           → Up
GET /docs                         → HTTP 200
OPTIONS /media/upload (跨域预检)   → HTTP 200,allow-origin 正确

待对齐

  • CORS 生产来源:默认值只覆盖本地 dev(5173 / 3000)并放行 Vercel 预览域名。正式域名定了之后用 WINDUP_CORS_ORIGINS 追加,不需要改代码。
  • 本 PR 只保证「服务起得来、浏览器连得上」。业务端点的实现在其它 PR。

让 main 上的后端可以直接部署起来并被浏览器访问。三处内容都是在一台真实云主机上
部署时踩出来的,不是照搬模板:

1. backend/Dockerfile —— builder 与 runtime **必须同路径**。uv 装出来的 venv 里,
   可执行脚本 shebang 与 workspace 包 .pth 都是绝对路径;builder 在 /build、
   runtime 在 /app 时,容器起来会报 "exec /app/.venv/bin/uvicorn: no such file or
   directory" —— 报的不是脚本本身而是它 shebang 指向的解释器,同时 workspace 包
   import 不到。两边都用 /app 即可,不需要任何 relocate 技巧。
   另加国内镜像源与 UV_HTTP_TIMEOUT=180:实测宿主机访问 pypi.org 需 8s,
   构建容器内默认超时会在下载 uvloop 时直接失败。

2. docker-compose.yml —— 网络显式设 mtu 1450。云主机链路 MTU 常小于 1500
   (实测某部署机 eno1 为 1480),而 compose 自建网络**不继承** daemon.json 的
   mtu 设置,默认仍是 1500,大包被丢,表现为 TLS 握手超时(对象存储上传挂死、
   pip 下载卡死)而不是明确报错。这条是分两次才定位到的:先改 daemon 以为好了,
   重建后容器里仍是 1500。
   POSTGRES_PASSWORD 用 ${VAR:?} 强制显式提供,不给默认值。

3. CORS 中间件 —— 不挂的话浏览器会把前端**所有**请求拦在预检那一步
   (OPTIONS 返回 405、响应无 access-control-* 头),且后端日志里连请求都看不到,
   很容易被误判成前端问题。来源用 WINDUP_CORS_ORIGINS 覆盖,默认覆盖本地 dev
   并放行 Vercel 预览域名。

本地验证:ruff 通过;pytest 1 passed;TestClient 实测 OPTIONS 预检返回 200 且
allow-origin 正确。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
windup Ignored Ignored Preview Aug 4, 2026 4:41pm

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the deployment and CORS changes. I found two concrete issues, both left inline.

Verification: inspected the fixed PR diff 023bf0f86e8089681ae9a09427a12306b3c0b3dc...8c15ea634ba70c9dd2faf8327e891118518de0f9; git diff --check passed.

View job run

Comment thread backend/Dockerfile
RUN uv sync --frozen --no-dev --no-install-workspace

COPY packages/ packages/
RUN uv sync --frozen --no-dev

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image installs only the locked production dependencies here, but the upload path imports qiniu lazily in windup_app.server.media.service, and neither backend/uv.lock nor any backend pyproject.toml declares that package. The container can start, but /media/upload will fail at request time with ModuleNotFoundError: qiniu. Please add the SDK to the appropriate package and update the lockfile so the deployable image contains the runtime dependency.

app.add_middleware(
CORSMiddleware,
allow_origins=_cors_origins(),
allow_origin_regex=r"https://.*\.vercel\.app",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regex still allows every https://*.vercel.app origin even when WINDUP_CORS_ORIGINS is set to a specific production frontend, and allow_credentials=True is enabled below. Any unrelated Vercel app can therefore make credentialed browser requests to this backend if a user has backend credentials. Restrict this to known project/preview domains, or make the regex configurable together with the explicit origin list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant