这篇接着上一篇继续记录。我正在用黑马程序员那套 DeepSeek + Cursor + Devbox + Sealos 零代码实战课的思路,从零搭一个 AI 问答助手应用。前面几篇大部分时间都花在怎么用 Cursor 快速出页面、怎么把 DeepSeek 的接口接回来,说实话这些环节其实都挺顺。真正让我在深夜挠头的是到了发版上线这一节,对应项目标题里的“项目开发——发版上线(Devbox)”,它要求你处理三个看似不起眼的东西:entrypoint.sh 文档、nginx 代理、允许源。
这篇不是课程照搬,是我自己按同样技术组合跑了一遍之后的全过程复盘。我希望用一篇能直接照做的实战笔记,把发版上线这一段拆开讲清楚。适合什么样的人看?你用 DeepSeek API 或者 Cursor 做出了一个能聊天、能查询的小应用,但不太清楚怎么才能把它放到云上让别人访问;你已经听说过 Devbox、Sealos 这类工具,但看到入口脚本、反向代理、跨域配置就有点犯怵。那这篇文章大概能帮你省掉一晚上排查时间。整篇会从零代码项目的角度出发,但你不需要提前是专业运维,只要能打开终端、能看懂最基础的命令就行。
1. 发版上线前,先把入口脚本、代理、允许源的职责理清
1.1 我们要上线的到底是一个什么东西
先说清楚我们上线的应用长什么样,因为我发现不少人卡住,不是因为某一个配置写错,而是根本没想明白“发版上线”到底是在上线一堆什么文件。
我做的这个 AI 问答助手很简单:前端是一个静态页面,用户输入问题,点击发送;前端把问题交给后端接口;后端拿到问题后,再携带我自己的 DeepSeek API Key 去调用大模型接口,最后把回答返回给前端。逻辑不复杂,但上线这件事就比本地跑复杂一档。因为本地跑的时候,前端可能由 Cursor 内置的预览开一个服务,后端又开着另一个端口,浏览器并不会真正模拟用户从公网访问的情况。
到了发版上线,你要处理的是一整个“可运行的系统”:前端静态文件必须有一个 Web 服务器让它能被读出来,比如 index.html 和 JS、CSS 资源;后端 Python 服务必须监听某个端口,比如 8000;Nginx 需要作为统一入口,把流量按路径分给前端文件或后端程序;如果前端静态页面和后端 API 分别被分配了不同的公网域名,还要处理浏览器跨域,也就是允许源配置。
这就解释了一个现象:为什么标题明明写着“零代码实战”,最后一步却冒出来 entrypoint.sh 和 nginx 配置。零代码不等于零部署,业务代码可以让 Cursor 和 DeepSeek 帮你生成,但这些“让系统真正跑起来”的胶水层,仍然需要用脚本和配置文件串起来。了解这一点,后面就不会慌。
1.2 为什么选 Devbox + Sealos 作为上线载体
如果换成传统方式,我大概需要自己买一台云服务器,装 Node、装 Python、装 Nginx,再手写 systemd 服务,甚至还要处理不同 Linux 发行版之间的差异。对一个用 AI 工具做产品的人来说,这套过程实在太重了。而 Devbox 和 Sealos 的组合,正好能把“环境一致性”和“云上运行”这两件事简化。
Devbox 在这里扮演的是“开发环境定义器”。你不需要自己折腾系统级软件包,而是用一份 devbox.json 把项目需要的 Python 版本、Nginx 版本、启动脚本清单全部声明出来。这份文件放在项目里之后,本地开发环境、云上运行环境都会尽量保持一致。哪怕换一台新电脑,只要拿到项目仓库,执行一下 Devbox 的命令,就能把工具链拉起来,这对不太熟悉系统级依赖的人来说非常友好。
Sealos 解决的是“云上怎么跑”的问题。你可以在 Sealos 上创建应用或开发环境,把项目拉进去,填好环境变量、暴露端口,平台本身会负责容器调度和访问入口。我当时的感受是:它把一个传统服务器运维的流程,变成了偏向“点按钮 + 看日志”的体验。当然,这不是说完全不用懂原理,尤其是 Nginx 和跨域这一层,平台没法替你抽象掉,因为每个人的前端域名、后端路径都可能不一样。
1.3 三个关键点分别解决什么问题
我第一次看到“entrypoint.sh 文档、nginx 代理、允许源”这三个词混在一起时,第一反应是它们为什么需要一起出现?后来跑通一遍才明白,它们是发起一次线上请求时链条上的三个环节。
entrypoint.sh:容器或应用进程启动时的“总导演”。它会声明当前工作目录、先检查 Nginx 配置是否正确、启动后端 Python 服务、再拉起 Nginx。如果入口脚本乱了,服务就起不来,后面的代理和跨域配置全都没机会生效。- Nginx 代理:外部每一个请求都需要一个统一的“门卫”来分流。静态页面请求直接给前端文件,
/api/请求转给后端的 8000 端口。没有 Nginx,你就得让用户在地址栏里输入带端口的地址,或在前端代码里写死后端地址,导致环境污染和跨域。 - 允许源:解决浏览器安全问题。浏览器会检查“这个页面发起请求时,目标接口是否允许来自当前页面域名的访问”。如果后端接口的允许源列表里没有你自己前端的域名,请求会被浏览器直接从网络层面拦截。
这三件事是递进关系,不能只配其中一两个。开发时你可以在本地用浏览器插件关闭跨域限制,看起来一切都好,但一上线,各种问题就会同时出现。理解了它们的职责后,接下来的配置才会顺理成章。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. entrypoint.sh 这样写,发版重启时才不会翻车
2.1 从零代码项目角度理解入口脚本的作用
很多人一上来就被“入口脚本”这个名字吓住,觉得这是专业运维才要碰的东西。其实你可以把它当成一份“开机自启动清单”。以前装 Windows 软件时,有一种机制是开机后运行某个程序,那个程序会帮你把需要的多个小服务都拉起来。entrypoint.sh 在云端发版场景里起的就是这个作用。
具体到我们这个项目,后端是个 FastAPI 应用,需要通过命令 uvicorn 启动,前端是静态文件,Nginx 需要后台运行。如果没有一份入口脚本,部署平台只会帮你执行一条固定的启动命令,它不知道后端要不要先启动、Nginx 怎么起、环境变量怎么读取。所以你需要用 shell 脚本把顺序写清楚,部署平台只要执行这份脚本,就能跑到最终状态。
有的零代码项目甚至会把这个脚本的职责再简化成三行:echo 日志、启动 Nginx、启动后端。但真正上线之后你会发现,如果脚本缺少必要的健壮性处理,比如没有设置进程退出机制、没有做配置语法检查,一旦后端代码有改动重新发版,就可能出现容器一直重启、但日志里看不到任何有效错误的情况。我刚开始因为这事反复折腾了很久,最后才意识到是入口脚本本身有问题,不是业务代码的问题。
2.2 先写出一个能用的 entrypoint.sh 示例
下面这份脚本是我在项目里用的简化版,适合作为同一容器内同时跑 Nginx 和 FastAPI 的场景。如果你的项目后端不是 FastAPI,改了启动命令就行,整体结构可以复用。
bash复制#!/usr/bin/env bash
set -Eeuo pipefail
# 保证脚本在工作目录内执行
cd "$(dirname "$0")"
echo "[entrypoint] project dir: $(pwd)"
# 创建运行 Nginx 时可能需要的目录
mkdir -p /var/log/ai-demo /run/nginx
# 检查 Nginx 配置语法,配置错误就直接退出
echo "[entrypoint] check nginx config..."
nginx -t
# 以后台方式启动 FastAPI
echo "[entrypoint] starting backend on 0.0.0.0:8000..."
uvicorn backend.main:app --host 0.0.0.0 --port 8000 &
BACKEND_PID=$!
# 以前台方式启动 Nginx,daemon off 避免它在容器里变成后台孤儿进程
echo "[entrypoint] starting nginx..."
nginx -g 'daemon off;' &
NGINX_PID=$!
# 容器收到停止信号时,把两个子进程一起停掉
trap 'echo "[entrypoint] stopping..."; kill -TERM $BACKEND_PID $NGINX_PID 2>/dev/null || true' TERM INT
# 等待两个服务退出
wait $BACKEND_PID $NGINX_PID
这里有几个地方非常关键。
第一,脚本第一行用了 #!/usr/bin/env bash,没有用默认的 #!/bin/sh。因为后面需要 set -Eeuo pipefail 这类更严格的 Bash 语法,用 sh 执行可能在部分系统上报错。第二,脚本里第一个动作是切到脚本所在目录,这能避免部署平台从根目录或者其他路径执行时找不到 backend.main。第三,启动 Nginx 时用了 daemon off;,这是一个很常见的容器踩坑点,如果少了这个参数,Nginx 会把自己放到后台,入口脚本会误以为 Nginx 已经退出,导致容器逻辑混乱。
2.3 上线级脚本必须考虑的四个细节
第一个细节是“配置检查要先于服务启动”。上面脚本里先执行了 nginx -t,这是 Nginx 自带的配置语法检查命令。如果配置有问题,脚本会立刻报错,不会带着一个坏的代理配置启动。这个动作能帮你把配置错误暴露
