Reflex 生产部署实战:基于 Caddy 单端口静态导出与多阶段构建(production-one-port)
【免费下载链接】reflex🕸️ Web apps in pure Python 🐍项目地址: https://gitcode.com/GitHub_Trending/re/reflex
本指南围绕仓库 docker-example/production-one-port 示例展开,讲解如何将 Reflex 应用以生产模式打包进单个容器,通过一个 HTTP 端口对外提供服务:Caddy 负责静态托管前端导出产物并把后端 API 请求反代给 Reflex 后端,容器内同时运行本地 Redis 保存用户状态。读完本文,你将掌握该 Dockerfile 的每一层构建意图、Caddyfile 的路由规则,以及如何在 Render、Heroku 等单端口应用平台上正确构建、运行和持久化该容器。
单端口部署的整体架构
production-one-port 是一个「单容器、单端口」的生产部署方案。整个容器对外只暴露一个端口:
8080(对应$PORT)——由 Caddy 服务器监听,它一方面把构建期导出的前端静态文件托管出去,另一方面将后端相关请求反代给容器内的 Reflex 后端进程。
此外,容器内部还会启动一个本地 Redis 服务器,用于存储每个用户的会话状态(通过环境变量REFLEX_REDIS_URL=redis://localhost指向本地实例)。前端采用静态导出(static export)而非运行 Vite 开发服务器,因此整体资源占用更低,特别适合内存受限的部署环境。
与 simple-one-port 的区别
从概念上讲,production-one-port 与 simple-one-port 示例非常相似(都使用 Caddy + 单端口 + 本地 Redis),两者的差异体现在镜像构建方式上:
| 特性 | simple-one-port | production-one-port |
|---|---|---|
| Python 依赖层缓存 | 无 | 有(单独 COPY requirements.txt 先安装) |
| Reflex/Node 依赖层缓存 | 无 | 有(先reflex init再预装 bun 依赖) |
| 构建方式 | 单阶段 | 多阶段(builder + slim 最终镜像) |
| 最终镜像体积 | 较大 | 更小(仅保留运行所需文件) |
正因为这两点优化,production-one-port 更适合内存受限、对镜像体积敏感的生产环境。
构建与运行
构建镜像
在包含requirements.txt、rxconfig.py和应用源码的项目根目录执行:
docker build -t reflex-production-one-port .构建时可以通过--build-arg覆盖 Dockerfile 中定义的构建参数:
docker build \ --build-arg PORT=10000 \ --build-arg API_URL=https://myapp.example.com \ -t reflex-production-one-port .PORT:默认8080。如果目标平台有特殊端口要求(例如 Render 期望应用监听10000端口),在此指定。API_URL:默认留空。仅用于本地直连场景;当使用 TLS 时,前端 API 地址被假定与前端地址相同,无需单独设置。
运行容器
docker run -p 8080:8080 reflex-production-one-port注意:该容器没有任何持久化,停止后所有数据都会丢失。如果需要保留数据库与上传文件,请使用 bind mount 或 named volume 挂载对应的database与uploaded_files目录,例如:
docker run -p 8080:8080 \ -v reflex-data:/app/database \ -v reflex-uploads:/app/uploaded_files \ reflex-production-one-port使用场景
- 该容器应当部署在已有的负载均衡器或反向代理之后,由它们负责 TLS 终止;
- 非常适合 Render、Heroku 这类只支持暴露单一端口、且不希望容器内常驻 node 进程的简单应用平台。
Dockerfile 逐层解析:多阶段构建与层缓存
Dockerfile 是整个方案的核心,分为 builder 与最终运行镜像两个阶段。
阶段一:builder(python:3.13)
准备虚拟环境与目录:创建
/app/.web目录,并在/app/.venv中建立 Python 虚拟环境,将其加入PATH。安装 Python 依赖并利用层缓存:先单独
COPY requirements.txt .并执行pip install -r requirements.txt。由于 Docker 的层缓存机制,只要requirements.txt未变化,这一层就会被复用,避免每次构建都重新安装依赖。仓库根目录 README.md 也提醒:务必保证requirements.txt始终包含reflex包并保持最新。初始化 Reflex 并预装前端依赖:
COPY rxconfig.py ./后执行reflex init生成项目骨架;随后把本地已有的*.web/bun.lockb与*.web/package.json复制进容器,并用 Reflex 自带的 bun 执行bun install --frozen-lockfile预装锁定的前端依赖(同样受益于层缓存)。导出静态前端:复制完整项目上下文后,执行:
reflex export --loglevel debug --frontend-only --no-zip该命令只导出前端(
--frontend-only)、不打包成 zip(--no-zip),产物位于.web/build/client,随后被移动到/srv并删除.web目录,以缩小最终镜像。在源码层面,reflex export由 reflex/reflex.py 实现,它会根据REFLEX_API_URL(此处通过REFLEX_API_URL=${API_URL:-http://localhost:$PORT}计算)在前端产物中写入正确的 API 地址。
阶段二:最终镜像(python:3.13-slim)
- 通过 apt 安装
caddy与redis-server两个运行时组件,并清理 apt 缓存; - 设置运行环境变量:
PORT、REFLEX_API_URL(默认http://localhost:$PORT)、REFLEX_REDIS_URL=redis://localhost、PYTHONUNBUFFERED=1; - 仅从 builder 阶段拷贝
/app与/srv两个目录,其余构建期文件一律丢弃; STOPSIGNAL SIGKILL:在 Reflex 后端尚未正确传递 SIGTERM 之前,用 SIGKILL 保证容器能及时停止;EXPOSE $PORT声明对外端口。
容器启动命令(CMD)
[ -d alembic ] && reflex db migrate; \ caddy start && \ redis-server --daemonize yes && \ exec reflex run --env prod --backend-only启动序列依次完成:
- 若存在
alembic目录,先执行reflex db migrate应用数据库迁移(对应源码 reflex/reflex.py 中的migrate命令); - 后台启动 Caddy(
caddy start); - 以守护进程方式启动 Redis(
redis-server --daemonize yes); - 最后以
exec方式运行reflex run --env prod --backend-only,即只启动生产模式后端,不再运行前端服务器——前端由 Caddy 静态托管。
Caddyfile:前端静态托管与后端反向代理
Caddyfile 定义单端口上的全部路由逻辑:
:{$PORT} encode gzip @backend_routes path /_event/* /ping /_upload /_upload/* handle @backend_routes { reverse_proxy localhost:8000 } root * /srv route { try_files {path} {path}/ /404.html file_server }逐行说明:
:{$PORT}:监听$PORT环境变量指定的端口;encode gzip:对响应启用 gzip 压缩;@backend_routes path /_event/* /ping /_upload /_upload/*:定义后端路由匹配器——WebSocket 事件通道/_event/*、健康检查/ping、文件上传/_upload相关路径;handle @backend_routes { reverse_proxy localhost:8000 }:将上述请求反代到容器内的 Reflex 后端(默认端口 8000);root * /srv:静态文件根目录为构建阶段导出的前端产物;route { try_files {path} {path}/ /404.html; file_server }:优先按路径查找静态文件,找不到时回退到/404.html(保证 SPA 路由可用),最后由file_server提供静态文件服务。
Redis 与后端配置要点
容器通过环境变量REFLEX_REDIS_URL=redis://localhost指定后端状态存储。仓库源码 reflex/utils/prerequisites.py 中的parse_redis_url会对该值做校验:必须以redis://、rediss://或unix://开头,否则抛出ValueError。这也意味着在本示例的基础上,你可以轻松将 Redis 指向远程实例(如rediss://加密连接或外部托管服务),而不改变容器内其他结构。
关于REFLEX_API_URL:当容器被部署在负载均衡/边缘网关之后并启用 TLS 时,前端访问后端的地址与前端自身地址一致,因此无需单独配置API_URL;只有当需要本地直连、前后端地址不同时才需要显式传入该构建参数。
生产部署注意事项小结
- 持久化是必须自行解决的问题:容器本身无状态,务必用 bind mount 或 named volume 保存
database(SQLite 数据库)与uploaded_files(用户上传文件); - TLS 由边缘负责:Caddy 在容器内以 HTTP 监听
$PORT,HTTPS 证书与终止应交给前置负载均衡器或平台边缘; - 端口按平台调整:Render 期望
PORT=10000,Heroku 等其他平台可能不同,构建时通过--build-arg PORT=...传入即可,Caddyfile 与EXPOSE都会随之生效; - 前端是静态导出:容器内不运行 node/Vite 进程,内存占用更小,这正是该方案优于两端口方案(参考 simple-two-port)的核心原因,也是它被推荐用于内存受限环境的价值所在。
【免费下载链接】reflex🕸️ Web apps in pure Python 🐍项目地址: https://gitcode.com/GitHub_Trending/re/reflex
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考