Sub-skills

Deploy in depth

For your agentCopy this and give it to the AI you're building with.
Run `cubek skill`, then `cubek skill deploy`, read all of both and follow them exactly (no CLI: download https://cubek.dev/SKILL.md and https://cubek.dev/skills/deploy.md verbatim with curl; don't summarize them).
Run `cubek check` after every edit until it is clean.

Previews, production, rollback, domains, secrets, logs, metrics and data recovery. Every command takes --json and --help.

Sign in (once per machine)

cubek login                                      # a human, in the browser (default https://api.cubek.dev)
cubek login --token T                            # headless: a personal token (cubek tokens create ci)
cubek whoami                                     # on any 401/403: user, organization, role

CI can set CUBEK_URL and CUBEK_KEY instead. A 451 (an app or a command) means the account is suspended: nothing to fix in the code; tell the user to contact hello@cubek.dev (the data is kept).

Previews and production

cubek deploy                  # r12: https://<name>--r12.cubek.app, a preview with a copy of production's data (taken now)
cubek deploy --db prod        # a preview on production's database itself: real data
cubek promote 12              # the preview you tried becomes https://<name>.cubek.app, unchanged
cubek deploy --prod -m "add invoices"   # builds a NEW revision (r13) and makes it production
cubek revisions               # every revision, * = production
cubek diff                    # local files (A/M/D) and schema migration vs production (--rev N: a revision)
cubek deploy --prod --dry-run # what would upload and migrate (additive/destructive); deploys nothing
cubek rollback 11             # code back to r11, database kept
cubek rollback 11 --with-db   # code and database back (the current db is snapshotted first)
cubek preview rm 12           # end a preview now (its URL answers 410)
cubek preview url 12          # a 1-hour link that opens the private preview (give it to the user)
curl -H "Authorization: Bearer $(cubek tokens print)" https://<name>--r12.cubek.app/api/x
  • Previews are private: only members of the project's organization open them (a browser signs in to cubek; scripts send the Bearer header above; without it: 401, or a redirect to sign in for a browser). The app never sees that header or cubek's cookie. deploy --public makes one public, cubek preview access public all of them; production is always public.
  • A preview lives 7 days from its deploy, then its URL answers 410 (the revision stays: promote, rollback); deploy again for a new one. Free keeps 3 per project: a 4th deploy without --prod gets 429 until one ends. deploy --prod makes no preview.
  • --db copy (default): production's data at deploy time; writes stay in the copy. --db prod: reads and writes go to production, its tasks too (cron never runs on previews); refused unless production's schema already has everything the revision declares (promote schema changes first). Its pages show a "Preview · production data" pill (--no-banner: none).
  • In every preview push.send sends nothing (logged; dryRun: true) unless deployed with --side-effects.
  • A revision is immutable; production is a pointer: promote <N> moves it to a revision you already tried; deploy --prod builds and uploads the directory again as a new revision (the output names it). A deploy compiles, bundles client/ with bun and uploads only what changed (it prints the files and bytes, new vs reused).
  • --prod snapshots the database, applies additive migrations and switches atomically. Destructive schema changes stop with a message: --allow-destructive after reading it (database.md).
  • The project is created on the first deploy, named by cubek.json name; cubek project create makes it empty first, to set secrets before that.

Secrets

cubek secrets set STRIPE_KEY sk_live_...      # production; read at the next request
cubek secrets list
cubek secrets --dev set STRIPE_KEY sk_test_...  # cubek dev: .cubek/secrets.json (keep it out of git)
// src/app.tsx
import { app, log, secrets } from "cubek"

app.get("/healthz", async (c) => c.text("ok"))

app.get("/api/status", async (c) => {
  const key = secrets.get("STRIPE_KEY")
  if (key === null) {
    log.error("STRIPE_KEY is not set")
    return c.json({ error: "payments are not configured" }, 503)
  }
  return c.json({ payments: key.startsWith("sk_live_") ? "live" : "test" })
})

Never commit secrets or print them in logs; secrets.get returns null when unset: handle it.

Custom domains

cubek domains add shop.example.com       # prints the DNS records to create
cubek domains verify shop.example.com    # check now (also automatic)
cubek domains list
cubek domains add example.com && cubek domains add www.example.com
cubek domains redirect www.example.com example.com   # 308, path and query kept
  • DNS: a CNAME to <name>.cubek.app, or (apex) the A/AAAA records the answer lists. HTTPS certificates are issued once the domain verifies.
  • c.origin() gives the host the request came to: use it for absolute links.
  • c.preview is { revision, db } in a preview, null in production and cubek dev; tasks get it as t.preview (task.on("x", async (p, t) => ...)): skip real side effects (emails, charges) in previews. Send the production *.cubek.app host to your domain, never a preview (registered before the routes):
// src/app.tsx
app.use(async (c, next) => {
  if (c.preview === null && (c.header("host") ?? "").endsWith(".cubek.app")) return c.redirect(`https://example.com${c.path()}`, 308)
  await next()
})

Logs, metrics, data

cubek logs                          # last 100 lines: requests, log.*, tasks, errors
cubek logs --follow --level warn    # every node's lines, streamed
cubek logs --route /checkout --since 10m   # one route, recent
cubek metrics --window 24h          # requests, errors, p50/p95/p99, CPU, DB time, storage
cubek metrics --by-route            # per route: requests, 4xx, 5xx, p50/p95/p99, DB time
cubek sql "SELECT status, COUNT(*) AS n FROM orders GROUP BY status"
cubek sql --write "UPDATE orders SET status = 'paid' WHERE id = '01J...'"
cubek db restore --to 2026-09-26T10:15:00Z   # the database at that second, onto a preview
cubek db restore --promote                    # then make it production's
cubek db export --out backup.sqlite           # the whole database as one SQLite file (--at <instant>, --snapshot <id>)
cubek db import backup.sqlite --replace       # bulk rows back in: SQLite, .csv, .ndjson or a dir (database.md)
cubek sql -f fix.sql --write                  # a script, one transaction (`-f -`: stdin)
cubek storage export --out files/             # every storage object (rerun to resume)
cubek backup                                  # everything but your code, one .tar.gz (--at <instant>)
cubek project list
  • cubek sql prints a table (NULL for null) and (N rows), or N rows changed; --json (scripts, agents) → { "ok": true, "columns": ["status", "n"], "rows": [["paid", 3]] }: rows are arrays in column order (≤ 1,000); --write without RETURNING adds "changes": n. --dev: the same on the local dev database. t.number() columns are REAL: they print as floats (3.0); CAST(qty AS INTEGER) for integers.
  • A 500 answers an empty body; its error is in cubek logs.
  • log.info("paid", { orderId }): structured data, searchable in the logs.
  • Restores (db restore --to, rollback --with-db, snapshots) reach 7 days back on Free (30 on Custom), no further.
  • Snapshots, db restore and rollback --with-db bring back the database with its storage objects as they were (contents deleted longer ago than the point-in-time window are gone; storage.md), not secrets. If you encrypt data with a key from secrets, keep that key elsewhere too: a lost secret makes restored ciphertext unreadable.
  • cubek sql stops at 1,000 rows; cubek db export has no limit (open the file with any SQLite tool).
  • cubek backup packages the database, every storage object, the schema, domains, cron schedules and secret NAMES (never values), each file with its sha256 in manifest.json. cubek restore-backup <file> --project P puts it into a deployed project (tables replaced, objects uploaded; a schema that does not fit is refused without --force), then lists the secrets to set and the domains to re-attach by hand.

Workflow for agents

  1. cubek check clean, cubek test green, the routes tried on cubek dev.
  2. cubek deploy, try the preview URL, then cubek promote <N> (that revision); cubek deploy --prod directly when no preview is needed.
  3. After production: cubek logs --level warn and a request to a changed route.
  4. Broken production: cubek rollback <previous rev> first, fix after.