Guide
The Layerbase CLI
The Layerbase CLI was previously known as SpinDB. The same engine now ships as the branded layerbase package, run as lbase, and SpinDB continues as the open-source engine under the hood. If you have used SpinDB, you already know this tool: it manages local databases as native processes, no Docker required, and now adds an optional cloud account layer on top.
The rename is a drop-in. Every SpinDB command works verbatim as lbase <command>: same flags, same TTY behavior, same exit codes. Bare lbase with no arguments opens the interactive menu.
lbase create myapp --start # same as: spindb create myapp --start
lbase ls # same as: spindb list
lbase branch myapp myapp-exp # same as: spindb branch myapp myapp-exp
lbase backup myapp # same as: spindb backup myapplbase chat is reserved for the interactive agentic chat console planned for an upcoming release. Today it opens the interactive account console as a placeholder, so do not treat it as a stable feature yet.
Install
Install it globally with your package manager of choice. You need Node.js 20 or newer. This page describes version 1.2.0 or later.
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 when nothing else on your system owns it (for example Debian's live-build ships an lb); if it is taken, the CLI leaves it alone and suggests a shell alias instead. The rest of this page uses lbase.
Sign in
No account is needed for any local command. Sign in only when you want the cloud features (listing, connecting to, and cloning your Layerbase Cloud databases). lbase login opens your browser, you sign in with GitHub or Google, and a 30-day token is returned to a loopback server the CLI runs on 127.0.0.1. A state nonce ties the response to your login, and the token is only ever delivered to a loopback address. It is stored at ~/.layerbase-cli/credentials.json with file mode 0600. This mirrors how the Layerbase desktop app signs in. Run lbase logout to remove it, or lbase whoami to see the account and token expiry.
Two credentials, two different APIs
This trips people up often enough to be worth stating plainly: the CLI can hold two kinds of credential, and they are not interchangeable.
The session token from lbase login is a browser-issued JWT. It authenticates only against the read-only CLI proxy routes on layerbase.com/api/cli/*, which back cloud ls, connection-string, connect, and whoami. It is not accepted by the Layerbase Cloud /v1 REST API.
The personal API key (prefixed sk_) authenticates against the cloud /v1 API. Every mutation goes there: cloud create, delete, start, stop, and branch all require a key, and the CLI says so rather than failing obscurely if you only have a browser login. When a key is present it takes precedence over any stored session token.
If you are calling the /v1 API yourself with curl or a script and get Invalid API key, check what you actually sent. ~/.layerbase-cli/credentials.json can hold either kind of credential, in separate fields: the token written by a plain lbase login browser sign-in is a session token that /v1 rejects, while apiKey is a real key. If you do not have one there, run lbase login --api-key sk_... or set LAYERBASE_API_KEY instead of reusing the session token. A real key starts with sk_ and comes from Personal API keys in cloud settings.
Headless auth for CI and agents
The browser login is for humans at a terminal. For CI, scripts, and coding agents, authenticate with a personal API key instead: no browser, no 30-day token to expire. Create a key under Personal API keys in cloud settings (the full sk_ secret is shown once), then give it to the CLI in one of three ways:
# 1. Environment variable (best for CI: no files, no login step)
export LAYERBASE_API_KEY=sk_...
lbase cloud ls --json
# 2. Per-command flag (wins over the env var and any stored key)
lbase cloud ls --api-key sk_...
# 3. Store it once, no browser (validates the key, then writes it 0600)
lbase login --api-key sk_...Precedence is --api-key flag, then LAYERBASE_API_KEY, then the stored credential. When a key is present, the CLI talks directly to the Layerbase Cloud /v1 REST API at https://cloud.layerbase.dev (override with LAYERBASE_CLOUD_API_URL), so the same quota, suspension, and rate-limit rules the dashboard enforces apply. A key is tied to the account that created it and works against that account's primary control plane; there is no separate CI login to keep alive.
Keys are long-lived by design (rotate on compromise, not on a schedule). Today a key belongs to one human account, so treat a CI key as an offboarding item. Store it as a secret, never in the repo.
Cloud databases from your terminal
Cloud actions live under the cloud namespace so they never collide with the local commands you already know. Create and delete databases, start and stop them, branch them, list them, connect with the right native client, and clone a cloud database down to a local container. Every one of these wraps the same public /v1 REST API the dashboard uses, so they all work headlessly with an API key.
lbase cloud create my-db --engine postgresql # provision a database
lbase cloud create ci-db -e postgresql --ttl 2h # transient: auto-destroys in 2h
lbase cloud ls # list your cloud databases (with an EXPIRES column)
lbase cloud stop my-db # stop (data kept)
lbase cloud start my-db # start again
lbase cloud connection-string my-db # print the full connection string
lbase cloud delete my-db --yes # delete (--yes is required in scripts)
lbase whoami # account, plan, and programmatic-create usageProvision a cloud database and print its connection string. Pass --ttl to make it transient (a whole number of hours, up to 72; 30m and 1d forms are accepted and round up to the hour). Transient databases self-destruct at expiry, which is ideal for CI.
Delete a database (aliases: rm, destroy). In a script or other non-interactive shell, --yes (-y) is required or the command refuses and exits 1.
Start a stopped or hibernated database, or stop a running one. Data is kept either way.
Fork a durable database into a named branch (idempotent: re-running reuses it). branch reset <db> <name> re-forks it clean, branch delete <db> <name> removes it, and branch ls <db> lists branches. Transient (--ttl) databases cannot be branched.
List your cloud databases with NAME, ENGINE, STATUS, EXPIRES, and ID columns. EXPIRES shows the destroy time for transient databases. Add --json for machine-readable output.
Connect with the engine's native client. <db> accepts a database id or its name. Add --print to show the connection details instead of launching a client.
Print the full connection string, which reveals the password. Use this for engines without a dedicated connect shortcut. Add --json, or use the url alias.
Create and populate a matching local container from a cloud database. Defaults the local name to the cloud database name.
Sign in through the browser, sign out and remove the stored token, or show the signed-in account. With an API key, whoami reports your plan and this month's programmatic-create usage. Add --json to whoami for scripting.
Shortcuts that connect to a Postgres-family, MySQL-family, or Redis/Valkey cloud database with the matching client.
Connecting without leaking a password
Passing a connection string like psql "postgresql://..." leaks the password into your shell history, into ps while the client runs, and into terminal scrollback. The connect commands resolve the database over TLS and hand the credential to the client through an environment variable or a transient 0600 file that is deleted on exit. The password is never an argv value.
Passed via a PGPASSFILE temp 0600 file, deleted on exit.
Passed via a --defaults-extra-file temp 0600 file, deleted on exit.
Passed via the REDISCLI_AUTH environment variable.
Use lbase cloud connection-string, or cloud connect --print, and copy the value.
The cloud connect, psql, mysql, and redis-cli commands need the matching native client on your PATH.
Clone a cloud database locally
lbase cloud clone copies a cloud database into a local container so you can work against a real copy of your data offline. The CLI resolves the remote connection details (authenticated), creates a matching local container for the engine and version if it does not already exist, starts it, and pulls the data in. The remote password is passed through an environment variable, never on the command line.
lbase cloud clone my-db # into a container named "my-db"
lbase cloud clone my-db local-copy # into a container you nameScripting the cloud
Every cloud command works the same in a script as it does interactively. Add --json for machine-readable output on cloud ls, cloud create, connection-string, whoami, and the migrate / import verbs below; --print on cloud connect shows connection details without launching a client. Commands that mutate refuse to run non-interactively without --yes, and every command sets a meaningful exit code so a pipeline can branch on the outcome.
Migrate and import from your terminal
migrate and import are top-level verbs (not under cloud). They drive the same migration engine as the dashboard, so moving off another provider no longer needs a browser.
# Migrate a live source into an existing cloud database
lbase migrate --source neon --target my-db --source-key napi_... --yes --json
# Load a dump file into a cloud database
lbase import ./dump.sql --target my-db --yes --jsonmigrate reads from a live source, runs a preflight, starts the run, and polls it to completion. import uploads a whole-database dump file and completes synchronously. Both refuse to start without --yes (-y) in a non-interactive shell, both accept --json (which prints a single final result object), and credentials are never echoed into output or logs. On a TTY, missing credentials are prompted.
Fourteen sources are supported. Connection-string sources take the source URL directly; API-key sources take a provider key, plus a second identifier where the provider needs one:
| --source | Credential flags |
|---|---|
| postgres | --connection-string (--url) |
| mysql | --connection-string (--url) |
| mariadb | --connection-string (--url) |
| redis | --connection-string (--url) |
| valkey | --connection-string (--url) |
| vercel-kv | --connection-string (--url) |
| neon | --source-key (--token) |
| supabase | --source-key + --source-secret (--db-password) |
| render | --source-key (--token) |
| railway | --source-key (--token) |
| planetscale | --source-key + --source-id (--token-id) |
| upstash | --source-key + --source-id (--email) |
| algolia | --source-key + --source-id (--app-id) |
| turso | --source-key + --source-id (--url) |
If a source exposes several databases, pass --source-db to pick one by number or by a substring of its name; in a non-interactive shell with more than one candidate and no --source-db, the command lists them and exits so nothing ambiguous runs.
Install the agent skill
agent init installs the Layerbase skill so Claude Code, Codex, and other agents can drive these commands and audit a codebase for providers Layerbase can replace. It fetches the always-current skill and prints an AGENTS.md snippet for agents that read one. See the agents guide for the full story.
lbase agent init # writes ./.claude/skills/layerbase/SKILL.md
lbase agent init --global # writes ~/.claude/skills/layerbase/SKILL.mdCloud databases in CI/CD
The headless pattern for CI is a transient database per run: create one with a --ttl, seed it, run the tests against it, then delete it. The --ttl is a safety net, so a job that crashes before the delete step still cannot strand a database against your quota: it self-destructs at expiry. Store the key as LAYERBASE_API_KEY in your repo secrets and nothing in the workflow touches a browser.
# .github/workflows/test.yml
jobs:
test:
runs-on: ubuntu-latest
env:
LAYERBASE_API_KEY: ${{ secrets.LAYERBASE_API_KEY }}
steps:
- uses: actions/checkout@v4
- run: npm i -g layerbase
- name: Create a transient database
id: db
run: |
URL=$(lbase cloud create ci-${{ github.run_id }} \
--engine postgresql --ttl 2h --json | jq -r '.connectionString')
echo "DATABASE_URL=$URL" >> "$GITHUB_ENV"
- name: Seed and test
run: |
psql "$DATABASE_URL" -f ./schema.sql
npm test
- name: Delete the database
if: always()
run: lbase cloud delete ci-${{ github.run_id }} --yesWhere the engine supports branching, a branch-per-PR flow is often the better primitive: branch from a database you have already seeded, so each PR gets an instant, real copy, and delete the branch on teardown.
# On pull_request: fork the seeded parent into a per-PR branch
- run: |
URL=$(lbase cloud branch seed-db pr-${{ github.event.number }} --json \
| jq -r '.connectionString')
echo "DATABASE_URL=$URL" >> "$GITHUB_ENV"
# On close: tear the branch down
- if: github.event.action == 'closed'
run: lbase cloud branch delete seed-db pr-${{ github.event.number }}The two patterns do not mix: a transient (--ttl) database cannot be branched because it is scheduled for destruction, so the branch parent must be a durable database. Attempting it fails with the branch_on_transient_forbidden error.
Exit codes
Cloud commands map the API status to a distinct exit code so a pipeline can react without parsing text. Anything else is 1; success is 0.
| Exit | HTTP | Meaning |
|---|---|---|
| 3 | 401 | Auth failed (bad or revoked key). |
| 4 | 402 | Account suspended for a failed payment. |
| 5 | 409 | Capacity conflict (name taken or pool exhausted). |
| 6 | 429 | Quota or rate limit reached (database limit or monthly programmatic-create limit). |
Over-limit and invalid-input responses also carry a machine-readable code, including programmatic_create_limit_reached, invalid_ttl, and branch_on_transient_forbidden. The API reference documents each one.
Local database reference
Everything below runs entirely on your machine with no account. These are the SpinDB engine commands, each usable as lbase <command>. The open-source engine still lives at github.com/robertjbass/spindb.
Quick start
lbase create myapp --start --connect
# Created PostgreSQL instance "myapp" on port 5432
# psql (18.1) ... myapp=#Data persists when a container is stopped unless you explicitly run lbase delete.
Native binaries, not containers
The SpinDB engine downloads real database binaries and runs them as native processes with isolated data directories. No Docker daemon, no VM overhead, same commands on macOS, Linux, and Windows (ARM and x64). Each database gets its own data directory, so there are no version conflicts and no shared state.
~/.spindb/containers/
├── postgresql/
│ └── myapp/
│ ├── container.json
│ ├── data/
│ └── postgres.log
├── mysql/
└── mongodb/Engines
Every engine works the same way. Learn one command set, use them all. Pass -e on create to pick one.
| Engine | Type | Command |
|---|---|---|
| PostgreSQL | Relational SQL | lbase create -e postgresql |
| MySQL | Relational SQL | lbase create -e mysql |
| MariaDB | Relational SQL | lbase create -e mariadb |
| SQLite | Embedded SQL | lbase create -e sqlite |
| DuckDB | Embedded OLAP | lbase create -e duckdb |
| MongoDB | Document Store | lbase create -e mongodb |
| FerretDB | Document Store | lbase create -e ferretdb |
| Redis | Key-Value | lbase create -e redis |
| Valkey | Key-Value | lbase create -e valkey |
| ClickHouse | Columnar OLAP | lbase create -e clickhouse |
| Qdrant | Vector Search | lbase create -e qdrant |
| Meilisearch | Full-Text Search | lbase create -e meilisearch |
| CouchDB | Document Store | lbase create -e couchdb |
| CockroachDB | Distributed SQL | lbase create -e cockroachdb |
| SurrealDB | Multi-Model | lbase create -e surrealdb |
| QuestDB | Time-Series | lbase create -e questdb |
| TypeDB | Knowledge Graph | lbase create -e typedb |
| InfluxDB | Time-Series | lbase create -e influxdb |
| Weaviate | Vector Database | lbase create -e weaviate |
| TigerBeetle | Financial Ledger | lbase create -e tigerbeetle |
Lifecycle
Full lifecycle control for every instance: create, start, stop, inspect, and delete.
lbase create mydb -e postgres
lbase create mydb -e mysql --db-version 8
lbase create mydb --start
lbase create mydb --from backup.sql
lbase start mydb
lbase stop mydb
lbase stop --all
lbase delete mydb -flbase list # ls is an alias
lbase list --json
lbase info mydb # status is an alias
lbase ports
lbase ports --running
lbase logs mydb
lbase logs mydb -f
lbase logs mydb --editorConnect and query
Native database shells, enhanced TUI clients, and structured query output.
# Native shells
lbase connect mydb
lbase connect mydb -d analytics
# Enhanced shells
lbase connect mydb --pgcli
lbase connect mydb --mycli
lbase connect mydb --litecli
lbase connect mydb --iredis
lbase connect mydb --dblab
lbase connect mydb --ui # built-in web UI (DuckDB only)# Run inline commands
lbase run mydb -c "SELECT 1"
lbase run mydb ./schema.sql
lbase run mydb -d analytics ./init.sql
# Structured output
lbase query mydb "SELECT * FROM users"
lbase query mydb "SELECT * FROM users" --jsonBackup, restore, clone, and pull
Full data lifecycle with automatic backups and remote sync.
lbase backup mydb
lbase backup mydb --format sql
lbase backup mydb --format custom
lbase backup mydb -o ~/backups
lbase backup mydb -d analytics
lbase restore mydb ./backup.sql
lbase restore mydb --from-url "postgresql://user:pass@host/db"# Clone locally
lbase stop mydb
lbase clone mydb mydb-copy
lbase start mydb-copy
# Pull from remote (auto-backup before replace)
lbase pull mydb --from "postgresql://user:pass@prod/db"
lbase pull mydb --from-env PROD_DB_URL
lbase pull mydb --from-env PROD_DB_URL --as mydb_prod
lbase pull mydb --from-env PROD_DB_URL --dry-runGit-driven branching
Fork any database copy-on-write, the way Neon and Vercel do: instant and near-zero-space on APFS / Btrfs / XFS-reflink / ZFS, with a full-copy fallback elsewhere. A running source is auto stopped, snapshotted, and restarted for you. Point it at your git branch and the database swaps in automatically while DATABASE_URL never changes.
# Fork a database (auto-starts it on its own port)
lbase branch myapp myapp-exp
# --json reports method: "reflink" (instant) or "copy"
lbase branch list
lbase branch reset myapp-exp
lbase branch delete myapp-exp --cascade# Your git branch drives your DB branch on a stable port
lbase branch init myapp
git checkout -b feature/x # DB swaps in automatically
git checkout main # base swaps back
# DATABASE_URL never changes
lbase branch status
lbase branch pruneLink remote databases
Manage cloud-hosted or external databases through the same commands. Auto-detects Neon, Supabase, PlanetScale, Upstash, Railway, Aiven, and more.
lbase link "postgresql://user:pass@ep-cool-123.neon.tech/mydb"
lbase link "mongodb+srv://user:pass@cluster.mongodb.net/mydb"
lbase link "redis://default:pass@us1-cat.upstash.io:6379"
# Custom name
lbase link "postgresql://user:pass@host/db" production-pg
# Use linked database
lbase connect production-pg
lbase url production-pg
lbase url production-pg --passwordConnection strings
Every engine provides a connection string. Copy it to the clipboard or pipe it straight into your app.
lbase url mydb
lbase url mydb --copy
lbase url mydb -d analytics
lbase url mydb --json
# Use in your app
export DATABASE_URL=$(lbase url mydb)
psql $(lbase url mydb)| Engine | URL Format |
|---|---|
| PostgreSQL | postgresql://postgres@127.0.0.1:5432/mydb |
| MySQL | mysql://root@127.0.0.1:3306/mydb |
| MariaDB | mysql://root@127.0.0.1:3307/mydb |
| MongoDB | mongodb://127.0.0.1:27017/mydb |
| Redis | redis://127.0.0.1:6379/0 |
| ClickHouse | clickhouse://default@127.0.0.1:9000/default |
| CockroachDB | postgresql://root@127.0.0.1:26257/defaultdb |
| SurrealDB | ws://root:root@127.0.0.1:8000/test/test |
| QuestDB | postgresql://admin:quest@127.0.0.1:8812/qdb |
| SQLite | sqlite:///path/to/file.sqlite |
| DuckDB | duckdb:///path/to/file.duckdb |
Databases inside a container
Create, rename, and drop databases inside a running container, and manage its users and credentials.
lbase databases create mydb analytics
lbase databases rename mydb old_name new_name
lbase databases drop mydb analytics --force
lbase databases list mydb
lbase databases set-default mydb prod
lbase databases refresh mydblbase users create mydb
lbase users create mydb devuser
lbase users create mydb --password p
lbase users create mydb --copy
lbase users create mydb --json
lbase users list mydbExport to Docker
The SpinDB engine does not need Docker, but it can package your database as a Docker image for deployment. Export generates a Dockerfile, docker-compose.yml, entrypoint script, TLS certificates, and credentials, ready for any container platform.
lbase export docker mydb -o ./deploy
cd ./deploy
docker compose build --no-cache
docker compose up -d
# Connect from host
source .env
psql "postgresql://$SPINDB_USER:$SPINDB_PASSWORD@localhost:$PORT/$DATABASE"Default ports
Ports are auto-assigned from these ranges. Override with --port on create.
| Engine | Default | Range | Notes |
|---|---|---|---|
| PostgreSQL | 5432 | 5432-5500 | |
| MySQL | 3306 | 3306-3400 | |
| MariaDB | 3307 | 3307-3400 | |
| MongoDB | 27017 | 27017-27100 | |
| FerretDB | 27017 | 27017-27100 | |
| Redis | 6379 | 6379-6400 | |
| Valkey | 6379 | 6379-6479 | |
| ClickHouse | 9000 | 9000-9100 | |
| Qdrant | 6333 | 6333-6400 | |
| Meilisearch | 7700 | 7700-7800 | |
| CouchDB | 5984 | 5984-6084 | |
| CockroachDB | 26257 | 26257-26357 | |
| SurrealDB | 8000 | 8000-8100 | |
| QuestDB | 8812 | 8812-8912 | |
| TypeDB | 1729 | 1729-1829 | |
| InfluxDB | 8086 | 8086-8186 | |
| Weaviate | 8080 | 8080-8180 | |
| TigerBeetle | 3000 | 3000-3100 | |
| SQLite | N/A | File-based | |
| DuckDB | N/A | File-based |
Local automation and scripting
The same JSON-everywhere design applies to the local engine: pipe it to jq, use it in scripts, or spin up a throwaway database on the CI runner itself (no account) when you do not need a real cloud database.
# Export connection string
export DATABASE_URL=$(lbase url mydb)
# Backup all PostgreSQL containers
for c in $(lbase list --json | jq -r \
'.[] | select(.engine=="postgresql") | .name'); do
lbase backup "$c" -o ./backups/
done
# Check if running
if lbase info mydb --json | \
jq -e '.status == "running"' > /dev/null; then
echo "Container is running"
fi# .github/workflows/test.yml
- name: Install Layerbase CLI
run: npm i -g layerbase
- name: Install database tools
run: lbase deps install --engine postgresql
- name: Create test database
run: lbase create testdb --start
- name: Run migrations
run: lbase run testdb ./schema.sql
- name: Run tests
run: npm test{
"scripts": {
"db:create": "lbase create myapp --start",
"db:reset": "lbase delete myapp -f && lbase create myapp --start",
"db:migrate": "lbase run myapp ./migrations/*.sql",
"db:seed": "lbase run myapp ./seeds/dev-data.sql",
"db:backup": "lbase backup myapp --format sql -o ./backups"
}
}Config and troubleshooting
Manage tool paths, run health checks, and resolve common issues.
lbase config show
lbase config detect
lbase config set psql /opt/pg/bin/psql
lbase config unset psql
lbase config update-check on
lbase engines
lbase engines download postgresql 18
lbase deps check
lbase deps install# Port conflict
lbase create mydb --port 5433
# Container won't start
lbase logs mydb
# Health check
lbase doctor
lbase doctor --fix
lbase doctor --dry-run
# Reset everything
rm -rf ~/.spindbTwo version commands do different things: lbase --version prints Layerbase's own CLI version, while lbase version runs the SpinDB engine's version command. Two independent overrides exist for the two cloud modes, and you do not normally need either: LAYERBASE_CLOUD_API_URL (default https://cloud.layerbase.dev) sets the direct /v1 base used with an API key, while LAYERBASE_API_URL (default https://layerbase.com) sets the base for the browser-login proxy.