Skip to content
Browse docs

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.

drop-in
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 myapp

lbase 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.

install
npm i -g layerbase
# or: pnpm add -g layerbase
# or: bun add -g layerbase

This 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:

headless auth
# 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.

cloud lifecycle
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 usage
lbase cloud create <name> --engine <e> [--ttl 2h] [--json]

Provision 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.

lbase cloud delete <db> --yes

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.

lbase cloud start / stop <db>

Start a stopped or hibernated database, or stop a running one. Data is kept either way.

lbase cloud branch <db> <name>

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.

lbase cloud ls

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.

lbase cloud connect <db>

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.

lbase cloud connection-string <db>

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.

lbase cloud clone <db> [name]

Create and populate a matching local container from a cloud database. Defaults the local name to the cloud database name.

lbase login / logout / whoami

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.

lbase psql / mysql / redis-cli <db>

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.

Postgres family

Passed via a PGPASSFILE temp 0600 file, deleted on exit.

MySQL / MariaDB

Passed via a --defaults-extra-file temp 0600 file, deleted on exit.

Redis / Valkey

Passed via the REDISCLI_AUTH environment variable.

Other engines

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.

cloud clone
lbase cloud clone my-db            # into a container named "my-db"
lbase cloud clone my-db local-copy # into a container you name

Scripting 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 & import
# 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 --json

migrate 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:

--sourceCredential 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.

agent init
lbase agent init            # writes ./.claude/skills/layerbase/SKILL.md
lbase agent init --global   # writes ~/.claude/skills/layerbase/SKILL.md

Cloud 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.

transient database per run (GitHub Actions)
# .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 }} --yes

Where 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.

branch per pull request (GitHub Actions)
# 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.

ExitHTTPMeaning
3401Auth failed (bad or revoked key).
4402Account suspended for a failed payment.
5409Capacity conflict (name taken or pool exhausted).
6429Quota 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

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.

data storage
~/.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.

EngineTypeCommand
PostgreSQLRelational SQLlbase create -e postgresql
MySQLRelational SQLlbase create -e mysql
MariaDBRelational SQLlbase create -e mariadb
SQLiteEmbedded SQLlbase create -e sqlite
DuckDBEmbedded OLAPlbase create -e duckdb
MongoDBDocument Storelbase create -e mongodb
FerretDBDocument Storelbase create -e ferretdb
RedisKey-Valuelbase create -e redis
ValkeyKey-Valuelbase create -e valkey
ClickHouseColumnar OLAPlbase create -e clickhouse
QdrantVector Searchlbase create -e qdrant
MeilisearchFull-Text Searchlbase create -e meilisearch
CouchDBDocument Storelbase create -e couchdb
CockroachDBDistributed SQLlbase create -e cockroachdb
SurrealDBMulti-Modellbase create -e surrealdb
QuestDBTime-Serieslbase create -e questdb
TypeDBKnowledge Graphlbase create -e typedb
InfluxDBTime-Serieslbase create -e influxdb
WeaviateVector Databaselbase create -e weaviate
TigerBeetleFinancial Ledgerlbase create -e tigerbeetle

Lifecycle

Full lifecycle control for every instance: create, start, stop, inspect, and delete.

create & control
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 -f
inspect
lbase 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 --editor

Connect and query

Native database shells, enhanced TUI clients, and structured query output.

connect
# 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)
query
# 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" --json

Backup, restore, clone, and pull

Full data lifecycle with automatic backups and remote sync.

backup & restore
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 & pull
# 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-run

Git-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.

branch
# 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
git-driven branching
# 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 prune

Link remote databases

Manage cloud-hosted or external databases through the same commands. Auto-detects Neon, Supabase, PlanetScale, Upstash, Railway, Aiven, and more.

link
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 --password

Connection strings

Every engine provides a connection string. Copy it to the clipboard or pipe it straight into your app.

url
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)
EngineURL Format
PostgreSQLpostgresql://postgres@127.0.0.1:5432/mydb
MySQLmysql://root@127.0.0.1:3306/mydb
MariaDBmysql://root@127.0.0.1:3307/mydb
MongoDBmongodb://127.0.0.1:27017/mydb
Redisredis://127.0.0.1:6379/0
ClickHouseclickhouse://default@127.0.0.1:9000/default
CockroachDBpostgresql://root@127.0.0.1:26257/defaultdb
SurrealDBws://root:root@127.0.0.1:8000/test/test
QuestDBpostgresql://admin:quest@127.0.0.1:8812/qdb
SQLitesqlite:///path/to/file.sqlite
DuckDBduckdb:///path/to/file.duckdb

Databases inside a container

Create, rename, and drop databases inside a running container, and manage its users and credentials.

databases
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 mydb
users
lbase 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 mydb

Export 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.

export docker
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.

EngineDefaultRange
PostgreSQL54325432-5500
MySQL33063306-3400
MariaDB33073307-3400
MongoDB2701727017-27100
FerretDB2701727017-27100
Redis63796379-6400
Valkey63796379-6479
ClickHouse90009000-9100
Qdrant63336333-6400
Meilisearch77007700-7800
CouchDB59845984-6084
CockroachDB2625726257-26357
SurrealDB80008000-8100
QuestDB88128812-8912
TypeDB17291729-1829
InfluxDB80868086-8186
Weaviate80808080-8180
TigerBeetle30003000-3100
SQLiteN/AFile-based
DuckDBN/AFile-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.

scripting patterns
# 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
local database in CI (GitHub Actions)
# .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
package.json scripts
{
  "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.

configuration
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
troubleshooting
# 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 ~/.spindb

Two 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.