# CLI Commands

Reference for the `keel` CLI. Access levels are **admin** > **developer** > **viewer**. The badges describe the IAM-role path. When you sign in as an operator, the control plane checks the command's capability grant instead. Commands without a badge may still require AWS credentials or an operator grant.

## Global flags

Available on every command:

| Flag | Type | Default | Description |
|---|---|---|---|
| `--region` | string | — | AWS region override |
| `-v, --verbose` | bool | `false` | Enable debug logging |
| `--config` | string | `./keel.yml` | Config file path |
| `--env` | string | — | Target environment (overrides `KEEL_ENV` and the active environment) |
| `--no-color` | bool | `false` | Disable color output |
| `--cautious` | bool | `false` | Preview and confirm every action before changing AWS state |

## Core

### `keel init`
Initialize a new Keel project by creating a `keel.yml` in the current directory. Interactive: app name (default: directory name), region (default `us-east-1`), service port (default `8080`), networking and load-balancer choices, pipeline source (default `github`) and repository. Errors if the config file already exists.

Keel also writes an annotated `keel.yml.example` with every supported option. It does not overwrite an existing example or read it as configuration.

### `keel up` <Badge type="danger" text="admin" />
Provision all infrastructure defined in `keel.yml`. Generates OpenTofu configuration and runs plan + apply to create the VPC, ECS cluster, ALB, ECR repositories, sites, schedules, and any add-on services. Registers the app+environment in the DynamoDB registry and (when Keel manages the CodeCommit repo) adds a git remote named `keel`.

New services start at zero tasks until the first image is built. `keel up` offers to run the initial deployment; use `--deploy -y` to run it without prompts.

### `keel deploy [service-or-site]` <Badge type="warning" text="developer" />
Deploy one service or site, or the entire application. Keel runs git preflight checks, builds with CodeBuild or `--local`, registers service task definitions, runs the release command, promotes services, and then publishes sites. Sites-only deployments skip container build phases; local site builds also skip git preflight. See [`build_in`](../guide/static-sites#where-the-build-runs).

| Flag | Default | Description |
|---|---|---|
| `--watch` | `false` | Stay attached and stream deployment progress |
| `--local` | `false` | Build locally instead of using CodeBuild |
| `--lock-timeout` | `30` | Deploy lock timeout in minutes |
| `--force` | `false` | Override an existing deploy lock |
| `--skip-release` | `false` | Skip the release command for this deploy |
| `--release-timeout` | — | Override `release.timeout` for this deploy (e.g. `20m`) |
| `--allow-dirty` | `false` | Deploy with uncommitted changes in the working tree |
| `-y, --yes` | `false` | Accept every preflight warning without prompting |
| `--no-push` | `false` | Never push to the Keel-managed source repository |

The release timeout must fit inside `--lock-timeout`.

### `keel deploy reconcile` <Badge type="warning" text="developer" />
Inspect incomplete deployment records against live AWS state. For each record, Keel can resume it, mark it succeeded or failed, or skip it, then release any stale deploy lock.

### `keel rollback [service]` <Badge type="warning" text="developer" />
Point a service to a previous task-definition revision without rebuilding or rerunning the release command.

| Flag | Default | Description |
|---|---|---|
| `--to` | `0` | Roll back to a specific task definition revision (default: the previous one) |
| `--watch` | `false` | Stay attached and stream rollback progress |
| `--lock-timeout` | `30` | Deploy lock timeout in minutes |
| `--force` | `false` | Override an existing deploy lock |

Refuses to roll back to a revision whose image has been expired from ECR.

### `keel status`
Show the current status of all ECS services: running task count, health, last deployment time, and CPU/memory utilization.

### `keel ps`
List running tasks with task ID, availability zone, revision, uptime, health, CPU, and memory. Per-task metrics can reveal uneven load hidden by service averages. Missing metrics appear as `—`. Flags: `--json`, `--service`.

### `keel cost`
Estimate the monthly cost of what `keel.yml` describes — the provisioned baseline, priced from config alone with **no AWS credentials**. Usage-driven costs are listed with their rates; override assumptions with flags (`--deploys 10`, `--build-minutes 4`, `--job-minutes 5`, `--nat-gb`, `--log-gb`, `--transfer-gb`, `--site-gb`, `--site-requests`, `--site-build-mb`, `--image-gb 5`). `--all` covers every environment; `--json` for machines. See [Cost Estimates & Budgets](../guide/cost).

- `cost budget` <Badge type="danger" text="admin" /> — create/update an AWS Budget filtered by this environment's tags. `--email` (required, repeatable), `--limit` (default: derived from the estimate), `--threshold` (default `80,100`), `--headroom` (default `40`), `--remove`
- `cost budget-show` — the limit, actual spend, forecast, and alert thresholds

### `keel history`
Show the deployment history for this application environment. `--limit` (default `20`) caps the number of entries. The history is Keel's own record of what it deployed — not a view of AWS: a service updated outside Keel leaves no entry, and entries survive the environment they describe being torn down.

### `keel history prune` <Badge type="danger" text="admin" />
Permanently delete deployment history for this app and environment. Keel confirms with the record count; `-y, --yes` skips the prompt. `keel destroy` keeps history unless you pass `--prune-history`.

### `keel rescue` <Badge type="danger" text="admin" />
Diagnose an interrupted operation by reporting the state lock and resources absent from OpenTofu state. Repairs require explicit flags. Keel prints deletion commands for untracked resources but does not run them. See [Interruptions & Rescue](../guide/rescue).

| Flag | Default | Description |
|---|---|---|
| `--unlock` | `false` | Release the state lock (only when nothing holds it; always confirms) |
| `--converge` | `false` | Re-run the apply to finish an interrupted `keel up` |
| `--finish-destroy` | `false` | Re-run the teardown to finish an interrupted `keel destroy` |
| `--delete-retained` | `false` | With `--finish-destroy`, delete `retain: true` resources instead of leaving them |

`--converge` and `--finish-destroy` together is an error. Within one run the order is: report → unlock → converge/finish-destroy.

### `keel plan`
Preview all infrastructure changes that will be applied on the next `keel up`.

### `keel dashboard`
Launch the interactive TUI dashboard for the selected environment. Live ECS/RDS/ElastiCache/ALB/CloudWatch/SSM state; actions are gated by your access level or operator grants inside the TUI. `--direct` bypasses the control plane for an IAM session. See [The Dashboard](../guide/dashboard).

### `keel destroy` <Badge type="danger" text="admin" />
Tear down all infrastructure Keel provisioned for this application. Resources declared `retain: true` are left running in AWS and released from Keel's state; the database is deleted after a final snapshot unless `--no-final-snapshot`. Confirmation requires typing the app name.

| Flag | Default | Description |
|---|---|---|
| `--force` | `false` | Skip confirmation prompt |
| `--delete-retained` | `false` | Delete resources declared `retain: true` instead of leaving them in AWS |
| `--no-final-snapshot` | `false` | Delete the database without taking a final snapshot |
| `--prune-history` | `false` | Also delete this environment's deployment history (irreversible) |

A successful destroy also releases the deploy lock if one was held, and reports how many deployment records were kept. An interrupted destroy points at `keel rescue`.

### `keel apps`
List all Keel applications registered in the DynamoDB app registry for the current AWS account. The registry is created by the first `keel up`.

### `keel version`
Print the version, commit hash, and build date of the Keel binary.

### `keel license` and `keel licenses`
`keel license` reports the active edition, expiry, source, and every gated capability (`--json`). `keel licenses` prints Keel's Apache-2.0 licence; `--third-party` prints attribution for every module linked into the binary.

### `keel orphans`
List AWS resources tagged as Keel-managed that no current `keel.yml` declares, with their estimated cost (`--json`).

## Runtime operations

### `keel logs [service]`
Stream or tail CloudWatch logs for one service (default: the first service in `keel.yml`).

| Flag | Default | Description |
|---|---|---|
| `-f, --follow` | `false` | Stream logs in real time |
| `--since` | `10m` | How far back to fetch logs (e.g. `10m`, `1h`, `2d`) |
| `--filter` | — | CloudWatch Logs filter pattern |

### `keel exec [service]` <Badge type="warning" text="developer" />
Open an interactive shell or run a command inside a running container via ECS Exec. `-c, --command` defaults to `/bin/sh`; `--reason` is recorded beside the transcript when session audit is enabled.

Keel speaks the SSM session protocol itself, so the normal path needs neither the AWS CLI nor `session-manager-plugin`. `--legacy-session` (or `KEEL_LEGACY_SESSION=1`) uses those external tools as an escape hatch.

### `keel run -- <command>...` <Badge type="warning" text="developer" />
Run a one-off command — a migration, a backfill, a console — as a **new** ECS task using a service's task definition, IAM role, security group, and environment.

| Flag | Default | Description |
|---|---|---|
| `-s, --service` | release service | Service whose task definition and role to use |
| `-i, --interactive` | `false` | Attach a terminal, for a console or a REPL |
| `--timeout` | `1h` | Give up and stop the task after this long |
| `--cpu` | — | Task CPU units for this run (e.g. `1024`) |
| `--memory` | — | Task memory in MiB for this run (e.g. `2048`) |

Interactive `run -i` still needs the AWS CLI and `session-manager-plugin`; unlike `keel exec`, it attaches to a newly launched task through the legacy external session path.

### `keel scale <service> <count>` <Badge type="warning" text="developer" />
Set the ECS desired task count for a service. Scheduled tasks are not supported because EventBridge starts one task per event.

### `keel autoscale`
- `autoscale show [service]` — what scales each service, configured *and* live, with drift called out (`--json`). A difference usually means an apply is pending, so both halves are reported.
- `autoscale set <service>` <Badge type="danger" text="admin" /> — write autoscaling to `keel.yml`; only the flags you pass change. Flags: `--min`, `--max`, `--cpu`, `--memory`, `--requests`, `--queue`, `--backlog-per-task`, cooldowns, and matching `--no-*` flags. A queue target is required for `min: 0`. Nothing changes in AWS until `keel up` — OpenTofu owns the scalable target.
- `autoscale off <service>` <Badge type="danger" text="admin" /> — remove the service's autoscaling block

### `keel jobs`
- `jobs list [service]` — scheduled tasks: what's declared, what's live in EventBridge Scheduler, and when each next fires (computed locally — Scheduler doesn't report it). Drift, including a schedule pinned to a stale revision, is called out (`--json`)
- `jobs run <service>` <Badge type="warning" text="developer" /> — run one firing now, using the schedule's own task definition, network config, and security group; the schedule itself is untouched (`--timeout`, default `15m`)

### `keel sites`
- `sites list [site]` — configured vs live state per site: bucket, where it builds, object count, size, URL (`--json`). A provisioned-but-never-deployed site serves a 403, and the listing says so
- `sites deploy [site]` <Badge type="warning" text="developer" /> — build the site where `build_in` says (printed before it starts), publish it, and invalidate the CDN with a single `/*` path. In CodeBuild the build clones the commit and uploads the files itself; locally it uploads changed files (compared by content hash) and deletes removed ones. Flags: `--local`, `--skip-build`, `--no-delete`, `--dry-run`
- `sites verify <site>` <Badge type="danger" text="admin" /> — for `domain.dns: external`: read the us-east-1 certificate from ACM and record `cdn.certificate_issued` in `keel.yml` so the next apply can attach the alias

## Services & environments

### `keel services`
- `services list` — list configured services with type and status
- `services add <type>` — add a `database` or `cache` add-on to `keel.yml` (run `keel up` after)
- `services remove <type>` — remove an add-on service

### `keel env`
- `env list` — list environments, regions, accounts, and the active one
- `env use <environment>` — persist the active environment for this app under `~/.keel`
- `env current` — show the environment the current context targets
- `env add <environment>` — declare a new environment in `keel.yml` while preserving comments. Flags: `--env-region` (defaults to the app region) and `--account` (enables account isolation). Adding the first named environment also writes the existing implicit `default` environment. Run `keel up --env <name>` to provision it.

### `keel db`
Inspect the managed database. The master password is generated and rotated by AWS and stored in Secrets Manager; Keel never holds a copy. See [Database Credentials](../database-credentials).

- `db credentials` (alias `creds`) <Badge type="danger" text="admin" /> — host, port, database, username, and the password masked; `--show` prints it
- `db url` <Badge type="danger" text="admin" /> — one-line admin connection string (`postgresql://...` or `mysql://...`), e.g. `psql "$(keel db url)"`
- `db shell` (aliases `psql`, `mysql`) <Badge type="danger" text="admin" /> — open a local database client through a Keel tunnel; the password is passed through the process environment rather than its argument list
- `db grant <iam-user>` / `db revoke <iam-user>` <Badge type="danger" text="admin" /> — create or drop a per-person database role when `database.iam_auth` is enabled (Pro)
- `db logs` — tail database engine logs from CloudWatch. Requires `database.log_exports` in `keel.yml`; Keel reports the missing setting when exports are disabled. Flags: `-f/--follow`, `--since 10m`, `--filter`, `--type`
- `db resize <instance-class>` <Badge type="danger" text="admin" /> — write the new size to `keel.yml`, then review with `keel plan`. Never calls the AWS API directly (OpenTofu owns the size); the output names the consequences — RDS restarts the instance, single-AZ means downtime, multi-AZ fails over

### `keel cache`
- `cache resize <node-type>` <Badge type="danger" text="admin" /> — same contract as `db resize`; ElastiCache replaces the nodes, and cached data is lost unless a replica can fail over

The database lives in private subnets, so `db shell` and third-party clients use `keel tunnel @database`. Keel ships no database client; install `psql` or `mysql` locally.

## Config variables & settings

### `keel config`
Values are stored in SSM Parameter Store as SecureStrings at `/keel/<app>/<env>/<key>` and injected into ECS task definitions.

- `config set KEY=VALUE [KEY=VALUE ...]`
- `config get KEY`
- `config list` — names/type/version only; values never printed. Also lists the AWS-managed block (e.g. `@database credentials` and the `DATABASE_*` variables it injects) so the full set of variables your containers see is in one place.
- `config unset KEY [KEY ...]`

Managed variable names (`DATABASE_USER`, `DATABASE_PASSWORD`, `DATABASE_CREDENTIALS`) are refused by `set`/`get`/`unset` — they come from a secret AWS generates and rotates; read them with `keel db credentials`.

### `keel settings`
Project-level CLI behavior, persisted in `keel.yml` and shared across environments.

- `settings` / `settings list` — show all settings
- `settings get <setting>` — print one value
- `settings set <setting> <true|false>` — change and persist (`verbose`, `cautious`)

## Resources & access

### `keel resources`
Summarize built-in (`@vpc`, `@cluster`, `@registry`, `@load-balancer`, `@logs`, `@pipeline`, `@database`, `@cache`, `@domain`, `@waf`), managed, and linked resources with bindings, IAM capabilities, network rules, and ownership. Flags: `--service`, `--managed`, `--existing`, `--json`.

- `resources show <name>` — effective bindings and rules for one resource (`--json`)
- `resources add <type> <name>` — declare a managed `s3`, `sns`, or `sqs` resource. Flags: `--bind SERVICE:CAP[,CAP]` (repeatable), `--versioning`, `--fifo`, `--visibility-timeout` (default `30`), `--retain` (default `true`)
- `resources link <arn-or-identifier>` — link an existing resource. Flags: `--as` (required), `--type`, `--url`, `--endpoint`, `--port`, `--security-group`, `--secret-arn`, `--secret-env`, `--kms-key-arn`, `--bind`
- `resources bind <resource> <service>` — grant capabilities. Flags: `--access` (required, comma-separated), `--prefix` (S3), `--env`
- `resources unbind <resource> <service>`
- `resources remove <name>` — refuses while bound (`--force`) or protected by `retain: true`

### `keel resources status`
The live state of the managed database, cache, and load balancer, with metrics — the terminal view of the dashboard's Resources tab, including topology (read replicas, Aurora cluster members, cache replicas). A declared resource that doesn't exist in AWS reports `NOT DEPLOYED` rather than being omitted. `--json`.

### `keel access`
- `access show <service>` — the service's task role, security group, bindings, add-ons, and custom IAM (`--json`)
- `access explain <service> <resource>` — why a service can access a resource

## Domains & WAF

### `keel domains`
A domain needs a zone, a certificate, and records; `domain.dns` records who owns the zone (`route53` \| `route53-managed` \| `external`) and decides how much Keel does for you. See [Custom Domains](../guide/domains-and-waf).

- `domains add <domain>` <Badge type="danger" text="admin" /> — writes `keel.yml` (scoped to the environment) and nothing else; run `keel up` after. Looks the zone up once and records the mode; a zone that doesn't exist requires you to choose `--dns route53-managed` or `--dns external`. Flags: `--zone`, `--zone-id`, `--dns`, `--alias` (repeatable), `--certificate-arn`, `--no-force-https`
- `domains remove <domain>` <Badge type="danger" text="admin" /> — removes it from `keel.yml`; the next `keel up` destroys the certificate and alias records. A hosted zone Keel created is **never** destroyed (recreating one invalidates the registrar delegation)
- `domains status` (alias `list`) — the live state: zone, **delegation checked with a real DNS query** (Route 53 reports a healthy zone even when the registrar points elsewhere), certificate, and the single next thing to do (`--json`)
- `domains records` — every record someone still has to create, in provider-form shape; empty means nothing left (`--json`)
- `domains verify` <Badge type="danger" text="admin" /> — recheck end to end and, for `dns: external`, record an issued certificate in `keel.yml` so the next apply attaches the HTTPS listener. `--wait` polls (`--timeout 15m`)

### `keel waf`
- `waf enable` <Badge type="danger" text="admin" /> — default rule set `AWSManagedRulesCommonRuleSet`; `--rules` for a comma-separated list
- `waf disable` <Badge type="danger" text="admin" />
- `waf status`

## Infrastructure

### `keel infra`
- `infra plan` <Badge type="danger" text="admin" /> — preview infrastructure changes; `--json` emits OpenTofu's JSON plan schema on stdout. `--keep-plan` leaves the saved plan in place so the next apply is bound to it (by default the plan file is discarded when the command ends — it embeds a state snapshot including sensitive values).
- `infra apply` <Badge type="danger" text="admin" /> — apply pending changes and converge ECS services against the resulting infrastructure. When a saved plan exists (from `infra plan --keep-plan`), the change set that runs is exactly the one that was displayed; otherwise a fresh apply.
- `infra output` — show outputs from the last apply (ALB DNS name, ECR URLs, VPC IDs)
- `infra history` — applies and destroys, including failed or interrupted operations, with actor, commit, plan summary, and resulting state (`--limit`, `--json`)
- `infra history prune` <Badge type="danger" text="admin" /> — permanently remove this environment's apply history (`-y`)
- `infra unlock <LOCK_ID>` <Badge type="danger" text="admin" /> — release a stale OpenTofu state lock left by an interrupted operation. `LOCK_ID` is the "ID:" field of the "Error acquiring the state lock" message. Confirms first; `--yes` for scripts. Only break a lock nobody holds. (`keel rescue` finds the lock ID and holder for you — see [Interruptions & Rescue](../guide/rescue).)

## Control plane and audit

### `keel api`

- `api deploy` <Badge type="danger" text="admin" /> — create or update the account-level control-plane Lambda, Function URL, roles, and cache
- `api status` — report what is deployed and whether this client can reach it (`--json`)
- `api whoami` — ARN, role, level, authorization path, operator label, and stable principal established by the server
- `api destroy` <Badge type="danger" text="admin" /> — remove the control plane; direct IAM operation continues

See [The Keel Control Plane](../guide/control-plane).

### `keel audit`

- `audit decisions` — authorization decisions from the last seven days by default. Flags: `--outcome allowed|denied`, `--who`, `--since-hours`, and `--all` to include routine reads
- `audit sessions` — recorded interactive sessions in this environment
- `audit transcript <stream>` — print one recorded session

Decision reads use the account-scoped `audit` capability. Session listings and transcripts use the environment-scoped `transcript` capability. See [Audit Records](../guide/audit).

## Operators and grants

### `keel auth idp`

- `idp setup` — create the account-level operator Cognito pool, identity pool, hosted sign-in page, and federated role (`--hosted-ui-prefix`; elevated key)
- `idp status` — show what is provisioned
- `idp add <email>` / `idp remove <email>` — begin or end an enrollment; removal also drops the principal's grants
- `idp suspend <email>` / `idp restore <email>` — disable and restore access without changing grants
- `idp list` — enrolled operators with principal and provider subject
- `idp export` — print the one account-wide sign-in token (`--json` for the provider document)
- `idp login` — browser sign-in (`--token` for first use, or `--provider` for the scriptable file form)
- `idp logout` — remove local authentication while remembering the provider; `--forget` removes both
- `idp destroy` — remove the operator identity provider (`--force`; elevated key)

### `keel auth grants`

- `grants list` — your rows, or the whole grid for an administrator (`--json`)
- `grants allow <operator> <capability>` / `deny` — write a row scoped with `--app`, `--env`, or `--platform`; `--reason` records why
- `grants revoke <operator> <capability>` — remove a row

An operator may be named by email or stable `prn_…` principal. See [Operators](../guide/operators) for capability kinds and precedence.

## Shared platforms and previews

### `keel platform`

`platform init`, `up`, `show`, `list`, `set`, `export`, `apply -f`, `lock`, and `destroy` manage one shared VPC and ECS cluster. An EC2 fleet is configured with keys such as `capacity.ec2.instance_types`; the same cluster may host Fargate and the fleet. `destroy` refuses while tenants remain, even with `--force`.

### `keel preview`

`preview name`, `create`, `list`, `destroy`, and `reap` manage branch-named environments synthesized from `preview:`. Pull-request numbers are optional metadata rather than names. Creation/listing require Pro; teardown remains available regardless of license state.

## Auth & teams

### `keel auth`
- `auth login` — start a session, prompting for an MFA code. `--mfa-serial` overrides device discovery. Sessions last 8 hours and are not auto-renewed.
- `auth logout` — clear the cached session; `--purge` also removes the stored access key
- `auth setup` — guided one-time setup: creates the three roles, your Keel admin user, and its MFA device. `--dry-run` prints every policy document without touching AWS; `--force` skips confirmation. Needs elevated AWS credentials, held in memory only.
- `auth whoami` — current identity, account, role, access level, and credential expiry
- `auth join` — configure auth from an admin-issued access key. `--account` (required), `--role` (expectation only; IAM decides)
- `auth connect github <organization>` <Badge type="danger" text="admin" /> — create the CodeConnections connection CodeBuild uses to clone from GitHub, register the account source credential, print the console authorization URL, and wait (`--timeout`, default `15m`). Follow [Connecting GitHub](../github-connections), especially the required App Installation selection.

### `keel auth mfa`
- `mfa enroll` — register an authenticator app against your Keel IAM user; the secret never leaves your machine
- `mfa status` <Badge type="danger" text="admin" /> — enforcement per role and enrollment per member
- `mfa open` <Badge type="danger" text="admin" /> — grant enrollment rights to existing members when migrating a team to MFA (`--force`)
- `mfa enforce` <Badge type="danger" text="admin" /> — require MFA to assume any Keel role (`--disable` to remove, `--force` to skip confirmation)

### `keel auth team`
- `team list` <Badge type="tip" text="viewer" /> — list members (`--all` includes disabled)
- `team add <email> <role>` <Badge type="danger" text="admin" /> — provision a member's IAM user and access key, pending MFA enrollment (`--no-user` for SSO-managed accounts, `--force`)
- `team activate <email>` <Badge type="danger" text="admin" /> — grant access once they have enrolled exactly one MFA device
- `team reenroll <email>` <Badge type="danger" text="admin" /> — reset a lost MFA device and reissue their key (`--keep-key`, `--force`)
- `team remove <email>` <Badge type="danger" text="admin" /> — revoke access, delete their access keys and IAM user (`--purge`, `--force`, `--allow-self`)
- `team set <email> <role>` <Badge type="danger" text="admin" /> — change a member's role, re-scoping their IAM user

## Diagnostics

### `keel debug`
- `debug logs` — recent diagnostic log entries (`-n` lines, default `50`; `-f, --follow`; `--all`)
- `debug path` — print the log file path (`~/.keel/logs/keel.log`)
- `debug info` — version, platform, and log diagnostics for bug reports
