World's first sealed Postgres
The Cellar
The Cellar is a sealed database for your app. You get a connection string, REST, live updates, and an import hatch. Paste a phpMyAdmin SQL dump (or CSV / JSON) and Zipper turns those tables into chambers. Browse and edit rows in Studio. Mint, revoke, and regenerate capability seals. Encryption and row rules stay on. SQL is Zipper’s dialect — it never hits the host Postgres parser. It is not Amazon RDS, not stock MySQL, and not a second Supabase. Live updates run on this Zipper process, not a world bus. Included on every Zipper plan.
Cloudflare analogue: Neon / Supabase Postgres
What it is
The Cellar is a sealed database for your app. You get a connection string, REST, live updates, and an import hatch. Paste a phpMyAdmin SQL dump (or CSV / JSON) and Zipper turns those tables into chambers. Browse and edit rows in Studio. Mint, revoke, and regenerate capability seals. Encryption and row rules stay on. SQL is Zipper’s dialect — it never hits the host Postgres parser. It is not Amazon RDS, not stock MySQL, and not a second Supabase. Live updates run on this Zipper process, not a world bus.
Closest analogue: Neon / Supabase Postgres. SLA 99.95% · p99 35ms. Admins create cellars, branches, seals, RPC, and snapshots. Operators may run select seals.
- ▸Hatch: capability seal, fail-closed Origin
- ▸Climate: AES-256-GCM and RLS always on
- ▸Import phpMyAdmin SQL / CSV / JSON
- ▸Studio: browse, insert, edit, delete rows
- ▸Mint · revoke · regenerate seals
- ▸SQL, REST, live hatch, and agent setup
- ▸Email/password seals for your users
How it works
The Cellar is your app database under Zipper. You get a connection string, REST, live updates, and POST /import for phpMyAdmin SQL (or CSV / JSON). Tables become chambers. Encryption and row rules stay on. SQL is Zipper’s dialect — it never hits the host Postgres parser. The hatch is a capability seal. Browser Origin is fail-closed (never *). It is not Amazon RDS, not stock MySQL, and not a second Supabase.
The climate cannot be turned off: AES-256-GCM around every row, default-deny RLS on every table (UPDATE/DELETE also need SELECT), fail-closed seals, overlay branches. Statement time, payload, and per-table inflight caps keep the rooms cool and stable.
The public `/demo` pad is a tasting room — SELECT only, per-IP, expires. Writes live behind a service or 1h user seal. Preview branches are overlays: forking copies nothing. Snapshots copy the resolved overlay onto an independent branch so later main writes cannot leak in.
Use cases
Concrete ways teams use this service on day one.
Paste a DATABASE_URL
An app today stores DATABASE_URL=postgres://… for Neon or Supabase.
- Dashboard → The Cellar → Open a cellar named app. Copy the bootstrap DATABASE_URL once (the password is the seal).
- Use psql, node-postgres, or Prisma against db.tinyzipper.com:5432 (sslmode=require). Or POST HTTPS /wire and /query.
- SQL is parameterized. Stacked statements, comments, and pg_catalog identifiers are refused.
PostgREST / supabase-js Data API
A client calls GET /rest/v1/orders?status=eq.open with an anon key.
- Prefer the control-plane hatch https://tinyzipper.com/api/v1/cellar/{id} — REST lives at /rest/{table}. Vanity https://app--workspace.cellar.tinyzipper.com needs *.cellar TLS; on that host /rest/v1 rewrites to /rest.
- apikey and Authorization are an anon or user seal — never the service seal in a browser or mobile app.
- GET/POST/PATCH/DELETE with PostgREST filters, or=(a.eq.b,c.eq.d), embeds like select=id,items(sku), and Prefer: resolution=merge-duplicates. CSV insert is accepted.
- Mint a 1h authenticated seal: POST /auth/v1/signup with email/password, or /auth/v1/token, or a BFF POST /auth/v1/token with the service seal and { "user_id" }. Recovery tokens are minted by a service seal — Zipper does not send mail. The hatch Origin is fail-closed; native clients omit Origin.
- Watch a chamber: GET /live/orders?listen=1 on the control plane (vanity: /live/v1/orders). The first climate event is the cool snapshot this seal can see. Writes speak write; a committed BEGIN speaks hitch. Zipper process — not a global bus.
Chat room + climate hatch
A small team chat with email/password and a live chamber.
- Sign up: POST /auth/v1/signup { "email", "password" }. Store the access_token; it is the user seal.
- INSERT INTO rooms, then INSERT INTO room_members (self-join). INSERT INTO messages. Membership RLS hides other rooms.
- GET /live/v1/messages — cool snapshot. GET /live/v1/messages?listen=1 — climate. POST occupancy or whisper on that chamber. This is one Zipper process, not a global bus.
Feed an agent the hatch
You paste CELLAR_URL and a schema seal into Cursor (or any agent) and ask it to create a todos chamber.
- Mint a service seal with schema. Copy the dashboard handoff (CELLAR_URL + CELLAR_SEAL). Do not commit the seal.
- The agent GETs /agent (or /llms.txt) then PUT /setup { "chambers": [{ "name": "todos", "columns": [...], "owner": "auth.uid" }] }.
- Users sign in at /auth/v1/signup. Live hatch is GET /live/v1/todos. This is Zipper’s climate, not a Supabase MCP clone.
Import a phpMyAdmin dump
You already have a MySQL database. You exported it from phpMyAdmin and want it in The Cellar.
- In phpMyAdmin: Export → SQL (or CSV / JSON). Copy the file. Do not expect views, triggers, or stored procedures to come along.
- Dashboard → The Cellar → Import. A dialogue asks whether imported emails/phones must set a new password, and an optional HTTPS redirect for your app’s reset page. Or POST /import?reset_logins=1&redirect_to=https://app.example.com/reset.
- Someone signing in with an imported email or phone gets password_reset_required with recovery_token and redirect_to. Zipper does not send mail. Open redirect_to (or show a form), then POST /auth/v1/token with that token and a new password.
Preview data like a git branch
A feature branch should mutate orders without touching production rows.
- POST /api/v1/cellar/{id}/branches with name preview. Only changed rows are stored on the overlay.
- Query with options=branch=preview on DATABASE_URL, or { "branch": "preview" } on /query.
- Snapshot the overlay (resolved copy on an independent branch), or drop the preview branch. Production main is untouched.
Set it up in the dashboard
Dashboard → Services → The Cellar.
Copy DATABASE_URL, the climate hatch, and the agent handoff after minting a seal. Run SQL in the console. Fork overlay branches.
Encryption, RLS, overlay branches, and fail-closed seals stay on. They are the climate of the cellar.
API
Control-plane: GET|POST /api/v1/cellar · POST /query · /wire · /rest/:table · /rpc/:name · /auth/:action · GET|PUT /live/:chamber · GET /agent · PUT /setup · POST /import · GET /export · DATABASE_URL db.tinyzipper.com:5432. Send Authorization: Bearer tz_live_YOUR_TOKEN.
Creates count against the plan quota. A 402 plan_limit means you are at the cap — upgrade or delete an unused resource.
Create a cellar
curl -sS -X POST https://tinyzipper.com/api/v1/cellar \
-H "Authorization: Bearer tz_live_YOUR_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"app","region":"af"}'DATABASE_URL (wire adapter)
psql "postgresql://service:zc_YOUR_SEAL@db.tinyzipper.com:5432/app?sslmode=require"
PostgREST
curl -sS "https://tinyzipper.com/api/v1/cellar/CELLAR_ID/rest/orders?status=eq.open" \ -H "apikey: zc_YOUR_SEAL" \ -H "Authorization: Bearer zc_YOUR_SEAL"
Sealed query
curl -sS -X POST https://tinyzipper.com/api/v1/cellar/CELLAR_ID/query \
-H "Authorization: Bearer zc_YOUR_SEAL" \
-H "Content-Type: application/json" \
-d '{"sql":"SELECT id, status FROM orders WHERE user_id = $1","params":["user_a"],"branch":"main"}'Mint a user session (BFF)
curl -sS -X POST https://tinyzipper.com/api/v1/cellar/CELLAR_ID/session \
-H "Authorization: Bearer zc_SERVICE_SEAL" \
-H "Content-Type: application/json" \
-d '{"user_id":"user_a"}'Climate hatch
const cellar = openHatch('https://app--workspace.cellar.tinyzipper.com', userSeal)
const messages = cellar.chamber('messages')
await messages.snapshot() // cool docs this seal can see
messages.watch().addEventListener('hitch', (ev) => console.log(JSON.parse(ev.data)))
await messages.put('m2', { body: 'stored cool', room_id: 'r1' })Import phpMyAdmin SQL
curl -sS -X POST "$CELLAR_URL/import?reset_logins=1&redirect_to=https://app.example.com/reset" \ -H "Authorization: Bearer $CELLAR_SEAL" \ --data-binary @wordpress.sql
Agent setup
curl -sS "$CELLAR_URL/agent" -H "Authorization: Bearer $CELLAR_SEAL"
curl -sS -X PUT "$CELLAR_URL/setup" \
-H "Authorization: Bearer $CELLAR_SEAL" -H "Content-Type: application/json" \
-d '{"intent":"todos","chambers":[{"name":"todos","owner":"auth.uid","columns":[{"name":"id","type":"text","pk":true},{"name":"user_id","type":"text","nullable":false},{"name":"title","type":"text"}]}]}'Public sandbox
const res = await fetch("https://tinyzipper.com/api/v1/cellar/demo", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ZIPPER_TOKEN}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID()
},
body: JSON.stringify({ sql: "SELECT id, total FROM orders", branch: "preview" })
});
const json = await res.json();
if (!res.ok) throw new Error(json.error ?? res.statusText);
console.log(json);List the same resource in JavaScript
const res = await fetch("https://tinyzipper.com/api/v1/cellar", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.ZIPPER_TOKEN}`,
"Content-Type": "application/json"
}
});
const json = await res.json();
if (!res.ok) throw new Error(json.error ?? res.statusText);
console.log(json);Tips
The Cellar is not Amazon RDS. DATABASE_URL speaks Zipper’s sealed-query wire adapter, not stock Postgres on :5432 of the host.
Never log seal secrets. Rotate by minting a new seal and deleting the old prefix.
Do not orange-cloud db, pool, or *.cellar. DATABASE_URL is PGWire, not HTTP. REST sends CDN-Cache-Control: no-store.
The public demo cannot write without Authorization: Bearer demo-authenticated. Do not send a service seal from the browser. Mint a user seal in your BFF and keep the hatch origins explicit.
Browsers need Origin on SITE_URL, localhost, or CELLAR_ALLOWED_ORIGINS. curl, psql, and native apps omit Origin and enter with the seal.
Two JOINs nest as child arrays (PostgREST embed). Unqualified JOIN keeps unmatched parents; INNER JOIN drops them; LEFT JOIN keeps empty arrays. GROUP BY / HAVING is allowed without JOIN. BEGIN; …; COMMIT is a sealed script of at most eight statements, or separate statements with X-Cellar-Tx (PGWire sends it per socket). Stacked SQL outside a transaction is still refused. The live overlay expires in 60 seconds; hitch and webhooks fire on COMMIT; drafts stay on that hatch. The mutation journal is not stock Postgres WAL PITR. Climate is SSE on Zipper, not a global bus.
- ▸SOC 2
- ▸AES-256-GCM
- ▸Default-deny RLS
- ▸Sealed dialect
Runbook
Open a cellar. Copy DATABASE_URL (the password is the seal). Sign users in at /auth/v1/signup. Paste a phpMyAdmin SQL dump to POST /import — tables become chambers. Query with psql, REST, or POST /query. Live data is GET /live/v1/{table}. Encryption and row rules stay on. Tenant SQL never reaches host Postgres.
Next: The Armory · All docs · Create a free account