Epic Stack 部署指南:基于 Fly.io 与 LiteFS 的生产级全栈部署实战
【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stack
本文以 Epic Stack 官方部署文档 docs/deployment.md 为骨架,系统讲解如何将这一全栈应用模板部署到 Fly.io——从安装 flyctl、创建 staging/production 双环境、配置密钥与 Consul、挂载 SQLite 持久卷、接入 Tigris 对象存储,到利用内置 GitHub Actions 实现 push-to-deploy,并补充使用 Docker/Podman 本地部署的完整方案。读完本文,你将掌握 Epic Stack 从本地开发到生产环境的完整部署链路,并理解 LiteFS 单主复制、Prisma 迁移与零停机部署背后的实现原理。
部署前的整体认知
Epic Stack 是一个"开箱即跑"的全栈应用模板,首次创建仓库时会引导你回答一系列问题来完成应用配置与部署。但官方在 docs/deployment.md 中把完整的部署步骤固化成了文档,既方便排查问题,也支持日后手动重建。整个部署流程围绕以下几个核心资产展开:
- fly.toml:Fly.io 应用声明文件,定义了应用名、主区域、构建方式、服务端口与健康检查;
- other/Dockerfile:生产镜像构建脚本,最终以
litefs mount作为容器入口; - other/litefs.yml:LiteFS 配置,负责 SQLite 的 FUSE 挂载、Consul 租约与启动时的 exec 命令链;
- .github/workflows/deploy.yml:内置 CI/CD,推送
main自动部署生产、推送dev自动部署 staging。
部署模型是"一台主实例(primary)可写、多台副本(replica)只读"的 LiteFS 单主复制架构,这与 Fly 托管的 Postgres 服务模式一致,详情可参考 docs/database.md。因此 Epic Stack 的部署本质上是在搭建一个多实例、零停机、可迁移 SQLite 的生产运行环境。
首次部署到 Fly.io:9 步完整流程
1. 安装并登录 Fly CLI
首先安装 Fly CLI(官方文档中若fly命令不可用,可尝试flyctl),然后注册并登录:
fly auth signup注意:如果你有多个 Fly 账号,务必确保终端里登录的账号与浏览器中登录的账号一致。可在终端执行
fly auth whoami,核对邮箱是否与浏览器中登录的 Fly 账号匹配。
2. 创建 staging 与 production 两个应用
Epic Stack 采用"双环境"策略,为生产与预发布各建一个应用:
fly apps create [YOUR_APP_NAME] fly apps create [YOUR_APP_NAME]-staging注意:应用名必须与 fly.toml 中的
app字段保持一致,否则无法部署。仓库默认值是app = "epic-stack-template",你需要替换为自己的应用名。
3. 初始化 Git 并关联远程仓库
git init git remote add origin <ORIGIN_URL>此时不要推送代码,因为后续还需要配置密钥与资源。
4. 配置密钥(Secrets)
Epic Stack 运行期依赖的敏感变量通过 Fly secrets 与 GitHub repo secrets 两级注入,其在 app/utils/env.server.ts 中被 Zod schema 强制校验,缺少任一必填项都会在启动时报Invalid environment variables并中止。
GitHub 侧:FLY_API_TOKEN
进入 Fly 用户设置创建一个 Personal Access Token,然后以FLY_API_TOKEN为名添加到 GitHub 仓库的 Encrypted Secrets。它是 .github/workflows/deploy.yml 中container与deploy两个 job 调用flyctl deploy的认证凭证(env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }})。
Fly 侧:SESSION_SECRET与HONEYPOT_SECRET
这两个密钥分别用于会话加密与蜜罐表单防护,需同时写入生产与 staging:
fly secrets set SESSION_SECRET=$(openssl rand -hex 32) HONEYPOT_SECRET=$(openssl rand -hex 32) --app [YOUR_APP_NAME] fly secrets set SESSION_SECRET=$(openssl rand -hex 32) HONEYPOT_SECRET=$(openssl rand -hex 32) --app [YOUR_APP_NAME]-staging注意:若未安装 openssl,可用 1Password 等密码生成器生成随机串,替换
$(openssl rand -hex 32)即可。
Fly 侧:ALLOW_INDEXING=false(仅 staging)
为防止搜索引擎索引到重复内容,为 staging 设置ALLOW_INDEXING=false:
fly secrets set ALLOW_INDEXING=false --app [YOUR_APP_NAME]-staging这一变量的作用可以从源码得到印证:在 app/root.tsx 中,const allowIndexing = ENV.ALLOW_INDEXING !== 'false',当其为false时,页面<head>会输出<meta name="robots" content="noindex, nofollow" />,从而阻止爬虫收录 staging 站点。
5. 创建 SQLite 持久化卷
SQLite 数据库需要持久卷支撑,staging 与 production 各建一个(可按需调整大小与区域,若更改区域须同步修改 fly.toml 中的primary_region):
fly volumes create data --region sjc --size 1 --app [YOUR_APP_NAME] fly volumes create data --region sjc --size 1 --app [YOUR_APP_NAME]-staging该卷名data与 fly.toml 的[mounts]配置对应:source = "data"、destination = "/data"。运行时数据库的真实落盘位置在/data/litefs/dbs/sqlite.db,而应用通过 LiteFS 的 FUSE 挂载点/litefs/data/sqlite.db访问(见 docs/database.md)。
6. 挂载 Consul
Consul 是 Fly 托管的服务,负责在 LiteFS 集群中选举"主实例"(primary):
fly consul attach --app [YOUR_APP_NAME] fly consul attach --app [YOUR_APP_NAME]-staging租约机制在 other/litefs.yml 中配置:lease.type = 'consul',且candidate: ${FLY_REGION == PRIMARY_REGION}——这意味着只有处于主区域的实例才有资格成为主节点。PRIMARY_REGION即 fly.toml 的primary_region(默认sjc)。因此官方建议选择离大多数用户最近的区域作为主区域,并在该区域部署至少两个实例,以实现零停机部署。
7. 创建 Tigris 对象存储
fly storage create --app [YOUR_APP_NAME] fly storage create --app [YOUR_APP_NAME]-staging该命令会为两个环境各创建一个 Tigris 对象存储桶,用于存放上传文件等对象,并自动写入AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_REGION、AWS_ENDPOINT_URL_S3、BUCKET_NAME等环境变量(这些均被 app/utils/env.server.ts 列为必填项)。本地开发时对象存储会被完全 mock 掉,无需额外处理。
8. 提交并推送,触发自动部署
Epic Stack 内置了完整的 GitHub Actions 工作流 .github/workflows/deploy.yml:
- 每次 push 到
main分支 → 自动部署production; - 每次 push 到
dev分支 → 自动部署staging; - 工作流由
lint、typecheck、vitest、playwright、container五个前置 job 把关,全部通过后才执行最终deployjob; containerjob 使用flyctl deploy --build-only --push预构建镜像并打上 commit sha 标签(--image-label ${{ github.sha }}),deployjob 再以flyctl deploy --image "registry.fly.io/<app>:<sha>"拉取该镜像完成发布,同时通过--build-arg COMMIT_SHA=${{ github.sha }}把 commit sha 传入构建过程,用于 Sentry release 命名等场景。
提交命令:
git add . git commit -m "initial deploy" git push可选:邮件、错误监控与数据库运维的对接
部署主链路之外,文档还给出三个可选的后续步骤,入口均位于 docs 目录:
- 邮件服务:为 Resend 设置
RESEND_API_KEY并配置自定义发信域名,见 docs/email.md; - 错误监控:为 Sentry 配置
SENTRY_DSN、SENTRY_AUTH_TOKEN、SENTRY_ORG、SENTRY_PROJECT等,见 docs/monitoring.md。其中SENTRY_AUTH_TOKEN通过构建阶段 secret 注入,other/Dockerfile 中RUN --mount=type=secret,id=SENTRY_AUTH_TOKEN即为其在 Vite 构建时可用而设计; - 连接生产数据库 / 生产环境种子数据:详见 docs/database.md,包括
fly ssh console直连、Prisma Studio 端口代理、litefs export/import备份恢复,以及"先加宽后收窄"(widen then narrow)的零停机 schema 迁移策略。
使用 fly 本地部署
如果只是想在本机验证部署产物,直接运行:
fly deploy该命令会在本机构建镜像并部署到当前 Fly 应用。Fly 平台本身也支持fly launch/fly deploy的本地预览模式,可直接把应用跑在本地模拟环境中。
使用 Docker / Podman 本地部署
不依赖 Fly 的纯本地容器化运行也完全可行,核心思路是:用 Docker 卷替换 Fly 的 LiteFS 挂载,用 Docker ENTRYPOINT 接管容器启动时的初始化命令。
第一步:裁剪 Dockerfile
打开 other/Dockerfile,从# prepare for litefs那一行开始(即COPY --from=flyio/litefs:0.5.11 ...)删到文件末尾,替换为:
# prepare for litefs VOLUME /litefs ADD . . EXPOSE ${PORT} ENTRYPOINT ["/myapp/other/docker-entry-point.sh"]这一步做了两件事:
- Docker volume:用
/litefs卷顶替 Fly 上的 LiteFS 挂载点(原 Dockerfile 中ENV LITEFS_DIR="/litefs/data"、DATABASE_PATH="$LITEFS_DIR/sqlite.db"等路径依赖依然生效); - Docker ENTRYPOINT:容器启动时执行自定义初始化脚本。
第二步:创建入口脚本
在other/docker-entry-point.sh创建以下内容:
#!/bin/sh -ex npx prisma migrate deploy sqlite3 /litefs/data/sqlite.db "PRAGMA journal_mode = WAL;" sqlite3 /litefs/data/cache.db "PRAGMA journal_mode = WAL;" npm run start该脚本依次完成:应用 Prisma 迁移 → 将主数据库与缓存数据库设为 WAL 日志模式(降低并发死锁)→ 启动 Node 应用(监听 8081 端口)。这与 other/litefs.yml 中exec段在 Fly 上执行的命令序列(npx prisma migrate deploy→ 两段PRAGMA journal_mode = WAL→npm start)完全对应,只是本地版不再依赖if-candidate主实例判定,因为本地单容器即单实例。
第三步:构建与运行
# 构建镜像(--build-arg 传入短 commit sha) docker build -t epic-stack . -f other/Dockerfile --build-arg COMMIT_SHA=`git rev-parse --short HEAD` # 创建本地 SQLite 数据库挂载点 mkdir ~/litefs # 运行容器(注意 FLY=false,否则应用会尝试走 Fly 运行时逻辑) docker run -d -p 8081:8081 -e SESSION_SECRET='somesecret' -e HONEYPOT_SECRET='somesecret' -e FLY='false' -v ~/litefs:/litefs epic-stack运行后:
- 访问
http://localhost:8081即可看到应用实例; ~/litefs目录下即是 SQLite 数据库文件,可随时用sqlite3检查;- 容器内
INTERNAL_PORT=8080、PORT=8081(见 other/Dockerfile),因此-p 8081:8081将宿主机 8081 映射到容器 8081。
若使用 Podman,把上述命令中的
docker替换为podman即可,其余参数完全一致。
部署相关的关键源码细节
为了在真实项目中排查部署问题,以下源码路径值得关注:
- fly.toml:
internal_port = 8080对应 LiteFS 代理端口;[[services.http_checks]]中的/resources/healthcheck与/litefs/health是 Fly 的 HTTP 健康检查路径;[build]指定了dockerfile = "/other/Dockerfile"与ignorefile = "/other/Dockerfile.dockerignore"; - other/Dockerfile.dockerignore:构建时排除
/node_modules、.env、/build等,避免把本地依赖与敏感文件打进镜像; - other/litefs.yml:
proxy.addr = ':{INTERNAL_PORT}'与proxy.target = 'localhost:{PORT}'实现 LiteFS 到应用端口的内网转发;exec段是 Fly 上容器启动时的迁移与启动命令链; - app/utils/litefs.server.ts:封装了
litefs-js的服务端 API(ensurePrimary、getInstanceInfo、getAllInstances等),供路由层实现"仅在主实例处理写请求"等一致性逻辑; - app/utils/env.server.ts:运行期环境变量的 Zod 校验 schema,部署时缺失任何必填项都会导致应用拒绝启动,是排查"部署成功但容器反复崩溃"的首选检查点。
结语
Epic Stack 的部署体系把 Fly.io 的托管能力(卷、Consul、Tigris、Secrets)与 LiteFS 的单主复制模型组合成一个可复制的"生产就绪"模板:main/dev双分支触发双环境自动部署,LiteFS 保证 SQLite 多实例读扩展与零停机迁移,Docker 方案则让本地即可 1:1 复现生产运行方式。理解这套链路后,无论你是跟随模板自动初始化,还是日后手动重建部署,都能在 Fly.io 上稳定运行自己的 Epic Stack 应用。
【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stack
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考