Skip to content

CLI Reference

The AnvilBase CLI (anvilbase) is a single Rust binary that drives the control plane’s HTTP API. Most commands resolve a control-plane URL, attach your token, call an endpoint, and print formatted output.

Install

Terminal window
cargo install --path cli # from a checkout
# or build explicitly
cargo build -p anvilbase-cli --release
./target/release/anvilbase --help

The crate is anvilbase-cli; the binary is anvilbase. Standard clap helpers apply: anvilbase --help, anvilbase <group> --help, anvilbase --version.

Configuration & auth

Control-plane URL resolution (first match wins)

OrderSource
1--url <URL> flag or ANVILBASE_URL env var
2--env <name> → that environment’s url in anvilbase.toml
3.anvilbase/config.json (url field) in the current directory
4http://localhost:39001 (default)

A .env in the current directory is auto-loaded, so ANVILBASE_URL / ANVILBASE_TOKEN can live there.

Token

Set ANVILBASE_TOKEN to a management credential (a PAT anvilbase_pat_…, or the admin token). It’s attached as Authorization: Bearer <token> on every request.

Terminal window
export ANVILBASE_TOKEN="anvilbase_pat_xxxxxxxx"

The management API is fail-closed, so any /api/v1 command needs a valid token; health/status work without one. realtime subscribe is the exception — it authenticates via a ?token= query param and takes a project credential from ANVILBASE_REALTIME_TOKEN (or --token), not the management ANVILBASE_TOKEN (see Realtime).

For a CLI-managed local stack, anvilbase start generates ANVILBASE_ADMIN_TOKEN into .env and anvilbase status --local prints an export ANVILBASE_TOKEN=<…> tip for it. For raw docker compose up deployments you set it yourself.

Mint, audit, and revoke PATs (requires an admin credential):

Terminal window
anvilbase token mint --name ci-deploy --scope deploy --expires 90d # plaintext shown ONCE
anvilbase token list # metadata only
anvilbase token revoke <id>

Scopes: admin (everything) ⊇ deploy (CI surface + reads) ⊇ read (GET-only, minus sensitive reads: secret reveals, bulk audit export, platform-user listing). See Security → RBAC.

Global flags

FlagEnvDescription
--url <URL>ANVILBASE_URLcontrol-plane base URL
--env <name>target a named environment from anvilbase.toml
--help / -hhelp for the binary or any subcommand
--version / -VCLI version

Platform & health

Terminal window
anvilbase health # GET /health/services — overall + per-service OK/FAIL
anvilbase status # health + /api/v1/admin/stats (project counts)
anvilbase status --local # local stack URLs + secrets from .env (no network)

/health and /health/services report the running control-plane version. health and status compare it against the CLI’s own version and print a one-line warning: CLI … != server … to stderr on a mismatch — a stale binary otherwise reads as “missing features.” Matching versions (or an older server that doesn’t report version) print nothing.

Local stack

Thin wrappers over docker compose (compose file auto-discovered; COMPOSE_FILE honored). On first start, a .env with fresh secrets is generated if missing.

Terminal window
anvilbase start # docker compose up -d --wait
anvilbase start --build # rebuild images first
anvilbase start --no-wait # don't block on healthchecks
anvilbase ps # container status
anvilbase restart
anvilbase stop # stop, keep containers + data
anvilbase down # remove containers/networks (keeps volumes)
anvilbase down --volumes # also remove volumes — DESTROYS local data

Projects

Terminal window
anvilbase projects list # GET /api/v1/projects
anvilbase projects create "My App" # POST /api/v1/projects
anvilbase projects create "My App" --slug my-app
anvilbase projects info <id> # GET /api/v1/projects/{id}
anvilbase projects suspend <id> # POST /api/v1/projects/{id}/suspend (blocks data plane)
anvilbase projects resume <id> # POST /api/v1/projects/{id}/resume
anvilbase projects delete <id> # DELETE /api/v1/projects/{id}
anvilbase projects keys <id> # anon plaintext + service_role masked
anvilbase projects keys <id> --show-service-role # reveal service_role (audit-logged)
anvilbase projects pooler <id> # GET /api/v1/projects/{id}/pooler — connection strings
# SMTP — per-project email delivery
anvilbase projects smtp show <id> # GET /api/v1/projects/{id}/email/smtp
anvilbase projects smtp set <id> # local dev Mailpit defaults (host=mailpit port=1025 encryption=none)
anvilbase projects smtp set <id> \
--host smtp.sendgrid.net --port 587 \
--from noreply@myapp.com --from-name "My App" \
--user apikey --encryption starttls # production provider (password prompted)

create prints the new id, anon key, service role key, and JWT secret — the service role key and JWT secret are shown once.

pooler prints the project’s pooled (Supavisor, transaction mode) and direct (session) Postgres connection strings. The pooled string embeds the per-project pooler password, so the reveal is audit-logged on the server. The pooled endpoint is profile-gated; if you ran a plain docker compose up, start Supavisor with docker compose --profile pooler up -d (or use anvilbase start, which enables it automatically). anvilbase projects pooler surfaces the server’s pooler_reachable signal and warns when the pooler isn’t reachable. See Connection Pooling.

After creating a project, run projects smtp set <id> to configure email delivery. Mailpit defaults work for local dev out of the box (no flags needed).

Branch (database branching)

Isolated clones of a project’s database — Neon-style branching. A branch gets its own database, API keys, and JWT secret, so it’s a full, isolated tenant; deleting a branch never touches the parent.

Terminal window
anvilbase branch create --project <id> dev # POST /api/v1/projects/{id}/branches — clone schema + data
anvilbase branch create --project <id> dev --schema-only # DDL only, ZERO rows (fast)
anvilbase branch list --project <id> # GET /api/v1/projects/{id}/branches
anvilbase branch delete --project <id> <slug> # DELETE /api/v1/projects/{id}/branches/{slug}

branch create takes the branch name as a positional arg (it becomes the slug) and prints the new branch’s id and keys. A data branch clones in ~30-60s (dump-based fallback) or near-instantly when the in-engine TEMPLATE path applies; --schema-only is always fast.

Database

Terminal window
anvilbase db push --project <id> # dry-run: list pending migrations
anvilbase db push --project <id> --apply # apply ordered ./migrations/*.sql
anvilbase db push --project <id> --path ./db/migrations --apply
anvilbase db pull --project <id> # dump schema → schema-<id>.sql
anvilbase db pull --project <id> --data --yes # RLS-scoped seed sample → seed-<id>.sql
anvilbase db reset --project <id> # drop all tables + re-apply (prompts)
anvilbase db reset --project <id> --yes # skip prompt (CI)
anvilbase migrations list --project <id> # applied vs local (drift detection)
anvilbase migrations apply --project <id> # alias for db push --apply

See Database → Migrations.

Schema

Terminal window
anvilbase schema tables --project <id>
anvilbase schema extensions --project <id>
anvilbase schema table create --project <id> --name todos --from ./todos.json
anvilbase schema table drop --project <id> --name todos --confirm todos
anvilbase schema column add --project <id> --table todos --name notes --type text --nullable true
anvilbase schema column drop --project <id> --table todos --name notes --confirm notes

Drops require --confirm to match the name exactly (checked before any HTTP call). See Tables & Schema.

RLS

Terminal window
anvilbase rls status --project <id>
anvilbase rls enable --project <id> --table todos
anvilbase rls disable --project <id> --table todos
anvilbase rls policies --project <id> --table todos
anvilbase rls policy create --project <id> --table todos \
--name owner_select --command SELECT --using "user_id = current_user_id()"
anvilbase rls policy drop --project <id> --table todos --name owner_select
anvilbase rls test --project <id> --table todos --role authenticated --user <user_id>

See Row Level Security.

Auth (users, sessions, invites)

Terminal window
anvilbase auth users list --project <id>
anvilbase auth users create alice@example.com --project <id> --name "Alice"
anvilbase auth users ban <user_id> --project <id> --reason spam --expires-in 3600
anvilbase auth users unban <user_id> --project <id>
anvilbase auth users delete <user_id> --project <id>
anvilbase auth sessions revoke --project <id> --user <user_id> # all sessions
anvilbase auth invites create --project <id> --email new@example.com --role admin
anvilbase auth invites list --project <id>

Secrets

Terminal window
anvilbase secrets list --project <id>
anvilbase secrets set STRIPE_KEY "sk_live_abc==" --project <id> # name & value separate
anvilbase secrets delete STRIPE_KEY --project <id>
anvilbase secrets rotate jwt --project <id> # invalidate all JWTs
anvilbase secrets rotate api-keys --project <id> # new anon + service_role

Functions

Terminal window
anvilbase functions deploy hello --project <id> # ./functions/hello.ts OR ./functions/hello/index.ts
anvilbase functions deploy webhook --project <id> --path ./src/edge
anvilbase functions list --project <id>

functions deploy accepts both ./functions/<name>.ts and ./functions/<name>/index.ts (the Supabase layout) — whichever exists. It persists through the management API (MinIO + the edge_functions store of record), so deploys are durable — they survive restarts and appear in functions list immediately (the listing reads the durable management store, no invoke required). The function goes live on its first invocation via lazy rehydration. See Writing & Deploying Functions.

Webhooks

Terminal window
anvilbase webhooks list --project <id>
anvilbase webhooks create --project <id> --url https://example.com/hook \
--events "insert,update,delete" --description "audit hook" # prints signing secret once
anvilbase webhooks update --project <id> --id <wid> --is-active false
anvilbase webhooks test --project <id> --id <wid>
anvilbase webhooks deliveries --project <id> --id <wid> # recent deliveries
anvilbase webhooks deliveries --project <id> --id <wid> --status dead_letter # the DLQ
anvilbase webhooks delete --project <id> --id <wid>

Database webhooks (row-change triggers)

Attach a webhook to a user table so it fires on INSERT/UPDATE/DELETE (Supabase-style database webhooks). Both attach and detach are idempotent.

Terminal window
anvilbase webhooks attach --project <id> --webhook <wid> --table todos \
--events "insert,update,delete" # omit --events to fire on all ops; --schema defaults to public
anvilbase webhooks detach --project <id> --webhook <wid> --table todos
anvilbase webhooks attachments --project <id> --webhook <wid> # tables this webhook is attached to

Retries & dead-letter queue

Terminal window
# Re-enqueue an exhausted (dead-letter) delivery — resets it to pending and
# re-notifies the webhooks service (increments manual_retry_count). Find the
# delivery id via `webhooks deliveries --status dead_letter`.
anvilbase webhooks retry --project <id> --webhook <wid> --delivery <delivery_id>
# Purge DLQ deliveries older than --days (admin). Omit --days to use the server
# default (ANVILBASE_DLQ_RETENTION_DAYS, 30). A nightly pg_cron job handles the
# steady-state case automatically.
anvilbase webhooks dlq purge # server default window
anvilbase webhooks dlq purge --days 7 # tighter window for this run

Backups

Terminal window
anvilbase backup create --project <id>
anvilbase backup list --project <id>
anvilbase backup restore <backup_id> --project <id>

PITR (point-in-time recovery)

List restore points and dry-run / execute operator-driven restores to any moment covered by the basebackup + WAL archive.

Terminal window
anvilbase pitr restore-points --project <id> # GET /api/v1/projects/{id}/pitr/restore-points
anvilbase pitr restore --project <id> --target-time 2026-04-17T15:04:05Z --dry-run # read-only preview
anvilbase pitr restore --project <id> --target-time 2026-04-17T15:04:05Z # enqueue the restore job
  • --target-time <RFC3339> — the moment to restore to (e.g. 2026-04-17T15:04:05Z).
  • --dry-run — preview only: prints which basebackup would be selected, the WAL replay window, and how much write activity would be rewound. Hits …/pitr/restore/preview; enqueues nothing.
  • Without --dry-run, restore POSTs to …/pitr/restore and enqueues a pending restore job.

Upgrade (data-safe platform upgrade)

anvilbase upgrade drives the data-safe upgrade runner. It defaults to a DRY-RUN — it prints exactly what would happen (target version, images to pull, the platform + per-project migrations that would apply, and the per-project snapshot plan) and changes nothing. You must pass --yes to actually execute, which makes an accidental destructive upgrade impossible.

Terminal window
# DRY-RUN (default): print the plan, change nothing.
anvilbase upgrade # target = the running version (no-op preview)
anvilbase upgrade --target 1.1.0 # preview the plan to upgrade to 1.1.0
anvilbase upgrade --target 1.1.0 --dry-run # explicit dry-run (same as default)
# EXECUTE: snapshot every project FIRST, abort if any snapshot fails, then
# apply migrations idempotently. Prints the rollback manifest (per-project
# pre-upgrade snapshot ids) at the end.
anvilbase upgrade --target 1.1.0 --yes
  • --target <version> — the version to upgrade to. Omit to use the running control plane’s own version (version-agnostic; a no-op until a newer release is deployed). A downgrade (target < current) is rejected.
  • --dry-run — explicit preview (this is the default; mutually exclusive with --yes).
  • --yes / -y — execute the upgrade. Requires an admin credential.

Rollback: restore the per-project pre-upgrade snapshots and pin the prior images. Full procedure in Upgrades.

Logs (audit)

Terminal window
anvilbase logs --limit 50
anvilbase logs --project-id <id> --limit 100
anvilbase logs --follow --project-id <id> # polls every 2s
# Retention (admin): prune the audit log + auth-event archive on demand.
# Omit --days to use the server default (ANVILBASE_AUDIT_RETENTION_DAYS, 90).
# A nightly pg_cron job handles the steady-state case automatically.
anvilbase audit prune # server default window
anvilbase audit prune --days 30 # tighter window for this run

Realtime (debug)

realtime subscribe needs a project credential — a project JWT, service_role key, or anon key — supplied via ANVILBASE_REALTIME_TOKEN or --token. It is deliberately a different env var from the management ANVILBASE_TOKEN: the token is placed in the WebSocket ?token= query string, which leaks into access logs, so the CLI refuses to send a management PAT (anvilbase_pat_…) or the ambient ANVILBASE_TOKEN there.

Terminal window
export ANVILBASE_REALTIME_TOKEN="<project-jwt-or-service-role-or-anon-key>"
anvilbase realtime subscribe --project <id> --channel "realtime:public:todos"
# connect directly to the realtime container in local dev:
REALTIME_URL=ws://localhost:4000 anvilbase realtime subscribe --project <id> --channel "realtime:public:todos"

Code generation

Terminal window
anvilbase gen types typescript --project <id> > database.types.ts # Supabase-compatible
anvilbase gen types drizzle --project <id> > src/db/schema.ts # drizzle-orm

MCP setup (AI tools)

Terminal window
anvilbase mcp setup # interactive: pick tools, project or global
anvilbase mcp status # what's configured + whether the token resolves
anvilbase mcp remove --agent cursor

See Tools → MCP.

Environments & deploy (multi-env)

Named environments live in anvilbase.toml (topology only — no secrets); local link state lives in .anvilbase/config.json (0600).

Terminal window
anvilbase env add staging --url https://anvil.staging.example.com \
--project <ref> --token-source env:ANVILBASE_TOKEN_STAGING
anvilbase env list
anvilbase env use staging # set the active default env
anvilbase env diff staging prod # non-zero exit on divergence — a CI gate
anvilbase deploy staging # preflight → db push → secrets → functions → verify
anvilbase deploy prod --dry-run # preview, change nothing
anvilbase deploy staging --only migrations,functions
anvilbase deploy prod --yes # required for require_approval envs (CI)

Safety: CI owns promotion. Real writes to any non-allow_local_writes env are refused outside CI; promote by committing and letting the deploy workflow run. Reads (status, migrations list, env diff, db pull) are always allowed. Data only flows down (sampled db pull --data); no command pushes local data up to a real environment.

Terminal window
anvilbase config set --url https://anvil.example.com
anvilbase config get
anvilbase config show # active URL, its source, and resolution order
anvilbase link https://anvil.example.com # write url to .anvilbase/config.json
anvilbase link staging # validate + activate a named env

Exit behavior

Commands exit non-zero on failure (connection errors, non-2xx, validation such as a mismatched --confirm). config get is the exception: with no URL configured it prints a notice to stderr but exits 0, so scripts can probe for a link file.

Next: Tools → MCP.