Reflex 生产环境部署实战:基于 Docker Compose + Caddy 自动 TLS 的单机部署指南

原创2026-09-09 17:56:511,745 阅读
文章标签:后端前端Web框架

Reflex 生产环境部署实战:基于 Docker Compose + Caddy 自动 TLS 的单机部署指南

本篇指南以 Reflex 官方仓库中的 production-compose 示例 为核心,完整讲解如何在一台独立 VPS 上,用 Docker Compose 编排 Caddy 反向代理与 Reflex 后端服务,实现「静态前端由 Caddy 直出、后端仅跑 Python 服务、HTTPS 证书自动签发」的生产级单机部署。读完本文,你将掌握该示例的全部构建细节、Caddyfile 路由配置、数据持久化方案,以及如何叠加 Postgres、Redis 与管理工具组成更健壮的部署栈。

一、方案概览:为何选择 production-compose

Reflex 官方在 docker-example 目录下提供了多种 Docker 部署范式,production-compose 是其中面向「独立 VPS、单应用托管」场景的完整栈方案。它与其他方案的本质区别在于:

  • 前端静态化:应用镜像在构建阶段通过 reflex export --frontend-only 将前端导出为静态文件,交由 Caddy 直接托管,运行期不再需要 Node.js;
  • 自动 TLS:Caddy 充当 Web 服务器与反向代理,为 localhost 或环境变量 DOMAIN 指定的域名自动申请并续期 HTTPS 证书;
  • 单栈编排:一个 compose.yaml 即可拉起 Web 服务器与后端,配合覆盖文件可追加 Postgres、Redis 与管理工具。

从架构看,这是「静态资源 + 动态接口分离」的经典模式:浏览器请求静态页面时由 Caddy 直接返回,只有后端 API 类请求(事件处理、上传、健康检查等)才会被代理转发到应用容器。由于后端镜像不包含 Node.js 运行时,这种部署方式占用内存更少、性能更佳——这是原文档明确指出的优势,也是选择该方案的核心动机。

二、镜像构建:两阶段 Dockerfile 拆解

production-compose 的核心构建逻辑位于根目录 Dockerfile,采用标准的多阶段构建,将「构建产物」与「运行环境」彻底分离。

阶段一:init —— 导出静态前端

基础镜像为 python:3.13,首先安装 uv 以加速 Python 依赖引导,然后将项目上下文复制到 /app:

FROM python:3.13 as init

ARG uv=/root/.local/bin/uv
ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
RUN /install.sh && rm /install.sh

WORKDIR /app
COPY . .
RUN mkdir -p /app/data /app/uploaded_files

随后在虚拟环境中安装依赖,并依次执行 Reflex 的初始化与前端导出:

ENV VIRTUAL_ENV=/app/.venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
RUN $uv venv
RUN $uv pip install -r requirements.txt

RUN reflex init
RUN reflex export --frontend-only --no-zip

这里的 reflex export --frontend-only --no-zip 是关键一步:--frontend-only 只导出前端产物(后端不参与静态导出),--no-zip 表示直接输出目录而非压缩包,导出结果位于 /app/.web/build/client。为节省后端镜像体积,阶段末尾将静态文件移出 .web 目录后重建:

RUN mv .web/build/client /tmp/client
RUN rm -rf .web && mkdir -p .web/build
RUN mv /tmp/client .web/build/client

阶段二:slim —— 精简运行镜像

最终镜像基于 python:3.13-slim,只携带虚拟环境与静态产物:

FROM python:3.13-slim
WORKDIR /app
RUN adduser --disabled-password --home /app reflex
COPY --chown=reflex --from=init /app /app
RUN apt-get update -y && apt-get install -y libpq-dev && rm -rf /var/lib/apt/lists/*
USER reflex
ENV PATH="/app/.venv/bin:$PATH" PYTHONUNBUFFERED=1

几个值得注意的实现细节:

  • 以非 root 用户 reflex 运行,符合最小权限原则;
  • libpq-dev 是为 psycopg(Postgres 驱动)准备的编译依赖,注释明确说明「如果不使用 Postgres 可以跳过」;
  • STOPSIGNAL SIGKILL 用于规避 Reflex 后端暂未正确传递 SIGTERM 的问题;
  • 启动命令在容器入口统一处理数据库迁移,再以生产模式仅启动后端:
CMD [ -d alembic ] && reflex db migrate; \
    exec reflex run --env prod --backend-only

--backend-only 意味着该容器不负责提供任何前端页面,只运行后端事件服务,前端完全交给 Caddy。reflex db migrate 仅在项目存在 alembic 目录(即启用了数据库迁移)时才执行,保证每次启动前 schema 与代码一致。

三、Web 服务器:Caddy 的自动 TLS 与反向代理

Caddy 镜像

Caddy.Dockerfile 非常简单——基于官方 caddy 镜像,将构建产物(local/reflex-app 镜像内的静态前端)复制到 /srv,并注入 Caddyfile 配置:

FROM library/caddy

COPY --from=local/reflex-app /app/.web/build/client /srv
ADD Caddyfile /etc/caddy/Caddyfile

注意这里的 --from=local/reflex-app 引用的是 compose 中构建出的本地镜像名(见下文 compose 的 image: local/reflex-app),两阶段镜像间通过该名称衔接。

Caddyfile 路由设计

Caddyfile 是整条请求链路的枢纽,全文如下:

{$DOMAIN}

encode gzip

@backend_routes path /_event/* /ping /_upload /_upload/*
handle @backend_routes {
	reverse_proxy app:8000
}

root * /srv
route {
	try_files {path} {path}/ /404.html
	file_server
}

逐行解读:

  • {$DOMAIN}:站点地址由环境变量注入。Caddy 会根据该值自动申请证书,localhost 则走本地自签证书;
  • encode gzip:对响应启用 gzip 压缩;
  • @backend_routes 定义了一组「必须转发给后端」的路径匹配器,包含 /_event/*(Reflex 事件处理端点)、/ping(健康检查)、/_upload 与 /_upload/*(文件上传端点)。命中后由 reverse_proxy app:8000 转发到 compose 服务名为 app 的容器 8000 端口(Reflex 后端默认端口);
  • root * /srv 指定静态资源根目录;
  • route 块内 try_files 先尝试精确路径、再尝试目录索引,兜底返回 404.html,最后 file_server 提供静态文件服务。

原文档特别提示:如果应用使用了额外的后端 API 路由,需要同步把它们追加到 @backend_routes 路径匹配器中,否则这些请求会被 Caddy 当作静态资源处理而得不到正确的后端响应。这是自定义该方案时最容易被忽略的一步。

四、Compose 编排:基础栈与三种启动方式

基础 compose.yaml

compose.yaml 定义了两个核心服务:

服务 镜像 职责 关键配置
app local/reflex-app Reflex 后端 REFLEX_DB_URL: sqlite:///data/reflex.db,挂载 db-data、upload-data 卷
webserver Caddy TLS 终止 + 静态托管 + 反向代理 暴露 443/80 端口,挂载 caddy-data 卷,depends_on: app

关键点:

  • REFLEX_DB_URL 默认使用 SQLite,数据库文件落在 /app/data(对应命名卷 db-data);
  • DOMAIN: ${DOMAIN:-localhost}:域名由宿主环境变量注入,未设置时回退为 localhost——这正是原文档强调「如果不提供 DOMAIN,服务将默认 localhost」的代码依据;
  • 80 端口保留用于 ACME HTTP 质询(证书签发验证);
  • 三个命名卷分别持久化数据库(db-data)、上传文件(upload-data)与 TLS 证书(caddy-data)。证书卷尤其重要:若不持久化,Caddy 每次重建容器都要重新签发证书,可能触发速率限制。

构建与启动命令

构建时必须传入 DOMAIN(注意不要带 http:// 或 https:// 前缀,Caddy 会强制使用 HTTPS):

DOMAIN=example.com docker compose build

该命令会同时构建 app(默认 Dockerfile)与 webserver(Caddy.Dockerfile)两个服务。随后启动:

DOMAIN=example.com docker compose up

应用即在指定域名以 HTTPS 提供访问。原文档提醒:证书签发是自动进行的,首次可能耗时几分钟,期间请耐心等待或观察 Caddy 日志。

数据持久化清单

整个栈共使用 4 个命名卷,职责划分如下:

卷名 挂载位置 持久化内容
db-data /app/data SQLite 数据库文件
upload-data /app/uploaded_files 用户上传文件
caddy-data /root/.caddy TLS 密钥与证书
postgres-data /var/lib/postgresql/data Postgres 数据(compose.prod.yaml 引入)

五、健壮化部署:叠加 Postgres 与 Redis

原文档指出,若要支撑更大流量、让后端以多 worker 运行,应使用 compose.prod.yaml 覆盖文件,它额外引入了 Postgres 与 Redis:

DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml up -d

覆盖文件的核心改动:

services:
  db:
    image: postgres
    restart: always
    environment:
      POSTGRES_PASSWORD: secret

  redis:
    image: redis
    restart: always

  app:
    environment:
      REFLEX_DB_URL: postgresql+psycopg://postgres:secret@db/postgres
      REFLEX_REDIS_URL: redis://redis:6379
    depends_on:
      - db
      - redis

两个环境变量的语义可以从源码得到印证:

  • REFLEX_DB_URL 对应 Reflex 配置中的 db_url(见 reflex/model.py 中 url or conf.db_url 的取值逻辑),此处切换为 postgresql+psycopg 驱动;
  • REFLEX_REDIS_URL 对应 redis_url 配置,reflex/utils/prerequisites.py 中的 parse_redis_url() 明确校验该值必须以前缀 redis://、rediss:// 或 unix:// 开头——redis://redis:6379 符合规范。Redis 在后端启用多 worker 时承担状态共享与缓存职责。

切换数据库后,Postgres 使用独立的 postgres-data 命名卷持久化数据(原文档特别说明这一点)。注意 POSTGRES_PASSWORD: secret 是示例值,真实生产环境务必替换为强密码,并建议通过 compose 的 .env 文件或密钥管理机制注入。

六、管理工具:按需启用的运维辅助栈

compose.tools.yaml 提供了两个图形化管理工具,需与 prod 覆盖文件叠加使用:

DOMAIN=example.com docker compose -f compose.yaml -f compose.prod.yaml -f compose.tools.yaml up -d
服务 工具 访问地址 用途
adminer Adminer http://localhost:8080 图形化数据库管理
redis-commander Redis Commander http://localhost:8081 Redis 缓存浏览器

redis-commander 通过 REDIS_HOSTS=local:redis:6379 指定要连接的 Redis 实例。原文档明确建议:这些服务仅在需要时临时拉起,不推荐长期部署——它们面向运维排查场景,常驻运行只会增加攻击面与资源占用。

七、自定义要点与部署检查清单

综合原文档与源码实现,部署或改造该方案时应重点检查以下事项:

  1. 追加后端路由:应用新增了自定义 API 端点时,务必同步更新 Caddyfile 中的 @backend_routes 匹配器;
  2. 环境变量一致性:DOMAIN 在构建与启动时都应传入(虽然 Caddyfile 的 {$DOMAIN} 在容器运行期读取,但保持一致的部署习惯可避免混淆);域名不带协议前缀;
  3. 数据库迁移:镜像启动命令会检测 alembic 目录自动执行 reflex db migrate;更换 Postgres 后需确认 psycopg 依赖与 libpq-dev 已就位;
  4. Redis URL 前缀:REFLEX_REDIS_URL 仅接受 redis://、rediss://、unix:// 三种前缀(源码校验),填错将导致启动期解析失败;
  5. 证书续期:caddy-data 卷务必持久化,否则重建容器会触发证书重新签发;
  6. 安全基线:将示例中的 Postgres 密码、管理工具暴露端口替换为符合生产要求的配置,管理工具仅在需要时启用。

八、总结

production-compose 示例为 Reflex 应用提供了一套开箱即用、可渐进增强的单机生产部署方案:两阶段镜像构建保证运行镜像轻量无 Node.js,Caddy 一站式解决静态托管、反向代理与自动 HTTPS,compose 覆盖文件机制让「基础栈 → 高可用栈 → 运维栈」的演进只差一条命令。无论是快速上线一个个人项目,还是作为更大规模部署的参照样板,这份配置都值得直接复用并按上述清单定制。

如需对比其他部署形态(单端口、双端口、平台托管),可继续阅读仓库中的 docker-example 总览 与 production-one-port、simple-two-port 等相邻示例。

登录后查看全文
reflex