SETUP
One command. Then sell.
Two targets, same codebase. Pick one. Deploy creates the database, runs migrations, and mints secrets — nothing manual, nothing hardcoded. Agents: the machine-readable version of this page is llms.txt.
Path A — Cloudflare (zero ops)
Worker + D1 + R2 in your account. Free tier covers a small shop. You can also start from the one-click installer.
That one command resolves the D1 database by name (creating it on first run), applies all migrations, mints BETTER_AUTH_SECRET iff missing, and deploys. Payment provider keys are optional — without them the shop runs on manual payments and the Pay buttons simply stay hidden.
Path B — your own box via celld (full control)
A Durable-Objects node on your hardware; state lives as SQLite cells inside your S3 bucket. Needs an S3 bucket with conditional writes (R2, AWS S3, Tigris — not B2/MinIO/Spaces) and celld ≥ 0.6.2 on the box.
Set CELLD_SSH + CELLD_REMOTE_CONFIG and the migrate step runs itself after every deploy. Supervise with Docker (--restart unless-stopped --stop-timeout 60); expose with any reverse proxy or tunnel pointing at the node's port.
First login
Every fresh database boots with one admin: admin / admin123. The first login forces a password change before anything else loads. There is no seed data — add products and payment modes in admin → items. Until payment modes exist, the till can't complete sales.
Updates
Migrations are additive and idempotent — data is never touched. Pull and redeploy on either path; the deploy step migrates first.
Troubleshooting
Deploy says “expected a D1 ID”
Old flow. pnpm run deploy resolves the database by name now — update and retry.
Local dev reads an empty/wrong DB
cf dev reads ./.wrangler/state but bare cf d1 --local defaults to the global state dir. Always migrate with pnpm db:migrate (pins --persist-to).
celld health hangs before first deploy
Normal on 0.6.x — a node with no deployment answers nothing, not even health. Deploy first, then gate.
celld d1 talks to a dead node
Fleet leases outlive dead nodes and loopback addresses don't route remotely. Run migrations on the box; one node per bucket prefix, always.
Worker throws about #impl on celld
Fixed in-repo: the entry exports a plain { fetch } object, never a class instance. Unbound dispatch breaks private fields.
Secrets vs plain vars
Publishable IDs (STRIPE_PUBLISHABLE_KEY, RAZORPAY_KEY_ID) are plain vars — they're client-side public. Only true secrets go in secrets. .dev.vars and .celld.env are gitignored; verify with git check-ignore before committing.
For agents
Start at llms.txt (repo laws, payments model, both deploy paths, conventions). Key facts: cf CLI only, never bare wrangler; single object params outside src/lib/data; Tailwind standard scale; dev ports 4871 (app) / 4873 (docs).