CLI

CLI

Install, authenticate, and drive Otterdeploy from the terminal.

The otterdeploy CLI drives everything the dashboard can — projects, services, databases, domains, deployments, logs, backups, and more — from your terminal and your CI. It talks to the same typed oRPC API as the web app.

Install

npm install -g @otterdeploy/cli
otterdeploy --version

Runs on Node ≥20 or Bun.

Authentication

otterdeploy login <url> runs a browser device-code flow and stores your token at ~/.config/otterdeploy/config.json (mode 0600). Commands that need auth trigger the same flow automatically the first time. An expired token is detected and re-authenticated in place.

Environment variables

VariablePurpose
OTTERDEPLOY_URLControl-plane URL (overrides stored config).
OTTERDEPLOY_TOKENAuth token — a session token or an otter_ API key. Bypasses the browser flow entirely (use in CI).
OTTERDEPLOY_CONFIG_DIROverride the config directory.
NO_COLORDisable ANSI color (also --no-color).
DEBUGPrint full stack traces on error.

For CI, mint a scoped key and export it:

otterdeploy tokens create --name ci --expires 90d --read-only
export OTTERDEPLOY_TOKEN=otter_...
export OTTERDEPLOY_URL=https://deploy.example.com

The config file

Most commands operate on a project described by an otterdeploy.json (or otterdeploy.ts) file in the working directory — a declarative manifest of services, databases, and compose stacks. deploy/sync/up reconcile the live project to match it; pull writes the server's manifest back to disk; status shows drift.

Files named otterdeploy.config.json / otterdeploy.config.ts (the pre-0.8 spelling) are still resolved, so an existing repo keeps working, and commands that rewrite the manifest (add, pull) keep writing to the file that is already there. Only init picks the new name. To move over, rename the file — nothing else changes.

{
  "$schema": "https://deploy.example.com/otterdeploy.schema.json",
  "version": 1,
  "project": "my-app",
  "databases": { "primary": { "engine": "postgres", "version": "16" } },
  "services": {
    "web": {
      "source": "git",
      "repo": "acme/my-app",
      "ports": [{ "container": 3000, "appProtocol": "http", "primary": true }],
      "env": { "DATABASE_URL": "${database:primary.url}" }
    }
  }
}

Commands that target a single resource take --slug <project> to override the project (otherwise it's read from the config's project field) and a resource name as a positional argument.

Global conventions

  • --json — machine-readable output on read commands (raw JSON to stdout, no decoration). Ideal for scripting.
  • --yes — skip confirmation prompts (destructive actions confirm by default).
  • --url <url> — override the control-plane URL for a single command.
  • Exit codes — non-zero on failure, on pending drift (status), on skipped manifest changes, and on a failed --wait deploy.

Shell completions

otterdeploy completions bash > /etc/bash_completion.d/otterdeploy
otterdeploy completions zsh  > "${fpath[1]}/_otterdeploy"
otterdeploy completions fish > ~/.config/fish/completions/otterdeploy.fish