Skip to main content
Version: 0.4.0 β€” Wine Answers

Configuration & Environments

This page is the single reference for how Sommelier Arena is configured across the three environments you will encounter:

Mode A β€” Local devMode B β€” DockerProduction
FrontendAstro dev server http://localhost:4321nginx container http://localhost:4321Cloudflare Pages (CDN)
PartyKit backendnpx partykit dev ws://localhost:1999PartyKit container (internal, nginx proxied)Cloudflare Workers + Durable Objects
Wine Answers Workernpx wrangler dev http://localhost:1998 (optional)wine-answers container http://localhost:1998Cloudflare Worker
DocumentationDocusaurus dev http://localhost:3002Docusaurus container http://localhost:3002Cloudflare 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
VariableMode A (local)Mode B (Docker)Production
PUBLIC_PARTYKIT_HOSTlocalhost: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_URLhttp://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/docshttps://your-domain/docs

Docker note: In Mode B, PUBLIC_PARTYKIT_HOST is a Docker build arg baked into the Astro static build β€” do not rely on .env.local for Docker builds. Pass it via docker-compose.yml or docker 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 names
  • wrangler secret put or CI secrets β€” production secrets (no .env file)
Storage keyContents
'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 dev or docker-compose down clears all sessions.
  • Production: SQLite-backed. onStart() in back/game.ts reads saved state when the DO wakes from cold storage.

The HOSTS_KV binding has been removed from partykit.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
VariableLocalDockerProduction
DOCS_BASE_URL/ (root)/docs (baked at build time)/docs (Cloudflare Pages base path)

Master config comparison​

All layers in one view:

LayerSettingMode A (local)Mode B (Docker)Production
FrontendPUBLIC_PARTYKIT_HOSTfront/.env.local β†’ localhost:1999Docker build arg β†’ localhost:1999Cloudflare Pages env β†’ <project>.partykit.dev
FrontendPUBLIC_WINE_ANSWERS_URLfront/.env.local β†’ http://localhost:1998Docker build arg β†’ http://localhost:1998Cloudflare Pages env β†’ https://<worker>.workers.dev
FrontendServingAstro dev server :4321nginx container (mapped 4321:4321)Cloudflare Pages CDN
BackendPartyKitnpx partykit dev --port 1999PartyKit container (exposed :1999)Cloudflare Workers (Durable Objects)
BackendDO storageIn-memory (resets on restart)In-memorySQLite (persistent across DO evictions)
BackendHOSTS_KVDisabled in all environments β€” binding removed from partykit.json (CF free plan incompatibility with custom account deploys). Session history is localStorage-only.
Wine AnswersServingnpx wrangler dev :1998 (optional)wine-answers container (mapped 1998:1998)Cloudflare Worker
Wine AnswersWINE_ANSWERS_KVFile-based local KV (.wrangler/state/) β€” seeded via npm run seedFile-based local KV (.wrangler/state/) β€” seeded via npm run seedCloudflare KV namespace
Wine AnswersADMIN_SECRETSet in .dev.vars or envDocker env varwrangler secret put ADMIN_SECRET
DocsServingDocusaurus dev :3002nginx container (mapped 3002:80)Cloudflare Pages (/docs)
DocsDOCS_BASE_URL//docs/docs
Proxy WorkerDOCS_ORIGINN/AN/AInjected via Wrangler --var or Worker env
DeploymentCommandnpx partykit devdocker-compose up --buildgit 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: static in astro.config.mjs) β€” no Node.js server runs at request time. nginx serves these HTML/JS/CSS files.
  • absolute_redirect off keeps Location headers relative (/host/ instead of http://localhost:4321/host/), which is safer for SPA routing in various Docker/proxy setups.
  • try_files $uri $uri/index.html /index.html is the standard SPA pattern: nginx checks for the exact file, then path/index.html, then falls back to index.html β€” no redirect triggered for /host or /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:

  1. There's no server to serve the Astro static build

Alternatives to nginx​

AlternativeTrade-offs
CaddySimpler 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 httpdHeavier; works but nginx:alpine is smaller.
Node.js + http-proxy-middlewareMore 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​

KeyContentsUsed by
sommelier-arena-host-{hostId}SessionListEntry[] β€” host's session historyHost 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_KV binding has been removed from partykit.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​

KeyContents
'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 dev or docker-compose down clears all sessions.
  • Production: SQLite-backed. onStart() in back/game.ts reads 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.html directly with file:// 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​

SymptomCauseFix
http://localhost:3002/docs refuses connectionServe script not runningRun npm run serve:docs
"Your site did not load properly" bannerOpened build/index.html with file://Use npm run serve:docs instead
Session history missing after restartExpected in dev β€” DO storage is ephemeralUse localStorage; no fix needed
Participant can't rejoin after partykit dev restartRejoin tokens in DO storage (in-memory)Expected; restart the session
Session list only shows in one browserKV not available locallyExpected; localStorage is per-browser