关于 spree-deployment
spree-deployment 是面向 Spree 6 生产环境的部署指南,涵盖容器构建、环境配置、数据库和后台任务,并说明如何拆分应用服务。
主要功能
- 01Docker 镜像构建与项目布局
- 02组合式与独立 Worker 部署
- 03环境变量和数据库配置
- 04迁移与升级任务
- 05Solid Queue 和 Sidekiq 配置
- 06仪表板、存储与搜索配置
适用场景
- 01在生产环境部署 Spree 6 商店
- 02将后台 Worker 与 Web 服务分离
- 03规划数据库迁移和版本升级
- 04配置仪表板交付与文件存储
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ spree/ agent-skills --skill spree-deployment安装后在 Claude Code 中输入/spree-deployment确认已加载。
附带文件references/environment-variables.md
详细介绍
部署 Spree
Spree 6 的生产部署只需一个 Docker 镜像和一个数据库。同一个镜像运行商店和管理后台 API,在 /dashboard 提供 React 管理后台,并且默认在进程内处理后台任务。任务(Solid Queue)和缓存(Solid Cache)都存储在数据库中;Redis 为可选项。一切均通过环境变量配置。
项目结构提示:Rails 应用位于 server/(较早的项目位于 backend/),管理后台位于 apps/dashboard/。经典版本(在仓库根目录放置 Spree gems 的纯 Rails 应用)使用相同的环境变量和 Rails 配置,只有路径和 CLI 封装器不同。
拓扑方案
| 方案 | 适用场景 | 方式 |
|---|---|---|
| 组合式(默认) | 大多数商店;一个容器就是整个应用 | Web 容器运行 Puma,并启用 Solid Queue 插件(SOLID_QUEUE_IN_PUMA=true) |
| 拆分工作进程 | 大量导入、大型商品目录、同步众多卖家的市场、多租户 | 使用同一个镜像,第二个服务运行命令 bin/jobs;在 Web 服务上设置 SOLID_QUEUE_IN_PUMA=false |
| 分布式 | 需要独立扩缩或发布不同界面 | API 部署在负载均衡器后;管理后台作为静态文件托管在 CDN 上;商店前端(Next.js)部署在 Vercel/Node 主机上 |
| Sidekiq | 仅当专用 Solid Queue 工作进程提高 JOB_THREADS/JOB_CONCURRENCY 后仍无法满足需求时 | 将 Active Job 适配器切换为基于 Redis/Valkey 的 Sidekiq(见下文) |
从组合式开始;每次升级都是配置变更,而非代码变更。
构建镜像
spree eject # once — materializes server/ incl. its production Dockerfile
spree build --production # → <project>-spree:latest
spree build --production --tag registry.example.com/my-store:v42
# without the CLI, from the project root:
docker build . -f server/Dockerfile -t my-store
以项目根目录作为上下文进行构建:Dockerfile 会检测 server/(Rails)和 apps/dashboard/(自定义管理后台),并将管理后台构建产物打包到镜像中。若没有 apps/dashboard/,则会打包默认管理后台。spree build --production 会根据 .spree-custom-app 标记识别适配项目结构的 Dockerfile(首个版本中为 .spree-custom-dashboard);若两者均不存在,则回退为以 server/ 作为上下文进行构建。因此,如果你的 server/Dockerfile 早于对此项目结构的支持,请从 spree-starter 模板更新它,否则自定义管理后台会被默认管理后台覆盖并打包。
未自定义的商店可直接运行官方多架构镜像 ghcr.io/spree/spree:<version>。
环境变量
严格必需:DATABASE_URL、SECRET_KEY_BASE。实际部署中必需:RAILS_HOST 和三个 Active Record 加密密钥。完整列表:references/environment-variables.md。
| 变量 | 重要性 |
|---|---|
DATABASE_URL | postgres://…、mysql2://… 或 sqlite3:db/production.sqlite3 |
SECRET_KEY_BASE | openssl rand -hex 64。保持稳定——会话、Cookie、JWT 回退机制和 secret-API-key 摘要都依赖它 |
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY、ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY、ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT | 用于静态加密 Webhook 签名密钥、网关客户 ID 和 OAuth 身份令牌。未设置 → 以明文存储,并在启动时发出警告。使用 spree encryption init --print 或 bin/rails db:encryption:init 生成;每个环境使用一套单独的密钥,并做好备份;数据加密后绝不要更改这些密钥。工作进程必须使用与 Web 进程相同的值。参见 spree-security |
RAILS_HOST | 用于邮件链接、Webhook 负载和 API 响应中附件 URL 的公共主机名(不含协议)。未设置 → URL 指向 localhost。在 Render 上,会回退到 RENDER_EXTERNAL_HOSTNAME |
CDN_HOST | 资产和图片使用的可选独立主机(设置 config.asset_host 和 Spree.cdn_host) |
SOLID_QUEUE_IN_PUMA | true(默认值)表示在 Web 进程中运行任务;运行 bin/jobs 工作进程时设为 false |
JOB_THREADS / JOB_CONCURRENCY | 工作进程线程数(默认值 3)/工作进程数(默认值 1) |
SPREE_IMPORT_JOB_CONCURRENCY | 并发 CSV 导入组任务数上限(默认为 JOB_THREADS 的 75%,最小值 1;0 = 不设上限) |
MISSION_CONTROL_USER / MISSION_CONTROL_PASSWORD | /jobs 的 HTTP Basic 身份验证。生产环境中未设置 → 仪表板将被锁定 |
SMTP_HOST(以及 SMTP_PORT、SMTP_USERNAME、SMTP_PASSWORD、SMTP_FROM_ADDRESS) | 未设置 SMTP_HOST 时,生产环境不会发送邮件。任何 SMTP 提供商都可使用,无需安装任何内容。始终尝试 STARTTLS,因此请使用端口 587,而不是隐式 TLS 端口 465。仅在设置 SMTP_USERNAME 时才会发送身份验证信息。商家可在每个店铺的 Settings → Emails 中覆盖发件人/回复地址 |
AWS_* 或 CLOUDFLARE_* | 上传文件使用的对象存储——在临时文件系统上必需 |
RAILS_FORCE_SSL / RAILS_ASSUME_SSL | 默认均为 true(位于 TLS 终止代理之后)。仅当全程都没有 TLS 时,才将两者都设为 false |
SENTRY_DSN、OTEL_*、MEILISEARCH_URL | 可选集成(见下文) |
Puma:PORT(3000)、RAILS_MAX_THREADS(3)、WEB_CONCURRENCY(1;auto = 每个核心一个,约需 1 GB RAM)。
数据库
- 推荐使用 PostgreSQL;生产环境也支持 MySQL/MariaDB 和 SQLite(SQLite:仅限单主机、小型店铺)。
- 连接池大小必须覆盖 Web 线程加上进程内任务线程:模板中的
database.yml使用RAILS_MAX_THREADS + JOB_THREADS + 3。如果手动编写database.yml,请保留此公式,否则 Puma 中的任务会耗尽连接池。 - Solid Queue、Solid Cache 和 Solid Cable 的表位于同一个数据库中——请将其作为一个整体备份。
迁移、种子数据与升级
- 镜像入口点(
bin/docker-entrypoint)仅当容器命令为 Rails 服务器时才会运行bin/rails db:prepare——首次启动时创建数据库并填充种子数据,之后启动时执行迁移。bin/jobs工作进程不会执行迁移。 - 首次启动: 扩容前先运行一次——通过运行
bin/rails db:prepare的发布/初始化任务,或启动一个 Web 副本。Rails 的迁移 advisory lock(PostgreSQL/MySQL)仅会串行化迁移,不会串行化数据库创建、架构加载或种子数据填充,因此多个副本同时操作空数据库时可能发生冲突或重复填充种子数据。 - 之后启动: 在 PostgreSQL/MySQL 上,多个 Web 副本同时执行迁移没有问题(由 advisory lock 保证)。如果你的平台有发布/部署前阶段,且希望迁移失败时阻止部署,则在该阶段运行
bin/rails db:migrate。 - 升级 Spree gems 后,每个环境运行一次数据回填:
bin/rails spree:upgrade(可幂等执行;使用DRY_RUN=1预览)。入口点不会运行此命令——请使用发布命令或一次性任务。参见spree-upgrade技能。 - 不会创建默认管理员。填充种子数据时会输出一次性设置链接;使用
bin/rails spree:setup:token可再次输出。
后台任务(Solid Queue)
-
config/puma.rb会在SOLID_QUEUE_IN_PUMA为true时加载plugin :solid_queue。 -
config/queue.yml按轮询顺序定义工作队列(结账关键队列优先,其次是批量目录任务,最后是 Active Storage 清理),并以"*"结尾,确保未列出的队列也会运行。 -
Spree 任务从
Spree.queues.<name>获取队列(每个条目默认值均为:default);模板的config/initializers/spree.rb会将其映射到spree_*名称。性能调优请参见spree-performance。 -
config/recurring.yml是 Solid Queue 的 cron 配置。模板设置的计划如下(仅限生产环境):Spree::StockReservations::ExpireJob— 每分钟(释放已过期的结账库存保留)Spree::Orders::FinalizeStaleDraftsJob— 每 5 分钟(完成付款成功但未成功提交完成状态的订单;绝不退款)Spree::Carts::ReapExpiredJob— 每小时(删除过期的废弃购物车;仍有有效付款的购物车会保留)Spree::Collections::RegenerateTimeBasedJob— 每小时(使用基于时间规则的自动集合)spree:price_history:prunerake 任务(通过command:条目)— 每天(按店铺执行 EU Omnibus 数据保留)Spree::SellerPayouts::SweepDueJob— 每天(市场卖家付款;无论卖家付款周期如何,都应每天运行——是否到期由每位卖家分别决定)Spree::SellerTransfers::ExecutePendingDueJob— 每小时(重试被拒绝的卖家转账)- Solid Queue 已完成任务和 Solid Cable 消息清理 — 每小时
没有卖家的店铺运行市场任务时不会执行任何操作——请保留这些任务。如果你的项目早于其中任何条目,请从 spree-starter 的
config/recurring.yml中复制。 -
分离模式:添加一个使用相同环境变量(
DATABASE_URL、SECRET_KEY_BASE、RAILS_HOST)运行bin/jobs的服务,在该服务上提高JOB_THREADS,通过JOB_CONCURRENCY或增加副本数进行扩容,并在 Web 上设置SOLID_QUEUE_IN_PUMA=false。采用分离式部署时,请在 Web 上明确设置SPREE_IMPORT_JOB_CONCURRENCY——该上限由将导入任务加入队列的进程计算。
Mission Control(/jobs)
在模板的路由中挂载于 /jobs。设置 MISSION_CONTROL_USER 和 MISSION_CONTROL_PASSWORD;在开发环境中,它们的默认值为 spree / spree123。此功能仅适用于 Solid Queue。
切换到 Sidekiq(可选)
# Gemfile
gem "sidekiq"
gem "sidekiq-cron" # replaces config/recurring.yml
# config/application.rb
config.active_job.queue_adapter = :sidekiq
config/sidekiq.yml必须列出你通过Spree.queues分配的所有队列,以及default、mailers和active_storage_*队列。Sidekiq 没有"*"通配队列,因此未列出的队列永远不会运行。- 将
recurring.yml中的计划任务移至config/schedule.yml(sidekiq-cron)。sidekiq-cron 调度的是作业,而非命令,因此请将spree:price_history:prune任务封装在一个小型作业中(如果尚未定义,则执行Rails.application.load_tasks,然后执行Rake::Task[...].tap(&:reenable).invoke)。 - 在身份验证保护下挂载
Sidekiq::Web,而不是 Mission Control;将bundle exec sidekiq作为独立服务运行,并确保RAILS_MAX_THREADS >= SIDEKIQ_CONCURRENCY;移除SOLID_QUEUE_IN_PUMA。
缓存
Solid Cache(config.cache_store = :solid_cache_store)是默认设置,无需预配。若要使用内存缓存:添加 redis gem,并在 config/environments/production.rb 中设置 config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"] }。也适用于 Valkey。
管理仪表板
- 单节点(默认):
spree_dashboardgem 在/dashboard提供构建好的仪表板(并在/sellers提供可选的市场卖家面板),支持 SPA 回退。它从Spree::Dashboard.dist_path或SPREE_DASHBOARD_DIST_PATH提供目录(卖家面板:SPREE_SELLER_PANEL_DIST_PATH);未设置时返回 404。镜像构建会完成此配置,同源、无需 CORS,也无需 API 密钥。 - 静态主机 / CDN: 执行
cd apps/dashboard && VITE_SPREE_API_URL=https://api.example.com pnpm build,上传dist/,将/* → /index.html重写,并确保两端均使用 HTTPS(刷新 Cookie 为SameSite=None; Secure),然后在 Settings → Allowed Origins 下添加仪表板来源。VITE_*值会写入构建包中,切勿在其中放入机密信息;每个环境构建一次。 - 仅提供 API 的部署可以移除
spree_dashboardgem。
文件存储和 CDN
- 上传通过 Active Storage 进行。本地磁盘是默认设置,但在临时平台(Render、Heroku、Fargate、大多数 K8s)上会丢失。设置
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY(以及AWS_REGION、AWS_BUCKET),或设置CLOUDFLARE_ENDPOINT+CLOUDFLARE_ACCESS_KEY_ID/CLOUDFLARE_SECRET_ACCESS_KEY(以及CLOUDFLARE_BUCKET);模板会自动检测服务。其他后端:标准的config/storage.yml。 - 在
/rails/active_storage/representations/和/assets前接入 CDN。若使用独立的 CDN 主机名,请设置CDN_HOST。
搜索
默认搜索使用 SQL(Spree::SearchProvider::Database)。对于大型商品目录,请添加 Meilisearch:
# Gemfile
gem "spree_meilisearch"
# config/initializers/spree.rb
Spree.search_provider = 'SpreeMeilisearch::SearchProvider'
环境变量:MEILISEARCH_URL(默认为 http://localhost:7700)、MEILISEARCH_API_KEY。首次部署后以及提供程序配置变更后,构建索引:bin/rails spree:search:reindex(本地使用 spree task search:reindex)。
可观测性
- 错误: 设置
SENTRY_DSN(模板内置sentry-rails)。 - 追踪: 添加
gem 'spree_opentelemetry'并设置标准 OTel 变量:OTEL_SERVICE_NAME、OTEL_EXPORTER_OTLP_ENDPOINT(或OTEL_EXPORTER_OTLP_TRACES_ENDPOINT),以及可选的OTEL_TRACES_SAMPLER=parentbased_traceidratio+OTEL_TRACES_SAMPLER_ARG。只有设置了导出程序变量后才会启用;OTEL_SDK_DISABLED=true始终优先。跨度涵盖 HTTP、SQL、作业、每次Spree::Workflow运行 / 步骤 / 钩子、事件分发、Webhook 交付(会传播 W3C 追踪上下文)以及网关调用。跨度属性不包含个人身份信息。 - Sentry + OTel: 保留 Sentry 以处理错误,且不要同时设置 Sentry 的
traces_sample_rate(会造成重复插桩)。若要将 Sentry 用作追踪后端,请参阅可观测性文档中的 Sentry 部分(sentry-opentelemetry、在Sentry.init之前调用SpreeOpenTelemetry.install!、OTEL_TRACES_EXPORTER=none)。 - 健康检查:
/up(Rails 内置;不会访问数据库)。
平台说明
- Render:
create-spree-app会将初始项目的 Blueprint 移至项目根目录,命名为render.yaml,并将每个服务的dockerfilePath(包括注释掉的 worker)指向./server/Dockerfile,将仓库根目录设为dockerContext——一个 Web 服务 + Postgres。Blueprint 会生成SECRET_KEY_BASE、三个ACTIVE_RECORD_ENCRYPTION_*密钥(将它们从 Environment 选项卡复制到你的密钥管理器中)以及MISSION_CONTROL_PASSWORD(用户jobs)。取消注释 worker 块即可启用拆分模式。文件系统是临时的——请配置 S3/R2。 - AWS: 单个 EC2 + RDS,或 ECS Fargate(从同一个镜像运行 Web 和
bin/jobs两个独立服务)。 - Kubernetes: 一个用于 Web 的 Deployment,可选再为
bin/jobs配置一个;使用 Secrets 存储SECRET_KEY_BASE/DATABASE_URL/存储凭据;在/up配置存活探针;通过入口点(server 命令)或运行db:migrate的 Job/初始化容器执行迁移。 - VPS: 使用
docker compose配置web+postgres即可完成部署;添加 Caddy/nginx/Traefik 以启用 TLS。
常见问题
| 症状 | 原因 / 修复方法 |
|---|---|
图片/附件 URL 指向 https://localhost/... | 未设置 RAILS_HOST(或 CDN_HOST) |
/dashboard 返回 404 | 缺少 spree_dashboard gem,或未设置 SPREE_DASHBOARD_DIST_PATH/Spree::Dashboard.dist_path(自定义镜像构建时未包含 dashboard 阶段) |
| 静态托管的 dashboard 无法登录 / 出现 CORS 错误 | 来源不在 Settings → Allowed Origins 中,或两端都未使用 HTTPS |
/jobs 始终返回 401 | 生产环境中未设置 MISSION_CONTROL_USER/PASSWORD |
| 拆分后任务始终不运行 | Worker 未运行 bin/jobs,或在 Web 上设置了 SOLID_QUEUE_IN_PUMA=false,但没有 worker |
| 任务在 Solid Queue 上运行,但在 Sidekiq 上不运行 | sidekiq.yml 中缺少队列(没有兜底队列) |
负载下出现 ActiveRecord::ConnectionTimeoutError | 连接池小于 RAILS_MAX_THREADS + JOB_THREADS |
| 重新部署后上传文件消失 | 在临时文件系统上使用本地磁盘存储 |
| 生产环境中没有邮件 | 未设置 SMTP_HOST |
| Meilisearch 搜索没有结果 | 尚未构建索引——运行 spree:search:reindex |
| 结账库存一直被占用 / 卖家始终未收到款项 | recurring.yml 中缺少条目,或条目不在 production: 键下;也可能是切换到 Sidekiq 时未迁移这些条目 |
| 升级 gem 后商店数据看起来只迁移了一半 | 未运行 spree:upgrade |
启动日志:[Spree] Active Record encryption is not fully configured | 未设置 ACTIVE_RECORD_ENCRYPTION_*(或未将其读入 config.active_record.encryption)——机密数据正以明文形式存储 |
| 部署后 Webhook 密钥 / 网关客户信息无法读取 | 此环境中的加密密钥已更改或缺失,或者启用加密前未先加密现有数据行(参见 spree-security) |
延伸阅读
node_modules/@spree/docs/dist/developer/deployment/—quickstart.md、docker.md、environment_variables.md、background_jobs.md、caching.md、database.md、assets.md、cdn.md、emails.md、render.md、aws.md、aws_ecs.mdnode_modules/@spree/docs/dist/developer/dashboard/deployment.mdnode_modules/@spree/docs/dist/developer/providers/observability.md- https://spreecommerce.org/docs/developer/deployment/quickstart
- 相关技能:
spree-performance(队列和 worker 调优)、spree-upgrade(发布阶段升级)、spree-security(机密信息、加密密钥)、spree-multi-tenant(根域名、Cookie、主机)