Skip to main content

Headless CLI

Everything the desktop app does, from a terminal. Same instances, same Docker, same SQLite - no GUI required.

The portside-cli binary ships with every Portside release (Windows, macOS Intel + ARM, Linux) next to the desktop installers, or builds from source with cargo build --manifest-path src-tauri/Cargo.toml --bin portside-cli. It talks to the same %LOCALAPPDATA%\portside\portside.db metadata store and the same Docker daemon as the desktop app, so instances created headlessly show up in the GUI and vice versa. This is the surface scripts, CI pipelines, and agents that prefer shell over MCP should use.

Command reference​

CommandWhat it does
list [--json]List instances (id, engine, port, status, container)
catalog [--json]Show the bundled engine catalog (engine, tag, image)
create --engine E --tag T [--port 0] [--lan] [--password P] [--wait 60] [--json]Create an instance; --port 0 means auto-assign; waits until healthy, then prints connect strings
start <id> [--json]Start a stopped instance
stop <id> [--json]Stop a running instance (data kept)
rm <id> [--wipe] [--json]Drop the container; --wipe also drops the data volume (irreversible)
db list <id> [--json]List databases inside an instance
db create <id> <name> [--json]CREATE DATABASE (SQL engines; names must be [A-Za-z0-9_]+)
db drop <id> <name> [--json]DROP DATABASE (SQL engines)
conn <id> [--lan] [--strict] [--json]Print connect strings without touching Docker
logs <id> [--tail 200] [--json]Tail container logs (not available for imported servers)
health <id> [--wait 0] [--json]Container/tcp/query health; --wait SEC polls until healthy
mcpRun the MCP server over stdio (see Agents and MCP)
setup-agent --host H [--project-dir D]Register the MCP server with a coding-agent harness (see Agents and MCP)
setup-path [--remove]Add (or remove) this binary's directory to the user PATH

The agent contract: --json and exit codes​

Every command prints human-readable text by default and a single JSON object with --json, which is what agents and scripts should always pass. Errors go to stderr as {"error": "...", "hint": "..."} and never include secrets beyond the connection object itself.

ExitMeaning
0Success
1Usage/validation error (bad engine, bad port, bad database name)
2Runtime error (Docker down, unhealthy after --wait) - the hint usually says to start Docker Desktop
portside-cli create --engine postgres --tag 17 --wait 60 --json
# {"id": "a1b2c3d4", "engine": "postgres", "tag": "17", "port": 5433,
# "status": "running", "localhost_url": "postgres://postgres:[email protected]:5433/postgres",
# "lan_url": null, "tls": false}

create --wait polls health once per second (container running, TCP open, live query OK) and only returns once the instance is actually ready, so one call replaces create-then-poll loops. Pass --wait 0 to skip waiting.

Recipes​

CI ephemeral database​

portside-cli create --engine postgres --tag 17 --wait 120 --json > db.json
export DATABASE_URL=$(python -c "import json; print(json.load(open('db.json'))['localhost_url'])")
# ... migrate + test against $DATABASE_URL ...
portside-cli rm $(python -c "import json; print(json.load(open('db.json'))['id'])") --wipe

The instance is fully isolated per run; --wipe guarantees no volume survives to the next job. The same shape works for MySQL/MariaDB (mysql://...) and Redis (redis://.../0).

Scratch-instance hygiene​

portside-cli list --json
portside-cli rm <id> --wipe # per finished experiment

Inactivated instances still hold their volumes; list shows what is stopped versus running, and rm --wipe reclaims disk. See Data and Lifecycle for the exact remove-versus-wipe contract, which the CLI honors identically.

Troubleshooting​

SymptomCause and fix
{"error": ..., "hint": "is Docker running? ..."} exit 2Docker Desktop is down - start it and retry; the CLI never starts the daemon itself
instance <id> not healthy after NsThe container started but the engine is not answering yet (large image first boot); check portside-cli logs <id> --tail 40, then retry with a longer --wait
no LAN address detected from conn --lanThe machine is offline or loopback-only; retry without --lan
Live-Docker test flakes in CIThe repo's own E2E suite must run serially (--test-threads=1): parallel image pulls flake on Docker Desktop's named pipe