spree-deployment について
spree-deploymentは、Spree 6を本番環境で運用するチーム向けのデプロイガイドです。コンテナ構築、環境設定、データベース、バックグラウンドジョブに加え、サービス分割の選択肢を説明します。
主な機能
- 01Dockerイメージ構築と配置
- 02一体型・分割ワーカー構成
- 03環境変数とデータベース設定
- 04マイグレーションとアップグレード
- 05Solid QueueとSidekiqの設定
- 06ダッシュボード・ストレージ・検索設定
活用シーン
- 01本番環境へのSpree 6ストアのデプロイ
- 02バックグラウンドワーカーの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 の本番環境へのデプロイは、1 つの Docker イメージと 1 つのデータベースで構成されます。同じイメージで Store/Admin API を実行し、/dashboard で React 管理画面を配信し、デフォルトではバックグラウンドジョブもプロセス内で処理します。ジョブ(Solid Queue)とキャッシュ(Solid Cache)はデータベースに保存されます。Redis は任意です。すべて環境変数で設定します。
プロジェクト構成の確認:Rails アプリは server/(古いプロジェクトでは backend/)、管理画面は apps/dashboard/ にあります。Classic flavor(リポジトリのルートに Spree gem を配置したプレーンな Rails アプリ)では、同じ環境変数と Rails 設定を使用します。異なるのはパスと CLI ラッパーだけです。
トポロジーの選択
| 構成 | 適用場面 | 方法 |
|---|---|---|
| 統合型(デフォルト) | ほとんどのストア。1 つのコンテナでアプリ全体を実行 | Web コンテナで Solid Queue プラグインを使って Puma を実行します(SOLID_QUEUE_IN_PUMA=true) |
| ワーカー分離型 | 大規模なインポート、大規模カタログ、多数のセラーと同期するマーケットプレイス、マルチテナント | 同じイメージを使用し、bin/jobs コマンドで 2 つ目のサービスを起動します。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 マーカー(初回リリースでは .spree-custom-dashboard)でレイアウト対応の Dockerfile を認識します。どちらのマーカーもない場合は、server/ をコンテキストとしてビルドします。そのため、server/Dockerfile がこのレイアウトへの対応以前のものであれば、spree-starter テンプレートから更新してください。更新しないと、カスタマイズした管理画面ではなく標準の管理画面がデプロイされます。
カスタマイズしていないストアでは、公式のマルチアーキテクチャイメージ ghcr.io/spree/spree:<version> をそのまま実行できます。
環境変数
必須:DATABASE_URL、SECRET_KEY_BASE。実質的に必須:RAILS_HOST と 3 つの Active Record 暗号化キー。全項目の一覧:references/environment-variables.md。
| 変数 | 重要な理由 |
|---|---|
DATABASE_URL | postgres://…、mysql2://…、または sqlite3:db/production.sqlite3 |
SECRET_KEY_BASE | openssl rand -hex 64。値を変更しないでください。セッション、Cookie、JWT のフォールバック、シークレット API キーのダイジェストはこの値に依存します |
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY、ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY、ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT | Webhook 署名シークレット、ゲートウェイの顧客 ID、OAuth ID トークンを保存時に暗号化します。未設定の場合は平文で保存され、起動時に警告が表示されます。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 認証。production で未設定の場合、ダッシュボードはロックされます |
SMTP_HOST(および SMTP_PORT、SMTP_USERNAME、SMTP_PASSWORD、SMTP_FROM_ADDRESS) | SMTP_HOST がない場合、production ではメールを送信しません。インストール不要で、任意の SMTP プロバイダーを使用できます。STARTTLS は常に試行されるため、暗黙的な TLS の 465 ではなくポート 587 を使用してください。認証方式は plain で、SMTP_USERNAME が設定されている場合にのみ送信されます。マーチャントは「Settings → Emails」で、ストアごとに From/Reply-To を上書きできます |
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 つ、約 1 GB の RAM)。
データベース
- PostgreSQL を推奨します。MySQL/MariaDB と SQLite も production でサポートされています(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 レプリカを 1 つだけ起動して、一度だけ実行してください。Rails のマイグレーションアドバイザリロック(PostgreSQL/MySQL)が直列化するのはマイグレーションのみであり、データベースの作成、スキーマのロード、シード処理は対象外です。そのため、空のデータベースで複数のレプリカが同時に実行されると、競合や二重シードが発生する可能性があります。 - 以降の起動時: PostgreSQL/MySQL では、複数の Web レプリカが同時にマイグレーションを実行しても問題ありません(アドバイザリロックが機能します)。プラットフォームにリリース/デプロイ前フェーズがあり、マイグレーション失敗時にロールアウトを中止したい場合は、代わりにそこで
bin/rails db:migrateを実行してください。 - Spree gem のアップグレード後は、環境ごとに一度、データのバックフィルを実行してください:
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 オムニバス指令のデータ保持処理)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を呼び出します)。- Mission Control の代わりに
Sidekiq::Webを認証の背後にマウントしてください。独立したサービスとして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 は、SPA フォールバック付きで/dashboardにビルド済みダッシュボードを(任意でマーケットプレイスの出品者パネルを/sellersに)配信します。ディレクトリは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 トレースコンテキストを伝播)、ゲートウェイ呼び出しを対象にスパンを記録します。スパン属性に個人識別情報(PII)は含まれません。 - Sentry + OTel: エラーには Sentry を使い、Sentry の
traces_sample_rateも設定することは避けてください(計測が二重になります)。Sentry をトレースのバックエンドとして使うには、可観測性ドキュメントの Sentry のセクションを参照してください(sentry-opentelemetry、Sentry.initの前にSpreeOpenTelemetry.install!を実行、OTEL_TRACES_EXPORTER=none)。 - ヘルスチェック:
/up(Rails 組み込みの機能です。DB にはアクセスしません)。
プラットフォームに関する注意事項
- Render:
create-spree-appはスターターの Blueprint をプロジェクトルートにrender.yamlとして配置し、すべてのサービスのdockerfilePath(コメントアウトされたワーカーを含む)を./server/Dockerfileに設定し、リポジトリのルートをdockerContextに指定します。Web サービス 1 つと Postgres の構成です。Blueprint はSECRET_KEY_BASE、3 つのACTIVE_RECORD_ENCRYPTION_*キー(Environment タブからシークレットマネージャーにコピーしてください)、およびMISSION_CONTROL_PASSWORD(ユーザー名はjobs)を生成します。分割モードにするには、ワーカーブロックのコメントを解除してください。ファイルシステムは一時的なものです。S3/R2 を設定してください。 - AWS: EC2 と RDS をそれぞれ 1 つずつ使用するか、ECS Fargate を使用します(同じイメージから Web と
bin/jobsを別々のサービスとして実行します)。 - Kubernetes: Web 用の Deployment を 1 つ作成し、必要に応じて
bin/jobs用も作成します。SECRET_KEY_BASE/DATABASE_URL/ストレージ認証情報には Secrets を使用します。/upでライブネスを確認します。マイグレーションはエントリーポイント(サーバーコマンド)で実行するか、db:migrateを実行する Job/init コンテナで実行します。 - VPS:
docker composeでwebとpostgresを構成すれば、完全なデプロイになります。TLS 用に Caddy/nginx/Traefik を追加してください。
よくある問題
| 症状 | 原因/対処方法 |
|---|---|
画像/添付ファイルの URL が https://localhost/... を指している | RAILS_HOST(または CDN_HOST)が未設定です |
/dashboard が 404 を返す | spree_dashboard gem がないか、SPREE_DASHBOARD_DIST_PATH/Spree::Dashboard.dist_path が未設定です(ダッシュボードのステージなしでカスタムイメージをビルドしています) |
| 静的ホストのダッシュボードにログインできない/CORS エラーが発生する | Origin が Settings → Allowed Origins に含まれていないか、両方が HTTPS ではありません |
/jobs が常に 401 を返す | 本番環境で MISSION_CONTROL_USER/PASSWORD が未設定です |
| 分割後にジョブがまったく実行されない | ワーカーが bin/jobs を実行していないか、ワーカーがない状態で Web に SOLID_QUEUE_IN_PUMA=false が設定されています |
| 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(キューとワーカーのチューニング)、spree-upgrade(リリースフェーズでのアップグレード)、spree-security(シークレット、暗号化キー)、spree-multi-tenant(ルートドメイン、Cookie、ホスト)