Deploy tiny-vercel in 15 minutes·
This is the exact sequence that produced the live demo (https://tiny-vercel-scratch.vercel.app) on
2026-09-15: one script for Cloudflare, one wrangler deploy, one Vercel project. Every command below
was run, not paraphrased — see docs/PROVISIONED.md for what it created.
You need: a Cloudflare account (free plan is enough — D1, KV, Vectorize, R2 and Workers all have free
tiers), a Vercel account, a GitHub account, an OpenAI API key (embeddings + the default model; any
other provider in docs/ENV.md works for chat, embeddings stay on text-embedding-3-small).
0 · Clone and install (1 min)·
git clone https://github.com/cagataycali/tiny-vercel tiny-vercel && cd tiny-vercel
npm ci
npx wrangler login # browser; grants the CLI your Cloudflare account
npx vercel login # browser
1 · Cloudflare storage (2 min)·
Creates, under your account and only with that prefix: 3 KV namespaces (tiny, post, stats), 1 D1
database (36 migrations applied by --migrate), 2 Vectorize indexes (1536-dim cosine, plus the
name/userId metadata indexes the memory queries filter on) and 1 R2 bucket. Writes
apps/worker/wrangler.generated.toml with the real ids (git-ignored) and lists everything in
docs/PROVISIONED.md. Re-running is safe: existing resources are reused. --dry-run prints the
wrangler commands instead. Undo with node scripts/teardown-cloudflare.mjs --yes.
2 · Worker secrets and deploy (2 min)·
cd apps/worker
openssl rand -hex 32 > /tmp/internal-key # keep it: the app needs the same value
npx wrangler secret put INTERNAL_API_KEY --config wrangler.generated.toml < /tmp/internal-key
npx wrangler secret put OPENAI_API_KEY --config wrangler.generated.toml # paste the key
npx wrangler deploy --config wrangler.generated.toml
cd ../..
The deploy prints https://tiny-vercel-worker.<you>.workers.dev. Check it:
curl https://tiny-vercel-worker.<you>.workers.dev/health → {"ok":true,"service":"worker",…,"paymentsEnabled":false}.
Rehearsed: undo and redo (2026-09-15)·
The scripts were proven in BOTH directions on the demo account, twice in a row:
node scripts/teardown-cloudflare.mjs --yes # 9s: worker, 2 vectorize, r2, d1, 3 kv — exactly the 8 prefixed rows; the Vercel project and every non-prefixed resource untouched
node scripts/bootstrap-cloudflare.mjs --prefix tiny-vercel --migrate --app-url https://<app> # 19s, 36 migrations
# secrets + wrangler deploy as in step 2 # 14s
33 s from an empty account to a serving worker. Nothing on the Vercel side changed — the app only knows
the worker by its name-based workers.dev URL, so a rebuilt worker is reachable at the same address
and TINY_WORKER_URL stays valid. What you lose is the data: D1 rows, KV, memories, media (the D1 ids
restart at 1). Vectorize is eventually consistent — a freshly stored memory shows in learnings at once
and in relevant (semantic recall) after ~20 s.
--keep-data deletes only the worker (for a redeploy with fresh bindings). Every wrangler delete's full
output is appended to .cloudflare-teardown.log (git-ignored); if wrangler exits non-zero the script
re-lists the resource and believes Cloudflare, not the exit code.
3 · GitHub OAuth App (2 min, the only step no CLI can do)·
https://github.com/settings/developers → New OAuth App:
- Homepage URL:
https://<your-project>.vercel.app(you can pick the Vercel project name in step 4 — it becomes<name>.vercel.app) - Authorization callback URL:
https://<your-project>.vercel.app/api/auth
Keep the client id and generate a client secret.
4 · Vercel project (5 min)·
Button: click Deploy with Vercel in the README. The form asks for the variables below; the root
directory (apps/web) and build settings come from apps/web/vercel.json.
CLI equivalent (what produced the demo):
npx vercel project add tiny-vercel
npx vercel link --yes --project tiny-vercel
# root directory is a project setting; the CLI has no flag for it — set it once in the dashboard
# (Settings → General → Root Directory → apps/web) or with the API:
# curl -X PATCH https://api.vercel.com/v9/projects/tiny-vercel -H "Authorization: Bearer $VERCEL_TOKEN" \
# -H 'content-type: application/json' -d '{"rootDirectory":"apps/web","framework":"nextjs","nodeVersion":"22.x"}'
add() { printf '%s' "$2" | npx vercel env add "$1" production --force; }
add TINY_WORKER_URL https://tiny-vercel-worker.<you>.workers.dev
add NEXT_PUBLIC_TINY_WORKER_URL https://tiny-vercel-worker.<you>.workers.dev
add INTERNAL_API_KEY "$(cat /tmp/internal-key)"
add AUTH_JWT_SECRET "$(openssl rand -hex 32)"
add ENROLL_SECRET "$(openssl rand -hex 32)"
add GITHUB_CLIENT_ID <from step 3>
add GITHUB_CLIENT_SECRET <from step 3>
add OWNER_LOGIN <your GitHub login>
add NEXT_PUBLIC_SITE_NAME "tiny-vercel"
add TINY_MODEL_PROVIDER openai
add OPENAI_API_KEY <key>
npx vercel deploy --prod --yes
| Variable | Why |
|---|---|
TINY_WORKER_URL, NEXT_PUBLIC_TINY_WORKER_URL |
the worker from step 2 (server-side and browser-side copies, same origin) |
INTERNAL_API_KEY |
must equal the worker secret — every internal worker call is refused otherwise |
AUTH_JWT_SECRET |
signs the tiny_session cookie |
ENROLL_SECRET |
HMAC for device enrollment codes |
GITHUB_CLIENT_ID/SECRET |
first login; passkeys and CLI tokens are issued afterwards |
OWNER_LOGIN |
your GitHub login(s), comma-separated — owner-only routes such as the device UDID roster; unset = nobody is owner (fail closed) |
OPENAI_API_KEY (+ TINY_MODEL_PROVIDER) |
default chat model; users may bring their own keys later |
NEXT_PUBLIC_SITE_NAME |
header, OG cards, manifest |
Everything else in .env.example is optional and documented in docs/ENV.md.
5 · Close the loop (2 min)·
# tell the app its own public origin (WebAuthn rpID, OAuth return, absolute links) and redeploy
printf '%s' https://<your-project>.vercel.app | npx vercel env add NEXT_PUBLIC_APP_URL production --force
npx vercel redeploy <your-project>.vercel.app
# tell the worker where the app lives (share links, firmware pointers) and redeploy it
node scripts/bootstrap-cloudflare.mjs --app-url https://<your-project>.vercel.app --deployed https://tiny-vercel-worker.<you>.workers.dev
(cd apps/worker && npx wrangler deploy --config wrangler.generated.toml)
6 · Verify·
A=https://<your-project>.vercel.app
curl -s $A/api/health # {"ok":true,"service":"web","workerConfigured":true,"appUrlConfigured":true,"paymentsEnabled":false}
curl -s $A/api/events # {"error":"login required"} — 401, fail-closed
curl -s -N -X POST $A/api/chat -H 'content-type: application/json' -H 'x-tiny-name: tiny' -H 'x-tiny-session: s1' \
-d '{"messages":[{"role":"user","content":"Reply with exactly the word: pong"}]}' # SSE … "textDelta":"pong" … [DONE]
Then in a browser: sign in with GitHub → Create your AI → chat → Settings → add a passkey → sign out and
back in with the passkey → Devices → enroll this laptop with npx tiny-vercel init <your origin> then npx tiny-vercel login.
Optional switches·
- Rate limiting — add Vercel KV / Upstash (
KV_REST_API_URL,KV_REST_API_TOKEN); unset = unlimited. - Web Push —
npx web-push generate-vapid-keys; public half to the app (NEXT_PUBLIC_VAPID_KEY), both halves +VAPID_SUBJECTto the worker. - Email in — Cloudflare Email Routing →
EMAIL_OWNER_FORWARDon the worker (docs/ENV.md §email). - Payments —
PAYMENTS_ENABLED=trueon both app and worker plus the keys indocs/ENV.md §payments. Off by default; every money route answers 404 until then.
Troubleshooting·
BLOCKED — the commit author doesn't have permission to create deployments— CLI deploys carry the HEAD commit's author; a team with Git author permission checks refuses authors who are not members (we hit this with a bot identitytiny <tiny@tiny.technology>). Commit as a team member or deploy from a Git connection. The CLI shows the deployment asUNKNOWNand hangs;vercel ls+ the REST/v6/deploymentsendpoint show the real reason.TINY_WORKER_URL not setin build logs /workerConfigured:false— the env var is missing on the environment you deployed to (vercel env ls production).- Every worker call 401 —
INTERNAL_API_KEYdiffers between the app andwrangler secret put. Vectorize: filter on unindexed property— the metadata indexes were not created; re-runbootstrap-cloudflare.mjs(idempotent, adds them).