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. logsandauth loginwrite 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"}}
| Code | Meaning |
|---|---|
missing_flag | A required value is missing. The message names the flag. |
invalid_flag | A flag or value is not valid. |
unauthorized | No valid token. suggested_command is deck auth login. |
missing_team | No active team. Set DECKRUN_TEAM_ID or run deck switch. |
forbidden | The token's user can't do this. |
not_found | The app, cluster, provider or service named doesn't exist. |
conflict | The resource is not in a state that allows the operation. |
payment_required | The team has no active subscription. |
deployment_limit_reached | The monthly deployment limit was reached. details has limit, used and cycle_resets_at. |
invalid_provider_credentials | The cloud provider rejected the stored credentials. |
confirmation_required | A destructive command needs --yes. |
deploy_failed | The release failed in the cluster. details has release_version and the diagnosed failure. |
internal | Anything else. Read message. |