CLI reference

This page documents every Ring CLI subcommand. Run ring <command> --help for the canonical list of flags on your installed version.

Global

ring --help

Print the list of subcommands.

ring --version

Print the installed Ring version.

Global options

--context / -c

Use a specific context from config.toml.

ring --context production deployment list
ring -c staging deployment list

--config

Load a specific config.toml instead of the default $RING_CONFIG_DIR/config.toml. Useful for keeping several explicitly-named files (config_dev.toml, config_prod.toml) and switching between them. Overrides the RING_CONFIG_FILE environment variable.

This overrides only the config file: auth.json, Cloud Hypervisor firmware, and other assets still come from $RING_CONFIG_DIR. If the given file does not exist, Ring logs an error rather than silently falling back to the default.

ring --config ~/.config/kemeter/ring/config_dev.toml deployment list
ring --config /etc/ring/config.toml server start

System

ring init

Initialize the Ring config directory: write config.toml and a freshly generated RING_SECRET_KEY (persisted to secret-key, mode 0600) under ~/.config/kemeter/ring/ (or $RING_CONFIG_DIR).

ring init

Interactive on a TTY (prompts for runtime and API port); scriptable via flags otherwise:

  • --runtime: docker, podman, cloud-hypervisor, firecracker (experimental), or both (Docker + Cloud Hypervisor). both never enables Firecracker.
  • --port: the API port (default 3030).
  • --force: overwrite an existing config.toml / secret-key. Regenerates the key, making every secret stored under the old one undecryptable.

Flags win over prompts and over the non-interactive defaults (Docker, port 3030), and compose per-field. When stdin is not a TTY and no flags are given, init uses the defaults instead of hanging on a prompt.

After writing the files, init runs the same diagnostics as ring doctor on the selected runtime as a pre-flight check. Failing checks are surfaced as warnings but do not change the exit code, since init already succeeded. Only the selected runtime is checked.

ring init does not create the SQLite database or seed the admin user. That happens automatically the first time ring server start runs the migrations.

ring doctor

Run diagnostic checks against the host. Runtime checks only run for runtimes enabled in config.toml; with no runtime enabled, all of them run.

  • Server-side env: RING_SECRET_KEY is set, decodes to base64, and is exactly 32 bytes.
  • Docker / Podman: docker --version succeeds (the binary is present and the daemon is reachable). Podman reuses this check since it speaks the Docker API.
  • Cloud Hypervisor: the binary is on $PATH, /dev/kvm is readable+writable, the binary has cap_net_admin,cap_net_raw set (printed by getcap), xorriso is on $PATH (needed for the cloud-init NoCloud ISO when a CH deployment ships environment), the firmware file at the configured firmware_path exists, and a virtiofsd binary is found at /usr/libexec/virtiofsd, /usr/lib/qemu/virtiofsd, or whatever RING_VIRTIOFSD points to.
  • Firecracker (experimental, only when enabled): the binary is on $PATH, /dev/kvm is readable+writable, the binary has cap_net_admin,cap_net_raw set, the kernel image at the configured kernel_path (an uncompressed vmlinux, not a firmware blob) exists, and socat is on $PATH for port forwarding.
ring doctor

Use this as the first step when something doesn't work as expected. Exits non-zero if any check fails.

Server

ring server start

Start the Ring server.

ring server start

On first start the server runs SQLite migrations, creates ring.db in the working directory (override with RING_DATABASE_PATH), and seeds the default admin / changeme user. Set RUST_LOG=info to see logs.

Authentication

ring login

Log in to a Ring server. Each login mints a fresh session token (a ring_pat_… value, full access) saved in ~/.config/kemeter/ring/auth.json and reused by subsequent commands. The session does not expire; end it with ring logout. Logging in again creates an independent session, and earlier ones stay valid until they are logged out.

ring login --username <USERNAME> --password <PASSWORD>

Required:

  • --username <USERNAME> / -u
  • --password <PASSWORD> / -p

Examples:

ring login --username admin --password changeme
ring login -u alice -p secret

If the server is unreachable (down, wrong host/port), the CLI prints a single actionable line and exits non-zero, not the underlying transport trace:

$ ring login -u admin -p changeme
error: cannot reach the server at http://localhost:3030 — is it running? (connection refused)

A timeout (the server ... did not respond in time) and a malformed request are reported the same way: one line, no nested error chain.

ring logout

Revoke the current context's session on the server and remove its token from auth.json.

ring logout

The server-side revocation is best-effort: if the server is unreachable the local token is still removed, so the command never leaves you "stuck logged in" locally. Only the current context's entry is touched; other contexts in auth.json are left intact. Revoking a PAT (rather than a login session) is done with ring token revoke <id>, not this command.

Deployments

ring apply

Apply a deployment manifest.

ring apply -f <FILE> [OPTIONS]

Options:

  • -f <FILE> / --file <FILE>: YAML or JSON manifest
  • -e <FILE> / --env-file <FILE>: load KEY=VALUE pairs from a file and use them to interpolate $VAR references in the manifest
  • -d / --dry-run: print what would be sent, without contacting the API
  • --verbose: print the full JSON of every deployment that will be sent
  • --force: skip the rolling-update path; do an immediate replacement even when health checks are configured

Examples:

ring apply -f deployment.yaml
ring apply -f config.json
ring apply -f app.yaml --env-file .env
ring apply -f app.yaml --dry-run --verbose
ring apply -f app.yaml --force

The manifest can contain a top-level namespaces: map and a deployments: map. See the file format section below.

ring deployment list

List deployments. Defaults to all namespaces.

ring deployment list [OPTIONS]

Options:

  • -n / --namespace <NAMESPACE>: filter by namespace
  • -s / --status <STATUS>: filter by status (repeatable). Values: pending, creating, running, completed, failed, deleted, crash_loop_back_off, image_pull_back_off, create_container_error, network_error, config_error, file_system_error, insufficient_resources, error (see Deployment status lifecycle)
  • --type <TYPE>: filter by deployment kind: worker or job
  • -l / --label <SELECTOR>: filter by label, key=value or just key (repeatable; a deployment must match all selectors). Works the same across runtimes (Docker and Cloud Hypervisor) since labels are matched on Ring's stored metadata.
  • -o / --output <FORMAT>: table (default) or json

Output (table):

The table has ten columns: Id, Created at (UTC), Updated at (UTC), Namespace, Name, Image, Runtime, Kind, Replicas (formatted instances/desired), Status.

Timestamps are rendered to the second (2026-05-03 22:22:21); sub-second digits and the UTC suffix are dropped from the cells since every Ring timestamp is UTC, as the column header says. The json output keeps the raw timestamp untouched, so scripts that parse it are unaffected.

Examples:

ring deployment list
ring deployment list --namespace production
ring deployment list --status running
ring deployment list --status running --status pending
ring deployment list --type job
ring deployment list --label env=prod
ring deployment list -l env=prod -l tier=web   # both must match
ring deployment list -o json | jq -r '.[].id'

ring deployment inspect

Show the full state of a deployment.

ring deployment inspect <DEPLOYMENT_ID>

<DEPLOYMENT_ID> is the UUID printed by ring deployment list.

ring deployment delete

Delete a deployment. The deployment is marked deleted and the scheduler removes its containers on the next tick.

ring deployment delete <DEPLOYMENT_ID>

ring deployment logs

Tail the logs of a deployment's containers.

ring deployment logs <DEPLOYMENT_ID> [OPTIONS]

Options:

  • -f / --follow: stream new lines (polls every 2 s)
  • --tail <N>: last N lines (default: 100)
  • --since <DURATION>: relative duration (30s, 10m, 2h) or RFC3339 timestamp
  • -c / --container <NAME>: filter to one instance/container name

Examples:

ring deployment logs web-app
ring deployment logs web-app --follow
ring deployment logs web-app --tail 50
ring deployment logs web-app --since 10m
ring deployment logs web-app --container production_web-app   # name prefix, or full container ID prefix

ring deployment events

Show scheduler events for a deployment.

ring deployment events <DEPLOYMENT_ID> [OPTIONS]

Options:

  • -f / --follow: stream new events
  • -l / --level <LEVEL>: filter by info, warning, or error
  • --limit <N>: maximum number of events (default: 50)

Examples:

ring deployment events web-app
ring deployment events web-app --follow
ring deployment events web-app --level error
ring deployment events web-app --limit 10

ring deployment metrics

Show CPU / memory / network / disk / pid stats for each instance of a deployment.

ring deployment metrics <DEPLOYMENT_ID>

Metrics are only available for the Docker runtime. Cloud Hypervisor deployments return an empty list.

ring deployment health-checks

Show the most recent health-check results for a deployment.

ring deployment health-checks <DEPLOYMENT_ID> [OPTIONS]

Options:

  • --latest: only the most recent result per instance
  • --limit <N>: maximum number of results

Users

ring user list

ring user list

ring user create

ring user create --username <USERNAME> --password <PASSWORD>

Required:

  • --username <USERNAME>
  • --password <PASSWORD>

ring user update

Update the currently authenticated user (the one whose token is in auth.json). At least one of --username or --password must be provided. There is no CLI command to update another user; for that, call PUT /users/{id} against the API directly.

ring user update [--username <USERNAME>] [--password <NEW_PASSWORD>]

Examples:

ring user update --password newsecret
ring user update --username alice
ring user update --username alice --password newsecret

ring user delete

ring user delete <ID>

<ID> is the user's UUID. Find it with ring user list.

API Tokens

Scoped API tokens (Personal Access Tokens) let scripts, CI and external agents call the API without using a human's login session. A token carries a set of verb:resource scopes, an optional namespace boundary and an optional expiry, and can be revoked or rotated individually.

The clear token (ring_pat_…) is shown once, at creation, and never again; only its prefix is stored in clear for display. Present it as Authorization: Bearer ring_pat_….

Login sessions (ring login) use the same storage and format: a session is a token scoped admin, created automatically on login and revoked on ring logout. It is distinguished from a PAT by its kind (not by its name), so naming a PAT session is fine and has no special effect. Sessions are not shown by ring token list and cannot be managed by id (ring token revoke/rotate); that command lists and acts only on the PATs you created. End a session with ring logout.

Scopes: deployments:read, deployments:write, secrets:read, secrets:write, configs:read, configs:write, namespaces:read, namespaces:write, users:read, users:write, and admin (grants everything).

ring token create

ring token create <NAME> --scope <SCOPE> [--scope <SCOPE>...] [OPTIONS]

Required:

  • <NAME>: token label (positional)
  • -s / --scope <SCOPE>: at least one; repeatable

Options:

  • -n / --namespace <NS>: restrict to a namespace; repeatable. Omit for all namespaces.
  • -e / --expires <DURATION>: 30d, 12h or 90m. Omit for no expiry.

The clear token is printed on stdout (so it can be captured); the summary and the "won't be shown again" notice go to stderr.

# Read-only CI token, all namespaces, 90-day expiry
TOKEN=$(ring token create ci-read --scope deployments:read --expires 90d)

# Write token scoped to a single namespace
ring token create deployer --scope deployments:write --namespace production

ring token list

ring token list

Lists your tokens with prefix, scopes, namespaces, status (active / revoked / expired), last-used and expiry. The secret is never shown.

ring token revoke

ring token revoke <ID> [-y|--yes]

Revokes a token immediately. Prompts for confirmation on a terminal; in a pipe/CI it refuses unless --yes is passed.

ring token rotate

ring token rotate <ID>

Revokes the token and mints a new one with the same name, scopes, namespaces and expiry. The new clear value is printed on stdout (shown once).

Webhooks

Webhooks let an external endpoint receive Ring events by HTTP POST instead of polling the API. Each webhook subscribes to a set of event kinds (or all of them) and is delivered through a durable queue with retry and dead-lettering, so a transient outage on the receiver doesn't drop events.

Today the only event kind is deployment.status_changed, emitted on every deployment status transition (creating→running, →failed, →completed, scaling, …). The payload carries old_status, new_status, the deployment id/name/namespace/kind and restart_count. When the webhook has a secret, deliveries are signed with X-Ring-Signature: sha256=<hmac>.

Managing webhooks requires the webhooks:write scope (or webhooks:read to list).

ring webhook create

ring webhook create <URL> [--event <KIND>...] [--secret <SECRET>]

Required:

  • <URL>: target URL receiving the signed POSTs (positional)

Options:

  • -e / --event <KIND>: subscribe to an event kind; repeatable. Accepts an exact kind (deployment.scaled), a family wildcard (deployment.*), or * for everything. Omit to receive all kinds. A malformed value (e.g. deployment* without the dot) is rejected at creation.
  • -s / --secret <SECRET>: HMAC secret. If omitted, Ring generates one and prints it once.

The webhook id is printed on stdout; the generated secret (if any) is printed on stderr and shown only once.

# Exact kinds
ring webhook create https://hooks.example.com/ring --event deployment.status_changed

# Every deployment event
ring webhook create https://hooks.example.com/ring --event 'deployment.*'

ring webhook list

ring webhook list

Lists webhooks with their URL, subscribed events, status (active/revoked) and creation time. Secrets are never shown.

ring webhook delete

ring webhook delete <ID>

<ID> comes from ring webhook list.

Secrets

Secrets are AES-256-GCM-encrypted values stored per-namespace. RING_SECRET_KEY (a base64-encoded 32-byte key) must be exported before ring server start, or the server refuses to start. Run ring doctor to confirm the variable is set and decodes correctly.

ring secret create

ring secret create <NAME> -n <NAMESPACE> -v <VALUE>

Required:

  • <NAME>: secret name (positional)
  • -n / --namespace <NAMESPACE>
  • -v / --value <VALUE>

Examples:

ring secret create database-password -n production -v "s3cret!"
ring secret create api-key -n staging -v "sk-1234567890"

ring secret list

Lists secret metadata. Values are never returned through the API or the CLI.

ring secret list [OPTIONS]

Options:

  • -n / --namespace <NAMESPACE>: filter by namespace

ring secret delete

ring secret delete <ID> [OPTIONS]

Options:

  • -f / --force: delete even if referenced by active deployments

If the secret is referenced and --force is not set, Ring lists the referencing deployments and aborts.

Configs

A config is a named blob (typically a config file or a JSON document) that can be mounted into a deployment via a volume of type: config.

The CLI exposes list, inspect, and delete. Creation goes through the REST API (POST /configs).

ring config list

ring config list [OPTIONS]

Options:

  • -n / --namespace <NAMESPACE>

ring config inspect

ring config inspect <CONFIG_ID>

ring config delete

ring config delete <CONFIG_ID>

Namespaces

ring namespace create

ring namespace create <NAME>

For Docker-runtime deployments, each namespace gets a dedicated Docker bridge network (ring_<name>). Namespaces are also auto-created when a deployment is applied to a non-existent namespace. The Cloud Hypervisor runtime does not create per-namespace networks; see Cloud Hypervisor for the VM networking model.

ring namespace list

ring namespace list

ring namespace prune

Remove inactive deployments from a namespace.

ring namespace prune <NAMESPACE> [--all]

Options:

  • -a / --all: delete every deployment in the namespace, including running ones. Destructive.

Prunable statuses (default): completed, failed, deleted, crash_loop_back_off, image_pull_back_off, create_container_error, network_error, config_error, file_system_error, error.

Preserved statuses (default): pending, creating, running.

Examples:

ring namespace prune development
ring namespace prune development --all

Node

ring node get

Display node information.

ring node get

Returns: hostname, os, arch, uptime, cpu_count, memory_total, memory_available, load_average.

Contexts

A context is a named connection profile in config.toml.

ring context

ring context [SUBCOMMAND]

Subcommands:

  • configs (default): list all contexts
  • current-context: print the currently active context name
  • user-token: print the authentication token for the current context

Examples:

ring context
ring context configs
ring context current-context
ring context user-token

Configuration files

Contexts and tokens live in ~/.config/kemeter/ring/ (or $RING_CONFIG_DIR):

  • config.toml: context definitions
  • auth.json: authentication tokens per context

config.toml example:

[contexts.default]
current = true
host = "127.0.0.1"
api.scheme = "http"
api.port = 3030

[contexts.production]
current = false
host = "prod.example.com"
api.scheme = "https"
api.port = 443

[scheduler]
interval = 10

Using contexts

ring --context production deployment list
ring -c staging server start

The default context (the one with current = true) is used when no --context flag is provided.

Environment variables

Server

  • RING_DATABASE_PATH: path to the SQLite file (default: ./ring.db)
  • RING_DB_POOL_SIZE: max SQLite connections (default: 5)
  • RING_CONFIG_DIR: config directory (default: ~/.config/kemeter/ring)
  • RING_SECRET_KEY: base64-encoded 32-byte key for secret encryption. Required: the server refuses to start without it (validated up front; see ring doctor).
  • RING_SCHEDULER_INTERVAL: scheduler tick in seconds (overrides server.scheduler.interval in config.toml)
  • RING_APPLY_TIMEOUT: single-deployment apply timeout in seconds (default: 300)
  • RUST_LOG: log level (e.g. info, debug, ring=debug)

CLI

  • RING_TOKEN: bearer token used for API requests. When set and non-empty, the CLI ignores auth.json. Useful for CI pipelines that should not depend on ring login.
  • RING_CONFIG_FILE: path to a specific config.toml to load (default: $RING_CONFIG_DIR/config.toml). The --config flag takes precedence.
# Generate a server-side key
export RING_SECRET_KEY="$(openssl rand -base64 32)"
ring server start

Exit codes

CodeNameTriggered when
0SuccessThe command completed successfully (HTTP 2xx)
1General errorValidation, parsing, or any non-categorized failure
2AuthAPI responded with 401 Unauthorized or 403 Forbidden
3ConnectionCLI could not reach the API (network, DNS, timeout, refused)
4Not foundAPI responded with 404 Not Found
5ConflictAPI responded with 409 Conflict (e.g. resource already exists)

Conditional create in a shell script:

ring deployment inspect "$DEPLOYMENT_ID" > /dev/null 2>&1
case $? in
  0) echo "already deployed, skipping" ;;
  4) ring apply -f deployment.yaml ;;
  2) echo "auth expired"; exit 1 ;;
  3) echo "API unreachable"; exit 1 ;;
  *) echo "unexpected error"; exit 1 ;;
esac

Notes:

  • Every command that talks to the API honours this table: a command that cannot reach the API or gets a non-2xx response exits non-zero (never 0), so set -e, CI gates and && chains detect the failure.
  • ring apply processes multiple deployments; if any fail, the command exits with the code of the first failure.
  • Follow modes (logs --follow, events --follow) keep running on transient errors and only exit if the initial request fails.

File formats

Manifest structure

Required deployment fields:

  • name
  • runtime: docker or cloud-hypervisor
  • image
  • namespace

Optional fields:

  • kind: worker (default) or job
  • replicas: default 1; jobs always run a single instance
  • environment: map of plain values or { secretRef: <name> } references
  • volumes: list of volume objects (see below)
  • labels: key/value map (or list of single-key objects)
  • command: list of arguments overriding the image entrypoint
  • resources: limits / requests for CPU and memory
  • health_checks: list of tcp, http, or command checks
  • config: image pull policy, registry auth, optional user

YAML example

namespaces:
  production:
    name: production

deployments:
  app-name:
    name: app-name
    namespace: production
    runtime: docker
    kind: worker            # "worker" (default) or "job"
    image: "nginx:1.25"
    replicas: 3

    environment:
      ENV_VAR: "value"
      DB_PASSWORD:
        secretRef: "database-password"

    volumes:
      - type: bind
        source: /var/lib/app
        destination: /data
        driver: local
        permission: rw

    labels:
      app: app-name
      tier: backend

    command:
      - "/bin/sh"
      - "-c"
      - "exec myapp --port $PORT"

    resources:
      limits:
        cpu: "500m"          # 500 millicores = 0.5 CPU
        memory: "512Mi"
      requests:
        cpu: "100m"
        memory: "128Mi"

    health_checks:
      - type: http
        url: "http://localhost:8080/health"
        interval: "30s"
        timeout: "5s"
        threshold: 3         # default: 3
        on_failure: restart  # restart | stop | alert
      - type: tcp
        port: 5432
        interval: "10s"
        timeout: "2s"
        on_failure: alert
      - type: command
        command: "pg_isready -U postgres"
        interval: "15s"
        timeout: "3s"
        on_failure: restart

Volumes

Three type values are supported:

  • bind: host path mount
  • volume: named Docker volume
  • config: file rendered from a Ring config and mounted at destination. Requires a key field that selects which entry in the config to mount; permission is forced to ro.

Required fields: type, source, destination. key is required for type: config. driver and permission have defaults via ring apply (local and rw respectively, except config which is ro); they are required at the API level (see manifest reference → volumes).

volumes:
  - type: bind
    source: /etc/nginx/conf.d/custom.conf
    destination: /etc/nginx/conf.d/custom.conf
    driver: local
    permission: ro

  - type: volume
    source: app-data
    destination: /data
    driver: local
    permission: rw

  - type: config
    source: nginx-config       # name of a Ring config in the same namespace
    key: site.conf             # which entry inside the config
    destination: /etc/nginx/conf.d/site.conf
    driver: local
    permission: ro

Resources

  • limits: hard cap on Docker (CPU throttled via nano_cpus, OOM-killed on memory overage). Allocation, not a cap, on Cloud Hypervisor (vCPU count + RAM size).
  • requests: only requests.memory is honored on Docker (mapped to memory_reservation, a soft minimum). requests.cpu is currently ignored on both runtimes.
  • CPU values: millicores ("500m") or whole cores ("1", "0.5"). On Cloud Hypervisor, fractional values are rounded down to whole vCPU (floor of 1).
  • Memory values: raw bytes or Ki / Mi / Gi suffixes.
  • Both limits and requests are optional; within each, cpu and memory are also optional.

Health checks

  • type: tcp: checks a TCP port is open. Requires port. Probe runs from the host against the runtime-private IP (Docker bridge IP / CH guest IP).
  • type: http: issues an HTTP GET, expects a 2xx response. Requires url. localhost in the URL is rewritten to the runtime-private IP.
  • type: command: runs a shell command inside the container via docker exec. Requires command. Currently the probe only checks that exec started without error; the command's exit code is not inspected (so a script that exits non-zero will still report success). Docker only.
  • interval and timeout use duration suffixes ms and s. m and h are not supported in this context (write 60s, not 1m). The --since flag on logs is a separate parser that does accept m/h.
  • interval is currently advisory: the actual cadence is one probe per scheduler tick (default 10s).
  • threshold: consecutive failures before on_failure triggers (default: 3).
  • on_failure: restart (recreate the instance), stop (mark the deployment deleted), or alert (emit an error event only).
  • Cloud Hypervisor: tcp and http are supported; command is rejected at the API.

Namespaces in YAML

The top-level namespaces: section is optional. When present, namespaces are created before deployments are processed. If a namespace already exists, it is silently skipped. Namespaces are also auto-created on first deployment.

JSON

{
  "name": "app-name",
  "runtime": "docker",
  "namespace": "default",
  "kind": "worker",
  "replicas": 1,
  "image": "nginx:1.25",
  "labels": {},
  "environment": {
    "ENV_VAR": "value",
    "DB_PASSWORD": { "secretRef": "database-password" }
  },
  "volumes": [
    {
      "type": "bind",
      "source": "/var/lib/app",
      "destination": "/data",
      "driver": "local",
      "permission": "rw"
    }
  ]
}

Patterns

Variable interpolation

ring apply interpolates $VAR references in string fields (image, namespace, name, environment values, command arguments) from your shell environment, or from a file passed with --env-file.

export APP_VERSION=v1.2.3
export NAMESPACE=production
ring apply -f template.yaml
deployments:
  app:
    name: myapp
    image: "myapp:$APP_VERSION"
    namespace: "$NAMESPACE"
    replicas: 3

CI deployment script

#!/bin/bash
set -euo pipefail

# Use a token-based auth flow rather than `ring login`
export RING_TOKEN="$RING_API_TOKEN"

ring apply -f production.yaml
ring deployment list --namespace production

Troubleshooting

Diagnostics

ring doctor
curl http://localhost:3030/healthz
RUST_LOG=debug ring server start
docker ps --filter "label=ring_deployment"
docker network ls | grep '^.\+ring_'

Reset

# List all deployment IDs as JSON, then delete each one
ring deployment list -o json | jq -r '.[].id' | xargs -I {} ring deployment delete {}

# Force-stop everything Ring-labelled at the Docker level
docker ps -a --filter "label=ring_deployment" -q | xargs -r docker rm -f

# Wipe the database (server must be stopped first)
rm -f ring.db ring.db-shm ring.db-wal

For a single-command help on any subcommand:

ring <command> --help

Error messages

When a command fails, the CLI prints a single human line to stderr, prefixed with error:, and exits non-zero. It does not echo the raw HTTP status: the status code is a category, and the message is composed from what the command already knows (the resource and the name you typed).

$ ring deployment delete web
error: deployment 'web' not found

$ ring deployment inspect web
error: deployment 'web' not found

$ ring config delete app
error: config 'app' not found

Mapping by status category:

SituationMessage
Resource missing (404)error: <kind> '<name>' not found
Conflict / has dependents (409)error: <kind> '<name>' already exists or still has dependents
Not authorized (401/403)error: not authorized to act on <kind> '<name>'
Listing, not authorizederror: cannot list <kind> in namespace '<ns>': not authorized
Anything elseerror: <kind> '<name>': request failed (<status>)

Validation failures on apply / user create are the exception: the server returns a structured RFC 7807 problem+json body, and the CLI renders every violation with its property path (see ring apply). The exit code still reflects the HTTP status in all cases.

Colour

Output is colourised only when stdout is a real terminal and the NO_COLOR environment variable is unset:

  • Errors (error: …) are red, success lines green.
  • In ring deployment list, the Status column is colour-coded: green = running / completed, yellow = pending / starting / deleted, red = the failure states (failed, crash_loop_back_off, create_container_error, …). Unknown values are left uncoloured.

When the output is piped, redirected to a file, run under CI, or NO_COLOR=1 is set, no ANSI escape is emitted at all, and the bytes are identical to a plain run. --output json is never colourised. This means ring deployment list | grep, ... -o json | jq, and existing scripts keep working unchanged. There is no --color flag; the automatic detection is the whole contract.