> Auth update: production now requires Supabase Auth configuration; see SUPABASE_AUTH.md. The original local password implementation is restricted to isolated test fixtures. Set SUPABASE_URL and SUPABASE_PUBLISHABLE_KEY before deploying.

Digital Walls backend operations

Production incident notes and “why we did X” live in the running log [PRODUCTION_LEARNINGS.md](./PRODUCTION_LEARNINGS.md) (start there after outages).

Run

Use Node 24 LTS or later. npm ci, then npm start. Open http://127.0.0.1:4173. The server applies versioned SQL migrations and creates data/walls.sqlite. This is a real persistent database, not a fixture or browser-storage substitute. Sign up with an email and a password of at least 12 characters. New accounts start with an empty wall list. Create a wall to start editing.

npm test exercises the real HTTP server and a disposable database. npm run test:e2e exercises Chrome against a fresh temporary database, removed after each run. npm run dev:e2e is a manual test server using data/e2e.sqlite; do not use that database as production data.

Deployment contract

Local and Docker deployments use one long-running Node server with a durable local disk. SQLite WAL supports concurrent requests within that server; transaction/revision checks prevent lost updates. Vercel does not have a durable disk, so account walls there live in Supabase Postgres. Connecting the Supabase integration provides POSTGRES_URL; the app uses that as the session pooler. DATABASE_URL overrides it. The server creates the tables on boot. Signup, sign-in, and share links then use that database. Without either URL, Vercel still serves guest mode, and account routes return 503 instead of writing accounts into /tmp.

Vercel

The repo root server.js is the Node entry Vercel should run. Browser assets live in public/ (including wall.js) so Vercel can CDN them and does not mistake client code for the backend. The self-host Docker image lives at docker/Dockerfile on purpose: a root-level Dockerfile can make Vercel try to build and push a Container Registry image named dockerfile, which fails when VCR push is not enabled for the project.

If a deploy fails with vcr.vercel.com/.../dockerfile:<sha> was denied, either fix registry access in the Vercel project (Sandboxes → Container Registry) or confirm the project is not configured to deploy a container image from the repo. Standard production deploys for this app use server.js plus Supabase Postgres, not VCR.

In Project Settings → Build & Development, leave Output Directory empty. A static output path (for example dist or public) makes Vercel skip the Node entry and every route returns platform 404 NOT_FOUND. The repo vercel.json serves public/ from the CDN first (filesystem), then falls back to server.js for /api/* and HTML that needs routing. The Node handler also serves public/` locally (Docker) without touching Postgres.

On first deploy to Supabase Postgres, many serverless instances can start at once. The app uses a try advisory lock + poll so only one instance applies SQL migrations; the rest wait for schema_migrations instead of blocking until statement timeout. Static .js / .css responses are served before session lookup so a slow database cannot turn asset requests into 500s.

On Vercel the app:

Set APP_ORIGIN to the HTTPS origin people open (for example https://digitalwalls.vercel.app). Signup also accepts the host the browser actually called, so a Vercel deployment URL still works when APP_ORIGIN is the production domain. Share links keep using APP_ORIGIN. In Supabase Auth → URL Configuration, allow https://YOUR_DOMAIN/?auth=recovery and https://YOUR_DOMAIN/?auth=confirm. Guest drafts stay in the browser until an account claims them. Postgres backups are Supabase's; the SQLite backup command below is only for the file database.

Build with docker build -f docker/Dockerfile ., mount a persistent volume at /data, and set APP_ORIGIN to the final HTTPS origin (no trailing slash). Terminate TLS with the host's reverse proxy and proxy all paths to port 4173. Keep NODE_ENV=production; startup refuses production without an HTTPS origin. Only expose the proxy publicly. Production cookies are HttpOnly, Secure, SameSite=Lax, and host-bound; development cookies work over localhost HTTP. Sessions expire after 30 days and are revoked on logout. Passwords use salted scrypt; session tokens are stored only as SHA-256 hashes. The database contains sensitive account and wall data: limit volume access and enable encrypted disk/backups on the host.

Set the final domain before distributing NFC stickers. Share tokens stay stable through editing, renaming, privacy changes, and sessions; a domain change still requires redirecting the previous domain. NFC uses an ordinary NDEF URL record containing the share URL. Physical NFC writing is outside the browser flow.

Example environment is in .env.example; inject it through your host (it is not automatically loaded). Restrict inbound request sizes to 10 MB at the proxy. Preserve the Origin header. App-level authentication rate limits use the socket IP and hashed email; add per-client edge rate limiting at the reverse proxy, because the app intentionally does not trust arbitrary forwarded-IP headers.

Permissions and data

Backups and recovery

Run DATABASE_PATH=/data/walls.sqlite node server/backup.js /secure/backup/walls-YYYY-MM-DD.sqlite daily via the host scheduler. The SQLite online backup API makes a consistent snapshot while the app runs, including images. Transfer encrypted backups off-host and retain a suitable recovery window. Never simply copy the open main SQLite file without its WAL.

To restore: stop the server; back up the current data directory; restore the snapshot as walls.sqlite into a clean data directory owned by the server user; start the server; verify /api/health, account login, a private wall, and a shared image. Restoring a backup also restores sessions from that point; deleting all rows from sessions invalidates them if needed. Rehearse restoration before accepting production traffic.

Release status / remaining operational decisions

A production host, HTTPS domain, durable volume, and scheduled off-host backups must be provisioned before live use. This checkout does not have hosting credentials, and no production deployment has been claimed. The app includes no email delivery integration, email verification or password recovery flow yet; select a transactional email provider before a broader public launch. The requested signup/login/logout/session flows work without email delivery. Do not describe this as a managed-auth service.