layerbase
The Layerbase CLI is local-first: it is a drop-in for
spindb for local database work, plus a
cloud layer for your managed Layerbase cloud account. Connect with native
clients, drop into spindb, and never paste a connection string into your shell
history.
The bare command namespace belongs to spindb. Any command that is not one of
layerbase's own verbs forwards straight to your local spindb install, verbatim,
so lbase create / ls / start / backup / branch ... behave exactly like
spindb <same>. Cloud database commands live under lbase cloud <verb>.
Install
npm i -g layerbase
# or
pnpm add -g layerbase
# or
bun add -g layerbaseThis installs two commands, layerbase and a shorter lbase. For a two-letter
lb, run layerbase alias (it only claims lb if nothing else owns it).
Quick start
lbase # spindb's interactive menu (local databases)
lbase create my-db --engine postgres # any spindb command, verbatim
lbase login # sign in through your browser
lbase promote ./app.db # put a local database in the cloud, data included
lbase cloud ls # list your cloud databases
lbase psql my-cloud-db # connect with the right client, no password typedLocal databases (drop-in for spindb)
Every spindb command works. Anything that is not a layerbase verb (see the cloud commands below) forwards straight to your local spindb install, with every flag and the exit code preserved:
lbase create my-db --engine postgres # == spindb create my-db --engine postgres
lbase ls # == spindb list
lbase start my-db # == spindb start my-db
lbase backup my-db # == spindb backup my-db
lbase branch my-db feature-x # == spindb branch my-db feature-x
lbase <cmd> --help # == spindb <cmd> --helpBare lbase with no arguments opens spindb's own interactive menu. If spindb
does not recognize a command either, spindb's own error is shown and the exit
code is nonzero. lbase spindb [args...] is an explicit form of the same
passthrough.
Cloud account
These are the only verbs layerbase owns; everything else is spindb.
| Command | Description |
|---|---|
lbase login | Sign in via the browser; stores a token in ~/.layerbase-cli. |
lbase login --api-key <key> | Save a personal sk_ API key for headless use (no browser). |
lbase logout | Remove the stored credentials. |
lbase whoami | Show the signed-in account (and API-key usage in key mode) (--json). |
lbase cloud ls | List your cloud databases and branches (--json for scripting). |
lbase cloud create <name> --engine <e> [--ttl 2h] | Provision a database (--ttl makes it transient). |
lbase cloud delete <db> --yes | Delete a database (--yes/-y to skip the prompt). |
lbase cloud start <db> / stop <db> | Start or stop a database. |
lbase cloud branch <db> <name> | Create or reuse a branch (idempotent). |
lbase cloud branch reset <db> <name> | Re-fork a branch from its parent. |
lbase cloud branch delete <db> <name> | Delete a branch. |
lbase cloud branch ls <db> | List a database's branches (--json). |
lbase cloud connect <db> | Connect with the engine's native client. |
lbase cloud clone <db> [name] | Clone a cloud database into a local spindb container. |
lbase cloud connection-string <db> | Print the connection string (reveals the password; alias: url; --json). |
lbase psql <db> | Connect to a cloud Postgres-family database. |
lbase redis-cli <db> | Connect to a cloud Redis / Valkey database. |
lbase mysql <db> | Connect to a cloud MySQL / MariaDB database. |
lbase promote <file-or-container> | Create a cloud database from a local file or spindb container, data included. |
lbase migrate --source <id> --target <db> | Migrate an external database into a cloud database. |
lbase import <dumpfile> --target <db> | Import a dump file into a cloud database. |
lbase agent init [--global] | Install the Layerbase skill for AI coding agents. |
lbase alias | Set up the short lb command (only if it is free). |
lbase chat | Interactive console for your Layerbase account. |
<db> accepts a cloud database id or its name. Add --print to
cloud connect to show the connection details instead of launching a client.
cloud ls lists databases and their branches in one table. When the account
has branches, a PARENT column names each branch's parent database (- on a
primary) and a footer splits the rows, because branches do not count toward your
plan's database limit: never read the row count as your database count. In
--json, a branch is any row with parentId set (parentName names its
parent); rows are passed through from the API untouched.
lbase cloud with no subcommand prints the cloud help. Cloud mutation commands
(create, delete, start, stop, branch) run against the cloud API and
need an API key (see Headless auth); every one
supports --json and returns a meaningful exit code.
Promote a local database to the cloud
lbase promote is the graduation path for a prototype: it creates a new
cloud database sized to the source, imports the data, and prints the connection
string. One command instead of create, dump, and import.
lbase promote ./app.db # SQLite file
lbase promote ./analytics.duckdb # DuckDB file
lbase promote ./dump.sql # Postgres-dialect SQL dump
lbase promote my-local-pg # a local spindb container
lbase promote ./app.db --write-env --yes # and rewrite DATABASE_URL in ./.envThe source is detected, never guessed: binary files are identified by their
header (a .db that is really a DuckDB file is treated as DuckDB), .sql is a
Postgres-dialect dump, and a bare name is looked up against your local spindb
containers. Anything ambiguous fails with an actionable message; --from pglite|sqlite|duckdb|sql|spindb forces the kind.
| Source | Cloud target |
|---|---|
SQLite file (.db, .sqlite, .sqlite3) | sqlite (SQLite storage behind the Postgres wire) |
DuckDB file (.duckdb) | duckdb |
SQL dump (.sql), PGlite dump | postgresql |
| spindb container | the same engine in the cloud |
--target libsql is accepted but currently refused: cloud libSQL restores from a
data-directory archive, not from a SQLite file, so there is no path that puts a
local .db into it yet. Use the default (--target pgsqlite), or
lbase migrate --source turso for an already-hosted libSQL database.
Desktop-only engines (MongoDB, CockroachDB, SurrealDB) are refused with the
licensing reason and the closest cloud alternative, and a spindb engine whose
local backup format the cloud import endpoint cannot restore is refused with a
pointer at lbase migrate. Every refusal happens before anything is created,
so an unsupported source never leaves an empty database behind.
PGlite data directories are not supported directly (that would put several MB
of WASM in every install). Dump the directory first and promote the .sql:
const db = await PGlite.create('./pgdata') // @electric-sql/pglite
const dump = await pgDump({ pg: db }) // @electric-sql/pglite-tools/pg_dump
await writeFile('./dump.sql', await dump.text()) // node:fs/promiseslbase promote ./dump.sql--write-env is opt-in: it rewrites DATABASE_URL in ./.env (creating the
file when missing), leaves every other line untouched, ignores commented-out
assignments, and prints what it did. --json prints one result object with the
database, the connection string, the dashboard URL, and the bytes uploaded.
--name overrides the derived database name. If the import fails after the
database is created, promote says the database exists and is empty and prints the
exact retry and delete commands; it never deletes anything on your behalf.
The create request records how the database was made: promote (with the kind
of source it came from, one of sqlite, duckdb, sql-dump, spindb) or a
plain cli create. That is the whole payload - your file paths, filenames, and
local container names never leave your machine.
Migrations and imports
lbase migrate imports an external database into an existing cloud database,
and lbase import restores a whole-database dump file. Both are headless (need
an API key), support --json, and never write credentials to stdout, stderr, or
JSON output. Run lbase migrate --help for the full per-source flag list.
# Connection-string sources (paste one URL):
lbase migrate --source postgres --target my-db \
--connection-string "postgresql://user:pass@host:5432/db" --yes
# API-key sources (we discover the account, then you pick a database):
lbase migrate --source neon --target my-db --source-key napi_... --yes
lbase migrate --source algolia --target my-search --source-key <admin-key> --app-id <app-id> --yes
lbase migrate --source turso --target my-libsql --source-key <token> --url libsql://... --yes
# Whole-database dump import:
lbase import ./backup.dump --target my-db --yesSources: neon, supabase, render, railway, postgres, mysql,
mariadb, planetscale, upstash, vercel-kv, redis, valkey, algolia
(to Meilisearch), turso (to libSQL). Connection-string sources
(postgres/mysql/mariadb/redis/valkey/vercel-kv) take
--connection-string (alias --url). API-key sources take --source-key (alias
--token) plus, where needed, --source-id (aliases --app-id, --email,
--token-id, --url for a Turso database URL) and --source-secret (alias
--db-password, Supabase only). When multiple source databases are discovered,
pick one with --source-db <label-or-number> (or interactively on a TTY).
--json on migrate prints one final result object (no interim progress
lines): { ok, runId, status, databaseId, report } on success or
{ ok: false, runId, status, error } on failure; without --json, status and
progress stream while the migration run polls. --yes (-y) confirms
non-interactively; a migration or import may overwrite the target's data, so a
non-TTY run without --yes refuses and exits 1.
Headless auth (CI and agents)
For CI pipelines and coding agents, authenticate with a personal API key instead
of the browser flow. Create one in the dashboard at
https://layerbase.com/cloud/settings, then either export it or save it:
export LAYERBASE_API_KEY=sk_... # env var: no login step needed at all
# or
lbase login --api-key sk_... # persists it to ~/.layerbase-cli (0600)
# or, per-invocation
lbase cloud ls --api-key sk_... --jsonPrecedence is --api-key flag > LAYERBASE_API_KEY env > stored key. When a key
is in play, cloud calls go directly to the cloud API (the browser-JWT proxy
is skipped). Note: a key is tied to one account and works against your primary
control plane.
A transient database auto-deletes at its TTL, so a crashed CI run cannot strand
a database against your quota. Where the engine supports branching, a
branch-per-run (branch from a seeded parent, reset between runs, delete on
teardown) is the cheaper CI primitive.
# .github/workflows/test.yml (excerpt)
env:
LAYERBASE_API_KEY: ${{ secrets.LAYERBASE_API_KEY }}
steps:
- run: npm i -g layerbase
- run: |
DB=$(lbase cloud create "ci-$GITHUB_RUN_ID" --engine postgresql --ttl 2h --json)
echo "DATABASE_URL=$(echo "$DB"
…