Configuration & Environments
This page is the single reference for how Sommelier Arena is configured across the three environments you will encounter:
| Mode A β Local dev | Mode B β Docker | Production | |
|---|---|---|---|
| Frontend | Astro dev server http://localhost:4321 | nginx container http://localhost:4321 | Cloudflare Pages (CDN) |
| PartyKit backend | npx partykit dev ws://localhost:1999 | PartyKit container (internal, nginx proxied) | Cloudflare Workers + Durable Objects |
| Wine Answers Worker | npx wrangler dev http://localhost:1998 (optional) | wine-answers container http://localhost:1998 | Cloudflare Worker |
| Documentation | Docusaurus dev http://localhost:3002 | Docusaurus container http://localhost:3002 | Cloudflare Pages (separate project) |
DO storage (room.storage) | β οΈ In-memory | β οΈ In-memory | β SQLite per DO |
Cloudflare KV (HOSTS_KV) | β Not available | β Not available | β Disabled (see note) |
Cloudflare KV (WINE_ANSWERS_KV) | β Not available (worker uses file-based local KV in .wrangler/state/) | β Not available (worker uses file-based local KV in .wrangler/state/) | β Real KV namespace |
| Browser localStorage | β Works | β Works | β Works |
Environment variablesβ
Frontend (front/)β
Copy front/.env.example β front/.env.local for local development:
cd front && cp .env.example .env.local
| Variable | Mode A (local) | Mode B (Docker) | Production |
|---|---|---|---|
PUBLIC_PARTYKIT_HOST | localhost:1999 (direct to partykit dev) | localhost:1999 (baked at Docker build time; frontend build is configured to connect to PartyKit at host port 1999; nginx serves static files only) | sommelier-arena.<username>.partykit.dev |
PUBLIC_WINE_ANSWERS_URL | http://localhost:1998 (direct to wine-answers dev) | http://localhost:1998 (wine-answers container exposed on host port 1998) | https://<wine-answers-worker>.workers.dev |
PUBLIC_DOCS_URL | (optional) http://localhost:3002/docs | (optional) http://localhost:3002/docs | https://your-domain/docs |
Docker note: In Mode B,
PUBLIC_PARTYKIT_HOSTis a Docker build arg baked into the Astro static build β do not rely on.env.localfor Docker builds. Pass it viadocker-compose.ymlordocker build --build-arg.
Backend & Storageβ
The backend is a PartyKit Durable Object (back/game.ts). PartyKit and Durable Object storage are two sides of the same coin β PartyKit IS the DO runtime, and room.storage IS its built-in SQLite-backed storage. There is no separate database.
Configuration comes from:
partykit.jsonβ KV namespace bindings, DO class nameswrangler secret putor CI secrets β production secrets (no.envfile)
| Storage key | Contents |
|---|---|
'state' | Full SavedState snapshot (restored after DO eviction) |
'hostId' | Host re-authentication token |
'participant:{pseudonym}' | Participant state (score, answers) β keyed by ADJECTIVE-NOUN pseudonym |
'response:{participantId}:{questionId}' | Answer record for accurate scoring |
- Local / Docker: storage is in-memory. Restarting
partykit devordocker-compose downclears all sessions. - Production: SQLite-backed.
onStart()inback/game.tsreads saved state when the DO wakes from cold storage.
The
HOSTS_KVbinding has been removed frompartykit.json(Cloudflare free plan blocks DO creation on custom accounts). Session history is localStorage-only everywhere. See Data Persistence for re-enabling instructions.
Copy docs-site/.env.example β docs-site/.env:
cd docs-site && cp .env.example .env
| Variable | Local | Docker | Production |
|---|---|---|---|
DOCS_BASE_URL | / (root) | /docs (baked at build time) | /docs (Cloudflare Pages base path) |
Master config comparisonβ
All layers in one view:
| Layer | Setting | Mode A (local) | Mode B (Docker) | Production |
|---|---|---|---|---|
| Frontend | PUBLIC_PARTYKIT_HOST | front/.env.local β localhost:1999 | Docker build arg β localhost:1999 | Cloudflare Pages env β <project>.partykit.dev |
| Frontend | PUBLIC_WINE_ANSWERS_URL | front/.env.local β http://localhost:1998 | Docker build arg β http://localhost:1998 | Cloudflare Pages env β https://<worker>.workers.dev |
| Frontend | Serving | Astro dev server :4321 | nginx container (mapped 4321:4321) | Cloudflare Pages CDN |
| Backend | PartyKit | npx partykit dev --port 1999 | PartyKit container (exposed :1999) | Cloudflare Workers (Durable Objects) |
| Backend | DO storage | In-memory (resets on restart) | In-memory | SQLite (persistent across DO evictions) |
| Backend | HOSTS_KV | Disabled in all environments β binding removed from partykit.json (CF free plan incompatibility with custom account deploys). Session history is localStorage-only. | ||
| Wine Answers | Serving | npx wrangler dev :1998 (optional) | wine-answers container (mapped 1998:1998) | Cloudflare Worker |
| Wine Answers | WINE_ANSWERS_KV | File-based local KV (.wrangler/state/) β seeded via npm run seed | File-based local KV (.wrangler/state/) β seeded via npm run seed | Cloudflare KV namespace |
| Wine Answers | ADMIN_SECRET | Set in .dev.vars or env | Docker env var | wrangler secret put ADMIN_SECRET |
| Docs | Serving | Docusaurus dev :3002 | nginx container (mapped 3002:80) | Cloudflare Pages (/docs) |
| Docs | DOCS_BASE_URL | / | /docs | /docs |
| Proxy Worker | DOCS_ORIGIN | N/A | N/A | Injected via Wrangler --var or Worker env |
| Deployment | Command | npx partykit dev | docker-compose up --build | git push (Pages) + npx partykit deploy + npx wrangler deploy |
nginx.conf explainedβ
The Docker front container uses nginx as a static file server. Here's what front/nginx.conf does and why:
What it doesβ
# 1. Listen on container port 4321 (Docker maps host port 4321 β container 4321)
listen 4321;
# 2. Use relative Location headers for safer SPA routing
absolute_redirect off;
# 3. SPA routing: serve index.html for any unknown path (/host, /play, ...)
location / {
try_files $uri $uri/index.html /index.html;
}
Why nginx:
- Astro builds to pure static files (
output: staticinastro.config.mjs) β no Node.js server runs at request time. nginx serves these HTML/JS/CSS files. absolute_redirect offkeeps Location headers relative (/host/instead ofhttp://localhost:4321/host/), which is safer for SPA routing in various Docker/proxy setups.try_files $uri $uri/index.html /index.htmlis the standard SPA pattern: nginx checks for the exact file, thenpath/index.html, then falls back toindex.htmlβ no redirect triggered for/hostor/play.
WebSocket traffic goes directly from the browser to localhost:1999 (the back container exposed on host port 1999). nginx does not proxy WebSocket connections β no /parties/ location block needed.
Why we need it for E2E testsβ
The Playwright E2E tests run against the Docker stack (Mode B). Without nginx:
- There's no server to serve the Astro static build
Alternatives to nginxβ
| Alternative | Trade-offs |
|---|---|
| Caddy | Simpler config syntax, auto-HTTPS; same capabilities. Swap nginx:alpine for caddy:alpine and replace nginx.conf with a Caddyfile. |
| Mode A (no Docker) | npm run dev in front/ uses Astro's built-in dev server on :4321. SPA routing works natively. No nginx needed. This is the recommended approach for daily development. |
| Apache httpd | Heavier; works but nginx:alpine is smaller. |
| Node.js + http-proxy-middleware | More complex Docker setup; not production-like. |
For local development (Mode A), you never need nginx. nginx is only required for the production-like Docker build used in E2E tests (Mode B).
Storage layersβ
1. Browser localStorage β always availableβ
| Key | Contents | Used by |
|---|---|---|
sommelier-arena-host-{hostId} | SessionListEntry[] β host's session history | Host Dashboard |
sommelier-arena-rejoin | { id, code } β participant rejoin credential (pseudonym + session code) | Participant rejoin |
Works identically in all environments. Managed by front/src/lib/sessionStorage.ts.
β οΈ Session history note: The
HOSTS_KVbinding has been removed frompartykit.json(Cloudflare free plan blocks DO creation on custom accounts; PartyKit does not support the required migration config). Session history is localStorage-only in all environments β local dev, Docker, and production. Use the π button in the Host Dashboard to clean up stale sessions.
2. Durable Object storage (room.storage) β in-memory locallyβ
| Key | Contents |
|---|---|
'state' | Full SavedState snapshot (restored after DO eviction) |
'hostId' | Host re-authentication token |
'participant:{pseudonym}' | Participant state (score, answers) β keyed by their ADJECTIVE-NOUN pseudonym |
'response:{participantId}:{questionId}' | Answer record for accurate scoring |
- Local / Docker: In-memory. Restarting
partykit devordocker-compose downclears all sessions. - Production: SQLite-backed.
onStart()inback/game.tsreads saved state when the DO wakes from cold storage.
3. Cloudflare KV (WINE_ANSWERS_KV) β production onlyβ
Curated answer suggestions for the session creation form. Managed by the Wine Answers Worker. See Data Persistence for the full KV schema.
Testing & Preview (docs site)β
Docusaurus local preview matching productionβ
Production serves docs at /docs (via the Proxy Worker). To preview with the same path locally:
cd docs-site
npm run build:local # builds with DOCS_BASE_URL=/docs
npm run serve:docs # serves at http://localhost:3002/docs
Do not open
build/index.htmldirectly withfile://when the build used/docsβ asset paths are absolute and the browser won't find them.
Preview at root (simpler)β
cd docs-site
npm run build # builds with DOCS_BASE_URL=/
npx docusaurus serve # serves at http://localhost:3000/
Troubleshootingβ
| Symptom | Cause | Fix |
|---|---|---|
http://localhost:3002/docs refuses connection | Serve script not running | Run npm run serve:docs |
| "Your site did not load properly" banner | Opened build/index.html with file:// | Use npm run serve:docs instead |
| Session history missing after restart | Expected in dev β DO storage is ephemeral | Use localStorage; no fix needed |
Participant can't rejoin after partykit dev restart | Rejoin tokens in DO storage (in-memory) | Expected; restart the session |
| Session list only shows in one browser | KV not available locally | Expected; localStorage is per-browser |