Getting started

Use deck with AI agents

AI coding agents such as Claude Code and Codex, scripts and CI jobs can run deck with no person at a terminal. Pass every value as a flag, ask for JSON with --output json, and authenticate with an environment variable.


Authentication

Run deck auth login yourself, or let the agent run it in the background: deck prints the authorization URL first, so the agent can show it to you, and stores the token once you approve it in the browser.

Where there is no browser, such as a cloud agent or a CI job, set DECKRUN_TOKEN (and DECKRUN_TEAM_ID if you belong to more than one team). See Install Deck CLI.

No prompts

deck only asks a question when a person can answer it: stdin and stdout are terminals, no AI agent is detected and CI is not set. Otherwise, a missing value makes the command fail immediately with an error that names the flag to pass, for example:

region is required in non-interactive mode: pass --region

Destructive commands (delete, secrets unset) still require --yes without a terminal. Agents should never run them.

JSON output

These commands accept -o, --output text|json:

auth login, providers add, providers list, clusters create, clusters list, init, deploy, apps list, apps status, logs, releases list, secrets list, secrets set, domains add, domains list.

  • Text is the default for everyone. Detecting an agent never changes the output, so your scripts behave the same whoever runs them. Agents pass --output json.
  • On success, stdout holds exactly one JSON document: an array for lists ([] when empty) and an object otherwise. It is compact when stdout is not a terminal, and indented when it is.
  • logs and auth login write one JSON object per line instead.
  • Progress and messages go to stderr, without colours. Commands never prompt in JSON mode.
  • Field names are snake_case and timestamps are RFC 3339. Secret values never appear in any output.
  • Within a major version, fields may be added but are never renamed, removed or retyped.

Shapes

providers list (and providers add, one object):

[{"id":"prov-1","type":"digitalocean","description":"DO main"}]

clusters list (and clusters create, one object; with --no-wait the status is creating):

[{"id":"clu-1","name":"acme-prod","provider_id":"prov-1","region":"fra1","zone":"","size":"s-2vcpu-4gb","nodes":2,"auto_scale":false,"min_nodes":0,"max_nodes":0,"status":"created","load_balancer_ips":["203.0.113.10"]}]

apps list:

[{"id":"app-1","name":"web","cluster":"acme-prod","public_ip":"203.0.113.10","urls":["https://web.example.com"]}]

apps status: release is null when the app was never deployed, and failure only appears when the release failed. When the processes can't be fetched, processes is null and processes_error says why.

{"app":{"id":"app-1","name":"web","cluster":"acme-prod","urls":["https://web.example.com"]},
 "release":{"id":"rel-3","version":"v3","status":"error","image_url":"registry.example.com/web:3","created_at":"2026-11-06T10:00:00Z",
            "failure":{"category":"image_pull","message":"The image couldn't be pulled","hint":"Check the image exists","detail":"ErrImagePull"}},
 "processes":[{"name":"web","size":"small","autoscaling":{"min_replicas":2,"max_replicas":4},
               "pods":[{"name":"web-7d9f-abc","cpu":"2m","memory":"146Mi"}]}]}

releases list:

[{"id":"rel-2","version":"v2","status":"deployed","image_url":"registry.example.com/web:2","created_at":"2026-11-05T10:00:00Z"}]

deploy: urls is empty when the release exposes no HTTP process.

{"app":"web","release":{"id":"rel-4","version":"v4","status":"deployed"},"image":"registry.example.com/web:4","urls":["https://web.example.com"]}

init:

{"app":{"id":"app-9","name":"api","cluster":"acme-prod"},"config_file":"deckrun.toml","files_created":["Dockerfile"],"files_kept":[],"secrets_set":[]}

secrets list and secrets set (names only):

[{"name":"DATABASE_URL","updated_at":"2026-11-02T09:30:00Z"}]
{"secrets":["ALPHA","ZED"]}

domains list and domains add (public_ip is the address to point the DNS record at):

[{"id":"dom-1","name":"web.example.com","has_custom_cert":false}]
{"id":"dom-9","name":"shop.example.com","has_custom_cert":false,"public_ip":"203.0.113.10"}

logs (one line per log entry; --timestamps adds the timestamp to message):

{"pod":"web-7d9f-abc","container":"app","message":"GET /health 200"}

auth login (the first line is written before waiting for the approval):

{"event":"authorize","url":"https://app.deckrun.com/cli/authorize?port=53908&state=..."}
{"event":"logged_in","team":{"id":"team-1","name":"Acme"}}

Errors

In JSON mode, a failed command writes nothing to stdout, writes one JSON object as the last line of stderr, and exits with code 1. tip, suggested_command and details only appear when they have a value.

{"error":{"code":"missing_flag","message":"region is required in non-interactive mode: pass --region"}}
CodeMeaning
missing_flagA required value is missing. The message names the flag.
invalid_flagA flag or value is not valid.
unauthorizedNo valid token. suggested_command is deck auth login.
missing_teamNo active team. Set DECKRUN_TEAM_ID or run deck switch.
forbiddenThe token's user can't do this.
not_foundThe app, cluster, provider or service named doesn't exist.
conflictThe resource is not in a state that allows the operation.
payment_requiredThe team has no active subscription.
deployment_limit_reachedThe monthly deployment limit was reached. details has limit, used and cycle_resets_at.
invalid_provider_credentialsThe cloud provider rejected the stored credentials.
confirmation_requiredA destructive command needs --yes.
deploy_failedThe release failed in the cluster. details has release_version and the diagnosed failure.
internalAnything else. Read message.
Previous
Install Deck CLI