spree/agent-skills

spree-deployment

Use when deploying a Spree 6 application to production or scaling it — Docker image builds, Render/AWS/Railway/Kubernetes/VPS, required environment variables, Solid Queue (jobs in Puma vs a dedicated bin/jobs worker), Solid Cache, optional Sidekiq/Redis, th…

查看源码

使用方法

  1. 复制安装命令,在自己的智能体环境安装前先检查源码包。
  2. 从原始文档核对所需工具、凭据和支持的宿主。
  3. 说明输入材料与期望交付物,并对照来源检查输出。
npx skills add https://github.com/spree/agent-skills --skill spree-deployment

开始前需了解

  • 安装不包含模型订阅、API 额度或访问文件的权限。
  • 仓库星数和安装量反映活跃情况,不等于独立质量或安全测试。
仓库原始内容

按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。

Deploying Spree

A Spree 6 production deployment is one Docker image + one database. The same image runs the Store/Admin API, serves the React admin at /dashboard, and — by default — processes background jobs in-process. Jobs (Solid Queue) and cache (Solid Cache) live in the database; Redis is optional. Everything is configured with environment variables.

Project layout reminder: the Rails app is server/ (older projects: backend/), the admin dashboard is apps/dashboard/. Classic flavor (plain Rails app with Spree gems at the repo root) uses the same env vars and Rails config — only the paths and the CLI wrapper differ.

Topology decision

ShapeWhenHow
Combined (default)Most stores; one container is the whole appWeb container runs Puma with the Solid Queue plugin (SOLID_QUEUE_IN_PUMA=true)
Split workerHeavy imports, big catalogs, marketplaces syncing many sellers, multi-tenantSame image, second service with command bin/jobs; set SOLID_QUEUE_IN_PUMA=false on web
DistributedIndependent scaling/release of surfacesAPI behind a load balancer; dashboard as static files on a CDN; storefront (Next.js) on Vercel/Node host
SidekiqOnly when a dedicated Solid Queue worker with raised JOB_THREADS/JOB_CONCURRENCY no longer keeps upSwap Active Job adapter to Sidekiq on Redis/Valkey (see below)

Start combined; every step up is configuration, not code.

Building the image

bash
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

Build from the project root as context: the Dockerfile detects server/ (Rails) and apps/dashboard/ (your customized dashboard) and bakes your dashboard build into the image. Without apps/dashboard/, the stock dashboard is baked in. spree build --production recognizes the layout-aware Dockerfile by its .spree-custom-app marker (.spree-custom-dashboard in the first release); without either it falls back to building with server/ as the context. So if your server/Dockerfile predates this layout support, update it from the spree-starter template — otherwise you ship the stock dashboard over your customized one.

Uncustomized stores can run the official multi-arch image ghcr.io/spree/spree:<version> directly.

Environment variables

Strictly required: DATABASE_URL, SECRET_KEY_BASE. Practically required: RAILS_HOST and the three Active Record encryption keys. Full table: references/environment-variables.md.

VariableWhy it matters
DATABASE_URLpostgres://…, mysql2://…, or sqlite3:db/production.sqlite3
SECRET_KEY_BASEopenssl rand -hex 64. Keep stable — sessions, cookies, JWT fallback and secret-API-key digests depend on it
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY, ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY, ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALTEncrypt webhook signing secrets, gateway customer IDs and OAuth identity tokens at rest. Unset → plaintext + a boot warning. Generate with spree encryption init --print or bin/rails db:encryption:init; a separate set per environment, backed up; never change them once data is encrypted. Workers need the same values as web. See spree-security
RAILS_HOSTPublic host (no protocol) for email links, webhook payloads, and attachment URLs in API responses. Unset → URLs point at localhost. On Render, falls back to RENDER_EXTERNAL_HOSTNAME
CDN_HOSTOptional separate host for assets and images (sets config.asset_host and Spree.cdn_host)
SOLID_QUEUE_IN_PUMAtrue (default) runs jobs in the web process; false when a bin/jobs worker runs
JOB_THREADS / JOB_CONCURRENCYWorker threads (default 3) / worker processes (default 1)
SPREE_IMPORT_JOB_CONCURRENCYCap on concurrent CSV import group jobs (default 75% of JOB_THREADS, min 1; 0 = no cap)
MISSION_CONTROL_USER / MISSION_CONTROL_PASSWORDHTTP Basic auth for /jobs. Unset in production → dashboard locked
SMTP_HOST (+ SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_FROM_ADDRESS)Without SMTP_HOST, production sends no email. Any SMTP provider works, with nothing to install. STARTTLS is always attempted, so use port 587, not an implicit-TLS 465. Auth is plain, sent only when SMTP_USERNAME is set. Merchants override From/Reply-To per store in Settings → Emails
AWS_* or CLOUDFLARE_*Object storage for uploads — required on ephemeral filesystems
RAILS_FORCE_SSL / RAILS_ASSUME_SSLBoth default true (behind a TLS-terminating proxy). Set both false only when there is no TLS anywhere
SENTRY_DSN, OTEL_*, MEILISEARCH_URLOptional integrations (below)

Puma: PORT (3000), RAILS_MAX_THREADS (3), WEB_CONCURRENCY (1; auto = one per core, ~1 GB RAM each).

Database

  • PostgreSQL recommended; MySQL/MariaDB and SQLite are supported in production too (SQLite: single host only, small stores).
  • Pool size must cover web threads plus in-process job threads: the template's database.yml uses RAILS_MAX_THREADS + JOB_THREADS + 3. If you hand-roll database.yml, keep that formula or jobs in Puma will exhaust the pool.
  • Solid Queue, Solid Cache and Solid Cable tables live in the same database — back it up as one unit.

Migrations, seeds, and upgrades

  • The image entrypoint (bin/docker-entrypoint) runs bin/rails db:prepare only when the container command is the Rails server — creates + seeds on first boot, migrates on later boots. bin/jobs workers don't migrate.
  • First boot: run it once before scaling out — a release/init job running bin/rails db:prepare, or a single web replica. Rails' migration advisory lock (PostgreSQL/MySQL) serializes migrations only, not database creation, schema load or seeding, so replicas racing on an empty database can collide or double-seed.
  • Later boots: several web replicas migrating at once is fine on PostgreSQL/MySQL (the advisory lock). If your platform has a release/pre-deploy phase and you want failed migrations to block the rollout, run bin/rails db:migrate there instead.
  • After upgrading Spree gems, run the data backfills once per environment: bin/rails spree:upgrade (idempotent; DRY_RUN=1 to preview). The entrypoint does not run it — use a release command or one-off task. See the spree-upgrade skill.
  • No default admin exists. Seeding prints a one-time setup link; print it again with bin/rails spree:setup:token.

Background jobs (Solid Queue)

  • config/puma.rb loads plugin :solid_queue when SOLID_QUEUE_IN_PUMA is true.
  • config/queue.yml defines worker queues in polling order (checkout-critical first, bulk catalog next, Active Storage housekeeping last) with a trailing "*" so any unlisted queue still runs.
  • Spree jobs pick their queue from Spree.queues.<name> (every entry defaults to :default); the template's config/initializers/spree.rb maps them to spree_* names. See spree-performance for tuning.
  • config/recurring.yml is Solid Queue's cron. The template schedules (production only):
  • Spree::StockReservations::ExpireJob — every minute (releases expired checkout holds)
  • Spree::Orders::FinalizeStaleDraftsJob — every 5 minutes (finishes orders whose payment succeeded but whose completion never committed; never refunds)
  • Spree::Carts::ReapExpiredJob — hourly (deletes abandoned carts past expiry; carts with a live payment are kept)
  • Spree::Collections::RegenerateTimeBasedJob — hourly (automatic collections with time-based rules)
  • spree:price_history:prune rake task (via a command: entry) — daily (EU Omnibus retention, per store)
  • Spree::SellerPayouts::SweepDueJob — daily (marketplace payouts; run it daily regardless of seller payout intervals — due-ness is decided per seller)
  • Spree::SellerTransfers::ExecutePendingDueJob — hourly (retries refused seller transfers)
  • Solid Queue finished-job and Solid Cable message cleanup — hourly

The marketplace jobs are no-ops on a store without sellers — keep them. If your project predates any entry, copy it from the spree-starter config/recurring.yml.

  • Split mode: add a service running bin/jobs with the same env (DATABASE_URL, SECRET_KEY_BASE, RAILS_HOST), raise JOB_THREADS there, scale with JOB_CONCURRENCY or more replicas, and set SOLID_QUEUE_IN_PUMA=false on web. On a split deployment set SPREE_IMPORT_JOB_CONCURRENCY explicitly on web — the cap is computed by the process that enqueues the import.

Mission Control (/jobs)

Mounted at /jobs in the template's routes. Set MISSION_CONTROL_USER and MISSION_CONTROL_PASSWORD; in development they default to spree / spree123. It's Solid Queue-specific.

Swapping to Sidekiq (optional)

ruby
# Gemfile
gem "sidekiq"
gem "sidekiq-cron"        # replaces config/recurring.yml

# config/application.rb
config.active_job.queue_adapter = :sidekiq
  • config/sidekiq.yml must list every queue you assigned via Spree.queues plus default, mailers, and the active_storage_* queues — Sidekiq has no "*" catch-all, so an unlisted queue never runs.
  • Move recurring.yml schedules to config/schedule.yml (sidekiq-cron). sidekiq-cron schedules jobs, not commands, so wrap the spree:price_history:prune task in a small job (Rails.application.load_tasks unless defined, then Rake::Task[...].tap(&:reenable).invoke).
  • Mount Sidekiq::Web behind auth instead of Mission Control; run bundle exec sidekiq as its own service with RAILS_MAX_THREADS >= SIDEKIQ_CONCURRENCY; drop SOLID_QUEUE_IN_PUMA.

Cache

Solid Cache (config.cache_store = :solid_cache_store) is the default — nothing to provision. For an in-memory cache: add the redis gem and set config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"] } in config/environments/production.rb. Works with Valkey.

The admin dashboard

  • Single node (default): the spree_dashboard gem serves the built dashboard at /dashboard (and an optional marketplace seller panel at /sellers) with SPA fallback. It serves the directory from Spree::Dashboard.dist_path or SPREE_DASHBOARD_DIST_PATH (seller panel: SPREE_SELLER_PANEL_DIST_PATH); unset → 404. The image build sets this up — same origin, no CORS, no API keys.
  • Static host / CDN: cd apps/dashboard && VITE_SPREE_API_URL=https://api.example.com pnpm build, upload dist/, rewrite /* → /index.html, HTTPS on both sides (the refresh cookie is SameSite=None; Secure), and add the dashboard origin under Settings → Allowed Origins. VITE_* values are baked into the bundle — never put secrets there; one build per environment.
  • API-only deployments can drop the spree_dashboard gem.

File storage and CDN

  • Uploads go through Active Storage. Local disk is the default and is lost on ephemeral platforms (Render, Heroku, Fargate, most K8s). Set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY (+ AWS_REGION, AWS_BUCKET) or CLOUDFLARE_ENDPOINT + CLOUDFLARE_ACCESS_KEY_ID/CLOUDFLARE_SECRET_ACCESS_KEY (+ CLOUDFLARE_BUCKET); the template auto-detects the service. Other backends: standard config/storage.yml.
  • Put a CDN in front of /rails/active_storage/representations/ and /assets. With a separate CDN hostname, set CDN_HOST.

Search

Default search is SQL (Spree::SearchProvider::Database). For large catalogs add Meilisearch:

ruby
# Gemfile
gem "spree_meilisearch"
# config/initializers/spree.rb
Spree.search_provider = 'SpreeMeilisearch::SearchProvider'

Env: MEILISEARCH_URL (default http://localhost:7700), MEILISEARCH_API_KEY. Build the index after the first deploy and after provider config changes: bin/rails spree:search:reindex (spree task search:reindex locally).

Observability

  • Errors: set SENTRY_DSN (template ships sentry-rails).
  • Tracing: add gem 'spree_opentelemetry' and set standard OTel vars — OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT (or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT), optional OTEL_TRACES_SAMPLER=parentbased_traceidratio + OTEL_TRACES_SAMPLER_ARG. Dormant until an exporter var is set; OTEL_SDK_DISABLED=true always wins. Spans cover HTTP, SQL, jobs, every Spree::Workflow run/step/hook, event dispatch, webhook deliveries (W3C trace context propagated), and gateway calls. Span attributes carry no PII.
  • Sentry + OTel: keep Sentry for errors and don't also set Sentry's traces_sample_rate (double instrumentation). To use Sentry as the trace backend, see the Sentry section of the observability doc (sentry-opentelemetry, SpreeOpenTelemetry.install! before Sentry.init, OTEL_TRACES_EXPORTER=none).
  • Health check: /up (Rails built-in; it does not touch the DB).

Platform notes

  • Render: create-spree-app moves the starter's Blueprint to the project root as render.yaml and points every service's dockerfilePath (including the commented worker) at ./server/Dockerfile, with the repo root as dockerContext — one web service + Postgres. The Blueprint generates SECRET_KEY_BASE, the three ACTIVE_RECORD_ENCRYPTION_* keys (copy them from the Environment tab into your secret manager) and MISSION_CONTROL_PASSWORD (user jobs). Uncomment the worker block for split mode. Filesystem is ephemeral — configure S3/R2.
  • AWS: single EC2 + RDS, or ECS Fargate (web and bin/jobs as separate services from one image).
  • Kubernetes: one Deployment for web, optionally one for bin/jobs; Secrets for SECRET_KEY_BASE/DATABASE_URL/storage creds; liveness on /up; migrations either via the entrypoint (server command) or a Job/init container running db:migrate.
  • VPS: docker compose with web + postgres is a complete deployment; add Caddy/nginx/Traefik for TLS.

Common problems

SymptomCause / fix
Image/attachment URLs point at https://localhost/...RAILS_HOST (or CDN_HOST) unset
/dashboard returns 404spree_dashboard gem missing or SPREE_DASHBOARD_DIST_PATH/Spree::Dashboard.dist_path unset (custom image built without the dashboard stage)
Static-host dashboard can't log in / CORS errorsOrigin not in Settings → Allowed Origins, or not HTTPS on both sides
/jobs always 401MISSION_CONTROL_USER/PASSWORD unset in production
Jobs never run after splittingWorker not running bin/jobs, or SOLID_QUEUE_IN_PUMA=false set on web with no worker
Jobs run on Solid Queue but not on SidekiqQueue missing from sidekiq.yml (no catch-all)
ActiveRecord::ConnectionTimeoutError under loadPool smaller than RAILS_MAX_THREADS + JOB_THREADS
Uploads vanish after redeployLocal disk storage on an ephemeral filesystem
No emails in productionSMTP_HOST unset
Search returns nothing on MeilisearchIndex never built — run spree:search:reindex
Checkout stock held forever / sellers never paidrecurring.yml entries missing or not in the production: key, or you moved to Sidekiq without porting them
Store data looks half-migrated after a gem bumpspree:upgrade not run
Boot log: [Spree] Active Record encryption is not fully configuredACTIVE_RECORD_ENCRYPTION_* unset (or not read into config.active_record.encryption) — secrets are being stored in plaintext
Webhook secrets / gateway customers unreadable after a deployEncryption keys changed or missing on this environment, or encryption enabled without first encrypting existing rows (see spree-security)

Where to read further

  • 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.md
  • node_modules/@spree/docs/dist/developer/dashboard/deployment.md
  • node_modules/@spree/docs/dist/developer/providers/observability.md
  • https://spreecommerce.org/docs/developer/deployment/quickstart
  • Related skills: spree-performance (queue and worker tuning), spree-upgrade (release-phase upgrades), spree-security (secrets, encryption keys), spree-multi-tenant (root domain, cookies, hosts)
来自同一仓库

更多 Skills

全部 Skills
spree
社区

spree-multi-tenant

Use when building a SaaS / multi-tenant platform on Spree 6 with the Enterprise spreemultitenant gem — many isolated merchants on one deployment, each with its own stores, catalog, customers and orders. Covers licensing and install (spreeenterprise:install, spreemultitenant:install), Spree::Tenant and row-level tenantid isolation, SpreeMultiTenant::Base, withtenant / withouttenant, console tenant context, insertall/upsertall pitfalls, background jobs, Spree.rootdomain, cookie domain and host aut

安装量
1
GitHub Stars
10
最近更新
9月30日
spree
社区

spree-order-totals

Use when the user is working with money on a Spree 6 cart or order — how total is built, the typed money rows (Spree::TaxLine, Spree::Discount, Spree::Fee), totals columns (itemtotal, discounttotal, deliverytotal, feetotal, includedtaxtotal vs additionaltaxtotal, adjustmenttotal, amountdue), Spree::Carts::RecalculateTotals, custom adjusters (Spree.adjusters), fees and customs duties, manual discounts and fees on placed orders, or rendering an order summary. Common phrasings include "order total

安装量
1
GitHub Stars
10
最近更新
9月30日
spree
社区

spree-performance

Use when investigating or improving Spree 6 performance — slow cart writes, slow checkout, slow Store API product listings, N+1 queries in custom endpoints or serializers, search latency, Meilisearch indexing floods, image processing backlogs, Solid Queue/Sidekiq queue tuning, HTTP/CDN caching of the Store API, dashboard data-fetching, or tracing with OpenTelemetry. Common phrasings include "slow cart", "add to cart is slow", "tax provider called too often", "N+1 in Spree", "slow product listing

安装量
1
GitHub Stars
10
最近更新
9月30日
spree
社区

spree-pricing

Use when the user is working with Spree 6 pricing — variant prices per currency (pricein, amountin, setprice), compare-at/sale prices, multi-currency, price lists and price rules (market, channel, volume), catalog-owned price lists for B2B/wholesale, percentage price adjustments, quantity breaks / volume pricing, scheduled sales, the pricing context and resolution order, external pricing providers, bulk price updates, and EU Omnibus price history (priorprice). Common phrasings include "set price

安装量
1
GitHub Stars
10
最近更新
9月30日