Back to Spree

Deploying the dashboard

docs/developer/dashboard/deployment.mdx

5.6.05.0 KB
Original Source

The dashboard deploys as static files. vite build produces dist/ — HTML, JS, CSS — there is no Node server in production. What varies is who serves those files.

<Warning> `VITE_`-prefixed values are compiled into the client bundle — never put credentials in them. The dashboard authenticates admins interactively (JWT in memory + an httpOnly refresh cookie); no API keys are involved. </Warning>

Single node — the default

The Spree server serves the dashboard itself at /dashboard: one container, one origin, zero CORS or cookie configuration. The official Spree Docker image ships with the stock dashboard baked in — deploy the image anywhere Docker runs (Render, Railway, AWS, Azure, your own host) and https://your-api-host/dashboard just works.

Under the hood, the spree_dashboard gem (in the Gemfile of the starter and the official image) serves the directory in Spree::Dashboard.dist_path — or the SPREE_DASHBOARD_DIST_PATH env var — with SPA semantics: every dashboard route falls back to index.html, hashed assets get immutable caching. The bundle is built with a relative API base (VITE_SPREE_API_URL unset) and base: /dashboard/, so the same image works on every host and environment with no rebuild. API-only deployments simply omit the gem.

Custom dashboard, single node

The official image contains the stock dashboard. Once you've customized yours (your own pages/plugins in apps/dashboard/), bake your build into your own image:

bash
spree build --production            # optionally --tag registry/repo:tag

The Dockerfile normalizes its own build context: built from the project root it detects the layout (backend/ = the Rails app, apps/dashboard/ = your dashboard) and builds your dashboard into the image — no flags, no named contexts. Without the CLI, the equivalent is:

bash
docker build . -f backend/Dockerfile -t my-shop-spree:latest

Inside the dashboard stage the build runs with VITE_BASE_PATH=/dashboard/ (asset URLs resolve under the mount) and VITE_SPREE_API_URL unset (API calls stay origin-relative); the Node toolchain lives only in that throw-away stage. Projects without apps/dashboard/ get the stock dashboard baked instead — and if your backend/Dockerfile predates this layout support, the command warns instead of silently shipping the stock dashboard over your customized one (update it from the spree-starter template).

Static host / CDN — the alternative

For edge-served assets or an independent dashboard release cadence, deploy dist/ to a static host on its own domain:

bash
cd apps/dashboard
VITE_SPREE_API_URL=https://api.mystore.com pnpm build

This is the cross-origin topology, with three requirements:

  1. HTTPS on both sides. The refresh cookie is issued with SameSite=None; Secure in production, and browsers drop Secure cookies over HTTP. There is no insecure-production mode.
  2. Allowed Origins. Add the dashboard's URL (e.g. https://dashboard.mystore.com) to Settings → Allowed Origins in the Spree admin — the API rejects cross-origin requests until then.
  3. SPA fallback. Every path must rewrite to index.html so the router owns the URL (/* → /index.html on your host: _redirects on Netlify, vercel.json rewrites, try_files $uri /index.html; on nginx).

A middle ground also works: serve dist/ and proxy /api/* + /rails/* to Rails from one domain behind your own nginx/Caddy/ingress — same-origin simplicity without baking the bundle into the image.

Render

The Blueprint that create-spree-app places at the project root deploys the backend as a Docker service built from your repo — the same Dockerfile as everywhere else, with the repo root as context:

yaml
runtime: docker
dockerfilePath: ./backend/Dockerfile
dockerContext: .

That's the whole setup — one service, one origin, your customized dashboard (it builds from your apps/dashboard, not the stock bundle) at https://your-service.onrender.com/dashboard. No VITE_SPREE_API_URL, no Allowed Origins entry, no extra service, no build commands to maintain — migrations run from the image entrypoint on boot. Redeploys rebuild on every push, and what Render builds is byte-for-byte what spree build --production builds locally.

If you prefer the dashboard on Render's CDN instead, add a static-site service by hand using the cross-origin recipe above.

Any other static host

The same three ingredients, by hand: build with VITE_SPREE_API_URL set, upload dist/, configure the /* → /index.html rewrite. Then add the origin to Allowed Origins.

Checklist (cross-origin only)

Single-node deployments skip all of this — one origin needs none of it.

  • VITE_SPREE_API_URL points at the production API (baked at build time — one build per environment)
  • HTTPS on the dashboard and the API
  • Dashboard origin added to Settings → Allowed Origins
  • All paths rewrite to index.html
  • No secrets in any VITE_ variable