A South African men's apparel store on Spree 5.6 + Next.js 15. 160 products
live, three markets, live Malaysian courier rates, customer-owned carts.
What is here:
storefront/ the Next.js app
backend/ ONLY the custom Spree code. Spree itself is a prebuilt image
that mounts no app code, so every file here needs an explicit
volume entry in docker-compose.yml or it is silently ignored.
catalogue/ the scripts that loaded and maintain the products
docs/ manifest, mock registry, napkins
Real .env files are committed deliberately. This lives on the house's private
Gitea, and a clone that runs beats a clone that needs a secrets handover.
LANDMINE.md maps every key, what it opens, and what must be rotated before this
repo goes anywhere less private.
The admin API key used to be a literal in six catalogue scripts. They now read
SPREE_ADMIN_KEY from the environment and refuse to run without it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
107 lines
4.3 KiB
YAML
107 lines
4.3 KiB
YAML
# Quick-start: docker compose up
|
|
# Uses prebuilt Spree image — no local build needed.
|
|
# To customize the backend, run: docker compose -f docker-compose.dev.yml up
|
|
x-app: &app
|
|
image: ghcr.io/spree/spree:${SPREE_VERSION_TAG:-latest}
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
# Uncomment when enabling the optional meilisearch service below.
|
|
# meilisearch:
|
|
# condition: service_healthy
|
|
mailpit:
|
|
condition: service_started
|
|
env_file: .env
|
|
environment:
|
|
DATABASE_URL: postgres://postgres@postgres:5432/spree_production
|
|
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
|
|
RAILS_FORCE_SSL: ${RAILS_FORCE_SSL:-false}
|
|
RAILS_ASSUME_SSL: ${RAILS_ASSUME_SSL:-false}
|
|
# Public host for generated URLs (image/attachment URLs in API responses,
|
|
# email links). Override in .env when serving from a real domain or IP.
|
|
RAILS_HOST: ${RAILS_HOST:-localhost:${SPREE_PORT:-3000}}
|
|
# Product search runs on the database by default. To switch to Meilisearch,
|
|
# uncomment this together with the meilisearch depends_on/service/volume
|
|
# blocks, then reindex: docker compose exec web bin/rails spree:search:reindex
|
|
# MEILISEARCH_URL: http://meilisearch:7700
|
|
# All outgoing mail is captured by Mailpit — read it at http://localhost:8025.
|
|
# Set SMTP_HOST (and friends) in .env to deliver through a real provider instead.
|
|
SMTP_HOST: ${SMTP_HOST:-mailpit}
|
|
SMTP_PORT: ${SMTP_PORT:-1025}
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:18-alpine
|
|
environment:
|
|
POSTGRES_HOST_AUTH_METHOD: trust
|
|
PGDATA: /var/lib/postgresql/18/docker
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql
|
|
healthcheck:
|
|
test: pg_isready -U postgres
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
# Optional Meilisearch search provider (typo tolerance, instant facets).
|
|
# Uncomment together with the meilisearch lines under x-app's depends_on and
|
|
# environment, and the meilisearch_data volume at the bottom.
|
|
# meilisearch:
|
|
# image: getmeili/meilisearch:latest
|
|
# volumes:
|
|
# - meilisearch_data:/meili_data
|
|
# ports:
|
|
# # Loopback-only: the app reaches Meilisearch over the compose network;
|
|
# # the host port is for debugging and must not be LAN-reachable (Docker's
|
|
# # NAT rules bypass host firewalls on Linux, and no API key is set).
|
|
# - "127.0.0.1:${SPREE_MEILISEARCH_PORT:-7700}:7700"
|
|
# healthcheck:
|
|
# test: curl -f http://localhost:7700/health || exit 1
|
|
# interval: 5s
|
|
# timeout: 5s
|
|
# retries: 5
|
|
|
|
mailpit:
|
|
image: axllent/mailpit:latest
|
|
ports:
|
|
# Loopback-only, matching meilisearch: reachable from the host browser
|
|
# but not the LAN (Docker's NAT rules bypass host firewalls on Linux).
|
|
# 8025 = web UI — open http://localhost:8025 to read captured mail.
|
|
# 1025 = SMTP endpoint the app delivers to (in-cluster as mailpit:1025).
|
|
- "127.0.0.1:${MAILPIT_UI_PORT:-8025}:8025"
|
|
- "127.0.0.1:${MAILPIT_SMTP_PORT:-1025}:1025"
|
|
environment:
|
|
MP_MAX_MESSAGES: "500"
|
|
MP_SMTP_AUTH_ACCEPT_ANY: "true"
|
|
MP_SMTP_AUTH_ALLOW_INSECURE: "true"
|
|
|
|
# One app container: Puma serves the web AND runs Solid Queue's job
|
|
# supervisor in-process (SOLID_QUEUE_IN_PUMA, on by default). To split job
|
|
# processing out, add a service from the same image with
|
|
# `command: bin/jobs` and set SOLID_QUEUE_IN_PUMA: "false" here.
|
|
web:
|
|
<<: *app
|
|
ports:
|
|
- "${SPREE_PORT:-3000}:3000"
|
|
volumes:
|
|
- storage_data:/rails/storage
|
|
- ./config/initializers/api_body_limit.rb:/rails/config/initializers/api_body_limit.rb:ro
|
|
- ./config/initializers/image_studio.rb:/rails/config/initializers/image_studio.rb:ro
|
|
- ./config/initializers/address_label.rb:/rails/config/initializers/address_label.rb:ro
|
|
- ./config/initializers/customer_preferences.rb:/rails/config/initializers/customer_preferences.rb:ro
|
|
- ./config/initializers/easyparcel_calculator.rb:/rails/config/initializers/easyparcel_calculator.rb:ro
|
|
- ./config/routes.rb:/rails/config/routes.rb:ro
|
|
- ./app/controllers/spree:/rails/app/controllers/spree:ro
|
|
- ./app/views/spree:/rails/app/views/spree:ro
|
|
healthcheck:
|
|
test: curl -f http://localhost:3000/up || exit 1
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 10
|
|
start_period: 30s
|
|
|
|
volumes:
|
|
postgres_data:
|
|
# meilisearch_data:
|
|
storage_data:
|