@spree/docs 0.1.129 → 0.1.131

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,460 @@
1
+ ---
2
+ title: AWS — ECS Fargate
3
+ sidebarTitle: ECS Fargate
4
+ description: Production-grade Spree on AWS — ECS Fargate, RDS, CI/CD, and auto-scaling.
5
+ ---
6
+
7
+ This is the advanced AWS path: containers managed by ECS Fargate with auto-scaling, zero-downtime rolling deploys, CI/CD via GitHub Actions, and secrets in AWS Secrets Manager. If you're getting started or running a single store, the [EC2 + RDS guide](aws.md) gets you to production with a fraction of the moving parts — on the same [Docker image](docker.md), so you can graduate to ECS later without rework.
8
+
9
+ ## Required AWS Services
10
+
11
+ The required set is smaller than an AWS architecture diagram suggests — four services run the store:
12
+
13
+ | Service | Purpose |
14
+ |---------|---------|
15
+ | [ECS Fargate](https://aws.amazon.com/fargate/) | Runs the web (and optional worker) containers — no servers to manage |
16
+ | [RDS](https://aws.amazon.com/rds/) | The database — data, background jobs, and cache all live here. PostgreSQL, MySQL/MariaDB, and both Aurora flavors work — see [Database Configuration](database.md) |
17
+ | [ECR](https://aws.amazon.com/ecr/) | Registry for your Docker image |
18
+ | [S3](https://aws.amazon.com/s3/) | Uploaded files (product images) — Fargate containers have no persistent disk. See [Asset Storage](assets.md#aws-s3) |
19
+
20
+ In front of the web service you'll put an **Application Load Balancer** with a free HTTPS certificate from [Certificate Manager](https://aws.amazon.com/certificate-manager/) — that's your public entry point.
21
+
22
+ Everything else is optional:
23
+
24
+ | Service | When |
25
+ |---------|------|
26
+ | [CloudFront](https://aws.amazon.com/cloudfront/) | [CDN](cdn.md) in front of assets and images |
27
+ | [ElastiCache](https://aws.amazon.com/elasticache/) | Valkey/Redis for [high-traffic caching](caching.md) — the default cache lives in the database |
28
+ | [Route 53](https://aws.amazon.com/route53/) | DNS — convenient on AWS, but any DNS provider works |
29
+
30
+ ## Docker Image
31
+
32
+ Build [your project's image](docker.md) with `spree build --production`, or use the stock `ghcr.io/spree/spree` image for deployments without customizations.
33
+
34
+ To build and deploy a custom image to AWS ECR via GitHub Actions:
35
+
36
+ ```yaml
37
+ name: Deploy to AWS Fargate
38
+
39
+ on:
40
+ push:
41
+ branches: [ main ]
42
+ pull_request:
43
+ branches: [ main ]
44
+
45
+ env:
46
+ AWS_REGION: us-east-1
47
+ ECR_REPOSITORY: spree-starter
48
+ ECS_SERVICE_WEB: spree-web
49
+ ECS_SERVICE_WORKER: spree-worker
50
+ ECS_CLUSTER: spree-cluster
51
+
52
+ jobs:
53
+ build:
54
+ name: Build and Push to ECR
55
+ runs-on: ubuntu-latest
56
+
57
+ outputs:
58
+ image: ${{ steps.build-image.outputs.image }}
59
+ image-tag: ${{ steps.build-image.outputs.image-tag }}
60
+
61
+ steps:
62
+ - name: Checkout code
63
+ uses: actions/checkout@v4
64
+
65
+ - name: Configure AWS credentials
66
+ uses: aws-actions/configure-aws-credentials@v4
67
+ with:
68
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
69
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
70
+ aws-region: ${{ env.AWS_REGION }}
71
+
72
+ - name: Login to Amazon ECR
73
+ id: login-ecr
74
+ uses: aws-actions/amazon-ecr-login@v2
75
+
76
+ - name: Build, tag, and push image to Amazon ECR
77
+ id: build-image
78
+ env:
79
+ ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
80
+ IMAGE_TAG: ${{ github.sha }}
81
+ run: |
82
+ # Build a docker container and push it to ECR
83
+ docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
84
+ docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
85
+ echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
86
+ echo "image-tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
87
+
88
+ deploy-web:
89
+ name: Deploy Web Service
90
+ runs-on: ubuntu-latest
91
+ needs: build
92
+ if: github.ref == 'refs/heads/main'
93
+
94
+ steps:
95
+ - name: Checkout code
96
+ uses: actions/checkout@v4
97
+
98
+ - name: Configure AWS credentials
99
+ uses: aws-actions/configure-aws-credentials@v4
100
+ with:
101
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
102
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
103
+ aws-region: ${{ env.AWS_REGION }}
104
+
105
+ - name: Fill in the new image ID in the Amazon ECS task definition
106
+ id: task-def-web
107
+ uses: aws-actions/amazon-ecs-render-task-definition@v1
108
+ env:
109
+ ECR_REGISTRY: ${{ needs.build.outputs.image }}
110
+ IMAGE_TAG: ${{ needs.build.outputs.image-tag }}
111
+ AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
112
+ with:
113
+ task-definition: .aws/web-task-definition.json
114
+ container-name: web
115
+ image: ${{ needs.build.outputs.image }}
116
+
117
+ - name: Deploy Amazon ECS task definition for web
118
+ uses: aws-actions/amazon-ecs-deploy-task-definition@v1
119
+ with:
120
+ task-definition: ${{ steps.task-def-web.outputs.task-definition }}
121
+ service: ${{ env.ECS_SERVICE_WEB }}
122
+ cluster: ${{ env.ECS_CLUSTER }}
123
+ wait-for-service-stability: true
124
+
125
+ deploy-worker:
126
+ name: Deploy Worker Service
127
+ runs-on: ubuntu-latest
128
+ needs: build
129
+ if: github.ref == 'refs/heads/main'
130
+
131
+ steps:
132
+ - name: Checkout code
133
+ uses: actions/checkout@v4
134
+
135
+ - name: Configure AWS credentials
136
+ uses: aws-actions/configure-aws-credentials@v4
137
+ with:
138
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
139
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
140
+ aws-region: ${{ env.AWS_REGION }}
141
+
142
+ - name: Fill in the new image ID in the Amazon ECS task definition
143
+ id: task-def-worker
144
+ uses: aws-actions/amazon-ecs-render-task-definition@v1
145
+ env:
146
+ ECR_REGISTRY: ${{ needs.build.outputs.image }}
147
+ IMAGE_TAG: ${{ needs.build.outputs.image-tag }}
148
+ AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
149
+ with:
150
+ task-definition: .aws/worker-task-definition.json
151
+ container-name: worker
152
+ image: ${{ needs.build.outputs.image }}
153
+
154
+ - name: Deploy Amazon ECS task definition for worker
155
+ uses: aws-actions/amazon-ecs-deploy-task-definition@v1
156
+ with:
157
+ task-definition: ${{ steps.task-def-worker.outputs.task-definition }}
158
+ service: ${{ env.ECS_SERVICE_WORKER }}
159
+ cluster: ${{ env.ECS_CLUSTER }}
160
+ wait-for-service-stability: true
161
+
162
+ migrate:
163
+ name: Run Database Migrations
164
+ runs-on: ubuntu-latest
165
+ needs: [build, deploy-web]
166
+ if: github.ref == 'refs/heads/main'
167
+
168
+ steps:
169
+ - name: Checkout code
170
+ uses: actions/checkout@v4
171
+
172
+ - name: Configure AWS credentials
173
+ uses: aws-actions/configure-aws-credentials@v4
174
+ with:
175
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
176
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
177
+ aws-region: ${{ env.AWS_REGION }}
178
+
179
+ - name: Run database migrations
180
+ run: |
181
+ aws ecs run-task \
182
+ --cluster ${{ env.ECS_CLUSTER }} \
183
+ --task-definition spree-web \
184
+ --overrides '{
185
+ "containerOverrides": [{
186
+ "name": "web",
187
+ "command": ["bundle", "exec", "rails", "db:migrate"]
188
+ }]
189
+ }' \
190
+ --launch-type FARGATE \
191
+ --network-configuration '{
192
+ "awsvpcConfiguration": {
193
+ "subnets": ["'${{ secrets.SUBNET_ID_1 }}'", "'${{ secrets.SUBNET_ID_2 }}'"],
194
+ "securityGroups": ["'${{ secrets.SECURITY_GROUP_ID }}'"],
195
+ "assignPublicIp": "ENABLED"
196
+ }
197
+ }'
198
+ ```
199
+
200
+ This action requires secrets to be set in your GitHub repository. You can find the full list of secrets in the [AWS ECS Deploy Task Definition](https://github.com/aws-actions/amazon-ecs-deploy-task-definition) GitHub Actions repository.
201
+
202
+ | Secret | Description |
203
+ |--------|-------------|
204
+ | `AWS_ACCESS_KEY_ID` | AWS access key ID |
205
+ | `AWS_SECRET_ACCESS_KEY` | AWS secret access key |
206
+ | `AWS_ACCOUNT_ID` | AWS account ID |
207
+ | `SUBNET_ID_1` | First subnet ID |
208
+ | `SUBNET_ID_2` | Second subnet ID |
209
+ | `SECURITY_GROUP_ID` | Security group ID |
210
+
211
+ ## Environment Variables
212
+
213
+ Store secrets in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) and reference them in your task definitions. Non-sensitive configuration goes in the `environment` array directly.
214
+
215
+ For a full list of available variables, see [Environment Variables](environment_variables.md).
216
+
217
+ ### Secrets Manager
218
+
219
+ Create the following secrets in AWS Secrets Manager:
220
+
221
+ | Secret Name | Variable | Description |
222
+ |---|---|---|
223
+ | `spree/database-url` | `DATABASE_URL` | PostgreSQL connection URL |
224
+ | `spree/secret-key-base` | `SECRET_KEY_BASE` | Generate with `bin/rails secret` |
225
+
226
+ Optional secrets for email delivery, file storage, and error tracking:
227
+
228
+ | Secret Name | Variable | Description |
229
+ |---|---|---|
230
+ | `spree/smtp-password` | `SMTP_PASSWORD` | SMTP auth password |
231
+ | `spree/sentry-dsn` | `SENTRY_DSN` | Sentry DSN for error tracking |
232
+
233
+ > **TIP:** S3 file storage credentials are not needed as environment variables when your ECS task role has the appropriate S3 permissions. Use IAM roles instead of access keys when possible.
234
+
235
+ ## ECS Task Definitions
236
+
237
+ One task definition runs the whole app — the web server also runs background jobs in-process ([combined mode](quickstart.md#web-and-worker)). Save it as `.aws/web-task-definition.json`. The worker task definition is an optional scale-out step — skip it (and the `deploy-worker` job in the workflow above) until job load deserves its own service.
238
+
239
+ ### Web Service
240
+
241
+ ```json
242
+ {
243
+ "family": "spree-web",
244
+ "networkMode": "awsvpc",
245
+ "requiresCompatibilities": ["FARGATE"],
246
+ "cpu": "1024",
247
+ "memory": "4096",
248
+ "executionRoleArn": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/ecsTaskExecutionRole",
249
+ "taskRoleArn": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/ecsTaskRole",
250
+ "containerDefinitions": [
251
+ {
252
+ "name": "web",
253
+ "image": "${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}",
254
+ "portMappings": [
255
+ {
256
+ "containerPort": 3000,
257
+ "protocol": "tcp"
258
+ }
259
+ ],
260
+ "essential": true,
261
+ "environment": [
262
+ {
263
+ "name": "RAILS_ENV",
264
+ "value": "production"
265
+ },
266
+ {
267
+ "name": "PORT",
268
+ "value": "3000"
269
+ },
270
+ {
271
+ "name": "RAILS_MAX_THREADS",
272
+ "value": "3"
273
+ },
274
+ {
275
+ "name": "WEB_CONCURRENCY",
276
+ "value": "auto"
277
+ },
278
+ {
279
+ "name": "RAILS_LOG_LEVEL",
280
+ "value": "info"
281
+ },
282
+ {
283
+ "name": "AWS_BUCKET",
284
+ "value": "your-spree-bucket"
285
+ }
286
+ ],
287
+ "secrets": [
288
+ {
289
+ "name": "DATABASE_URL",
290
+ "valueFrom": "arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:spree/database-url"
291
+ },
292
+ {
293
+ "name": "SECRET_KEY_BASE",
294
+ "valueFrom": "arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:spree/secret-key-base"
295
+ }
296
+ ],
297
+ "logConfiguration": {
298
+ "logDriver": "awslogs",
299
+ "options": {
300
+ "awslogs-group": "/ecs/spree-web",
301
+ "awslogs-region": "${AWS_REGION}",
302
+ "awslogs-stream-prefix": "ecs"
303
+ }
304
+ },
305
+ "healthCheck": {
306
+ "command": ["CMD-SHELL", "curl -f http://localhost:3000/up || exit 1"],
307
+ "interval": 30,
308
+ "timeout": 5,
309
+ "retries": 3,
310
+ "startPeriod": 60
311
+ }
312
+ }
313
+ ]
314
+ }
315
+ ```
316
+
317
+ ### Worker Service (optional scale-out)
318
+
319
+ Switches to [split mode](quickstart.md#web-and-worker): background jobs move into a dedicated service — same image, no rebuild. When you deploy it, set `SOLID_QUEUE_IN_PUMA=false` in the web task's `environment` so the web container stops running jobs. Save as `.aws/worker-task-definition.json`:
320
+
321
+ ```json
322
+ {
323
+ "family": "spree-worker",
324
+ "networkMode": "awsvpc",
325
+ "requiresCompatibilities": ["FARGATE"],
326
+ "cpu": "512",
327
+ "memory": "2048",
328
+ "executionRoleArn": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/ecsTaskExecutionRole",
329
+ "taskRoleArn": "arn:aws:iam::${AWS_ACCOUNT_ID}:role/ecsTaskRole",
330
+ "containerDefinitions": [
331
+ {
332
+ "name": "worker",
333
+ "image": "${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}",
334
+ "command": ["bin/jobs"],
335
+ "essential": true,
336
+ "environment": [
337
+ {
338
+ "name": "RAILS_ENV",
339
+ "value": "production"
340
+ },
341
+ {
342
+ "name": "RAILS_LOG_LEVEL",
343
+ "value": "info"
344
+ }
345
+ ],
346
+ "secrets": [
347
+ {
348
+ "name": "DATABASE_URL",
349
+ "valueFrom": "arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:spree/database-url"
350
+ },
351
+ {
352
+ "name": "SECRET_KEY_BASE",
353
+ "valueFrom": "arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:spree/secret-key-base"
354
+ }
355
+ ],
356
+ "logConfiguration": {
357
+ "logDriver": "awslogs",
358
+ "options": {
359
+ "awslogs-group": "/ecs/spree-worker",
360
+ "awslogs-region": "${AWS_REGION}",
361
+ "awslogs-stream-prefix": "ecs"
362
+ }
363
+ },
364
+ "healthCheck": {
365
+ "command": ["CMD-SHELL", "pgrep -f solid_queue || exit 1"],
366
+ "interval": 30,
367
+ "timeout": 5,
368
+ "retries": 3,
369
+ "startPeriod": 60
370
+ }
371
+ }
372
+ ]
373
+ }
374
+ ```
375
+
376
+ ## Production Sizing
377
+
378
+ The task definitions above are sized for production. Here is a summary and scaling guidance:
379
+
380
+ | Service | Fargate CPU | Fargate Memory | Instances |
381
+ |---|---|---|---|
382
+ | **Web** | 1 vCPU | 4 GB | 2+ (use ECS Service Auto Scaling) |
383
+ | **Worker** (optional split) | 0.5 vCPU | 2 GB | 1+ |
384
+ | **RDS (PostgreSQL)** | — | `db.r6g.large` (2 vCPU, 16 GB) | 1 |
385
+
386
+ The database also stores background jobs and cache — the sizing above accounts for it. Scaling the web service to multiple instances is safe with in-process jobs: instances coordinate through the database. Add ElastiCache (Valkey or Redis) only for [high-traffic caching](caching.md).
387
+
388
+ ### Auto Scaling
389
+
390
+ Use [ECS Service Auto Scaling](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-auto-scaling.html) to scale the web service based on CPU or memory utilization:
391
+
392
+ ```bash
393
+ # Register a scalable target (min 2, max 6 tasks)
394
+ aws application-autoscaling register-scalable-target \
395
+ --service-namespace ecs \
396
+ --resource-id service/spree-cluster/spree-web \
397
+ --scalable-dimension ecs:service:DesiredCount \
398
+ --min-capacity 2 \
399
+ --max-capacity 6
400
+
401
+ # Scale based on average CPU utilization (target 70%)
402
+ aws application-autoscaling put-scaling-policy \
403
+ --service-namespace ecs \
404
+ --resource-id service/spree-cluster/spree-web \
405
+ --scalable-dimension ecs:service:DesiredCount \
406
+ --policy-name spree-web-cpu-scaling \
407
+ --policy-type TargetTrackingScaling \
408
+ --target-tracking-scaling-policy-configuration '{
409
+ "TargetValue": 70.0,
410
+ "PredefinedMetricSpecification": {
411
+ "PredefinedMetricType": "ECSServiceAverageCPUUtilization"
412
+ },
413
+ "ScaleInCooldown": 300,
414
+ "ScaleOutCooldown": 60
415
+ }'
416
+ ```
417
+
418
+ ## After Deployment
419
+
420
+ ### Admin
421
+
422
+ Access your admin panel at:
423
+
424
+ ```
425
+ https://<your-domain>/admin
426
+ ```
427
+
428
+ Default credentials are created during `db:seed`. Change them immediately after first login.
429
+
430
+ ### Database Migrations
431
+
432
+ The GitHub Actions workflow above runs migrations automatically on deploy. To run migrations manually:
433
+
434
+ ```bash
435
+ aws ecs run-task \
436
+ --cluster spree-cluster \
437
+ --task-definition spree-web \
438
+ --overrides '{
439
+ "containerOverrides": [{
440
+ "name": "web",
441
+ "command": ["bundle", "exec", "rails", "db:migrate"]
442
+ }]
443
+ }' \
444
+ --launch-type FARGATE \
445
+ --network-configuration '{
446
+ "awsvpcConfiguration": {
447
+ "subnets": ["subnet-xxx", "subnet-yyy"],
448
+ "securityGroups": ["sg-xxx"],
449
+ "assignPublicIp": "DISABLED"
450
+ }
451
+ }'
452
+ ```
453
+
454
+ ## Next Steps
455
+
456
+ - [Asset Storage](assets.md) — configure S3 for product images and uploads
457
+ - [Configure CDN](cdn.md) — set up CloudFront for asset delivery
458
+ - [Configure caching](caching.md) — works out of the box; add ElastiCache for high-traffic installs
459
+ - [Set environment variables](environment_variables.md) — SMTP, Sentry, SSL, etc.
460
+ - [Deploy the storefront](../storefront/nextjs/deployment.md) — the Next.js storefront ships separately, typically to Vercel
@@ -0,0 +1,106 @@
1
+ ---
2
+ title: Background Jobs
3
+ description: Spree's background jobs live in your database by default (Solid Queue) — nothing extra to run, with a Sidekiq/Redis swap for very high volume.
4
+ ---
5
+
6
+ Everything that doesn't happen during a request runs as a background job: sending emails, processing images, delivering webhooks, importing and exporting catalogs, releasing expired stock reservations. Spree uses Active Job (Rails' standard job interface), so the queue backend is swappable — but the default needs nothing beyond your database.
7
+
8
+ ## Solid Queue — the default
9
+
10
+ Production uses [Solid Queue](https://github.com/rails/solid_queue) — the built-in background job processor: jobs are stored in your database alongside everything else. There is nothing extra to provision, and by default the job supervisor runs **inside the web container** — one container is the whole application ([combined mode](quickstart.md#web-and-worker)).
11
+
12
+ When job volume grows, switch to split mode: run a dedicated worker from the same image with `bin/jobs`, and set `SOLID_QUEUE_IN_PUMA=false` on the web service. Multiple web instances and multiple workers are safe by default — they coordinate through the database.
13
+
14
+ | Variable | Default | Description |
15
+ |---|---|---|
16
+ | `SOLID_QUEUE_IN_PUMA` | `true` | Run jobs inside the web container; set `false` when a dedicated worker runs |
17
+ | `JOB_THREADS` | `3` | Worker thread count — raise it on a dedicated worker |
18
+ | `JOB_CONCURRENCY` | `1` | Worker process count — the scaling knob for dedicated workers |
19
+
20
+ ## Job Dashboard
21
+
22
+ Every deployment ships [Mission Control](https://github.com/rails/mission_control-jobs) — a web UI for background jobs — at `/jobs`: inspect, retry, and discard jobs from the browser. It's guarded by HTTP Basic auth: set `MISSION_CONTROL_USER` and `MISSION_CONTROL_PASSWORD` in production (without them the dashboard stays locked).
23
+
24
+ ## Recurring Jobs
25
+
26
+ Scheduled work — releasing expired stock reservations, pruning price history, queue housekeeping — is defined in `config/recurring.yml`, Solid Queue's built-in cron. Add your own:
27
+
28
+ ```yaml config/recurring.yml
29
+ production:
30
+ nightly_report:
31
+ class: MyReportJob
32
+ schedule: at 5am every day
33
+ ```
34
+
35
+ ## Queues
36
+
37
+ `config/queue.yml` lists queues in polling order: checkout-critical and operator-facing work first, bulk catalog processing second, housekeeping last. A trailing `"*"` catches any queue added later. Large CSV imports are additionally capped so they can't occupy the whole worker pool — see [`SPREE_IMPORT_JOB_CONCURRENCY`](environment_variables.md).
38
+
39
+ ## Swapping to Sidekiq
40
+
41
+ For very high job volume, Spree works with [Sidekiq](https://sidekiq.org/) on Redis or Valkey — like any Active Job backend. This is a bigger step than the [cache swap](caching.md); here is the full setup.
42
+
43
+ Add the gems and switch the adapter:
44
+
45
+ ```ruby
46
+ # Gemfile
47
+ gem "sidekiq"
48
+ gem "sidekiq-cron" # recurring jobs (replaces config/recurring.yml)
49
+ gem "sentry-sidekiq" # only if you use Sentry
50
+
51
+ # config/application.rb
52
+ config.active_job.queue_adapter = :sidekiq
53
+ ```
54
+
55
+ Configure the queues with weights — Sidekiq's equivalent of Solid Queue's polling order (checkout-critical work first, bulk catalog processing second, housekeeping last):
56
+
57
+ ```yaml config/sidekiq.yml
58
+ :concurrency: <%= Integer(ENV.fetch('SIDEKIQ_CONCURRENCY', '10')) %>
59
+ :queues:
60
+ - [default, 5]
61
+ - [spree_imports, 5]
62
+ - [spree_payment_webhooks, 5]
63
+ - [mailers, 5]
64
+ - [spree_events, 3]
65
+ - [spree_exports, 3]
66
+ - [spree_images, 3]
67
+ - [spree_products, 3]
68
+ - [spree_reports, 3]
69
+ - [spree_variants, 3]
70
+ - [spree_taxons, 3]
71
+ - [spree_stock_location_stock_items, 3]
72
+ - [spree_coupon_codes, 3]
73
+ - [spree_addresses, 3]
74
+ - [spree_gift_cards, 3]
75
+ - [spree_webhooks, 3]
76
+ - [spree_api_keys, 3]
77
+ - [spree_search, 3]
78
+ - [active_storage_transform, 3]
79
+ - [active_storage_analysis, 1]
80
+ - [active_storage_purge, 1]
81
+ ```
82
+
83
+ Move the `config/recurring.yml` schedules to sidekiq-cron (it auto-loads `config/schedule.yml`):
84
+
85
+ ```yaml config/schedule.yml
86
+ expire_stock_reservations:
87
+ cron: "* * * * *"
88
+ class: Spree::StockReservations::ExpireJob
89
+ ```
90
+
91
+ The `/jobs` dashboard is Solid Queue-specific — mount [Sidekiq's Web UI](https://github.com/sidekiq/sidekiq/wiki/Monitoring) instead:
92
+
93
+ ```ruby
94
+ # config/routes.rb
95
+ require "sidekiq/web"
96
+
97
+ Sidekiq::Web.use Rack::Auth::Basic do |user, password|
98
+ ActiveSupport::SecurityUtils.secure_compare(user, ENV["SIDEKIQ_USER"].to_s) &
99
+ ActiveSupport::SecurityUtils.secure_compare(password, ENV["SIDEKIQ_PASSWORD"].to_s)
100
+ end
101
+ mount Sidekiq::Web => "/sidekiq"
102
+ ```
103
+
104
+ Finally: point Sidekiq at your Redis/Valkey via `REDIS_URL`, run `bundle exec sidekiq` as a dedicated worker service (set `RAILS_MAX_THREADS` to at least `SIDEKIQ_CONCURRENCY` there so the database pool covers every worker thread), and remove the `SOLID_QUEUE_IN_PUMA` setting — with Sidekiq, web and worker are always separate processes.
105
+
106
+ Reach for this when a dedicated Solid Queue worker with raised `JOB_THREADS`/`JOB_CONCURRENCY` no longer keeps up — for most stores, that point never arrives.
@@ -1,19 +1,27 @@
1
1
  ---
2
2
  title: Caching
3
- description: Configure Redis-backed caching for your Spree application to speed up database queries and template renders, with tips for cache keys and invalidation.
3
+ description: Spree's cache lives in Postgres by default (Solid Cache) nothing extra to run, with a one-line Redis/Valkey swap for high-traffic installs.
4
4
  ---
5
5
 
6
- Caching improves performance by storing the results of expensive database queries and template renders in memory.
6
+ Caching improves performance by storing the results of expensive computations. Spree's headless API caches sparingly — small memoized values (geo data, settings, taxonomy lookups) and the API's rate-limit counters — so the default store needs no dedicated service.
7
7
 
8
- ## Setting up Redis as Cache Store
8
+ ## Solid Cache the default
9
9
 
10
- Spree comes pre-configured to use Redis for caching in production. Just set the `REDIS_URL` environment variableno gem installation or code changes needed.
10
+ Production uses [Solid Cache](https://github.com/rails/solid_cache) — the built-in cache store: the cache lives in your PostgreSQL database alongside everything else. There is nothing to configure, provision, or monitor it works out of the box in every deployment.
11
11
 
12
- ```bash
13
- REDIS_URL=redis://localhost:6379/0
12
+ ## Swapping to Redis or Valkey
13
+
14
+ For high-traffic installs that want an in-memory cache, the swap is one line in `config/environments/production.rb` plus the gem:
15
+
16
+ ```ruby
17
+ # Gemfile
18
+ gem "redis"
19
+
20
+ # config/environments/production.rb
21
+ config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"] }
14
22
  ```
15
23
 
16
- When `REDIS_URL` is set, Spree automatically uses it for caching. When not set, it falls back to an in-memory cache store.
24
+ Works identically with Redis or Valkey (the Linux Foundation fork most hosting platforms now provision).
17
25
 
18
26
  ## Testing Locally
19
27
 
@@ -30,7 +30,7 @@ DATABASE_URL=sqlite3:db/production.sqlite3
30
30
 
31
31
  ### Using database.yml
32
32
 
33
- You can also configure the database in `config/database.yml`:
33
+ You can also configure the database in `config/database.yml`. The connection pool is sized to cover web request threads plus [background job](background_jobs.md) threads, since jobs run inside the web container by default:
34
34
 
35
35
  **PostgreSQL:**
36
36
 
@@ -38,7 +38,7 @@ You can also configure the database in `config/database.yml`:
38
38
  default: &default
39
39
  adapter: postgresql
40
40
  encoding: unicode
41
- pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
41
+ pool: <%= Integer(ENV.fetch("RAILS_MAX_THREADS", 3)) + Integer(ENV.fetch("JOB_THREADS", 3)) + 3 %>
42
42
 
43
43
  development:
44
44
  <<: *default
@@ -56,7 +56,7 @@ You can also configure the database in `config/database.yml`:
56
56
  default: &default
57
57
  adapter: mysql2
58
58
  encoding: utf8mb4
59
- pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
59
+ pool: <%= Integer(ENV.fetch("RAILS_MAX_THREADS", 3)) + Integer(ENV.fetch("JOB_THREADS", 3)) + 3 %>
60
60
 
61
61
  development:
62
62
  <<: *default
@@ -73,7 +73,7 @@ You can also configure the database in `config/database.yml`:
73
73
  ```yaml
74
74
  default: &default
75
75
  adapter: sqlite3
76
- pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
76
+ pool: <%= Integer(ENV.fetch("RAILS_MAX_THREADS", 3)) + Integer(ENV.fetch("JOB_THREADS", 3)) + 3 %>
77
77
  timeout: 5000
78
78
 
79
79
  development: