Cluster operations
Day-two operations on a registered data-plane cluster: inspecting health, rotating the agent's join token, and tearing down infrastructure. For first-time provisioning, see Getting started: admin.
Listing and inspecting
$ adj cluster ls
$ adj cluster get prod
adj cluster get shows agent health, active instance count, and when the
agent last checked in.
Status: provisioning progress + agent health
$ adj cluster status prod
ID: 1c9c14da-8b0e-4c6e-9f1a-2d7c3a5b6e42
Provisioning: succeeded
Started: 2026-07-18T09:14:02Z
Log: /home/dana/.config/adj/provision/1c9c14da-8b0e-4c6e-9f1a-2d7c3a5b6e42.log
Name: prod
Agent health: connected
Instances: 14
Last seen: 2026-07-19T08:12:41Z
Use this while adj cluster provision is running detached to watch the
Provisioning: line move from running to succeeded or failed (a
failure also shows the exit code and points at the log file). The
Provisioning: / Started: / Log: lines come from local provision state,
so they only appear on the machine that ran adj cluster provision.
Once provisioning has succeeded, the command doubles as an ongoing health
check: Agent health: is connected, stale, or never (agent has never
checked in), and Last seen: is the agent's last check-in time. A stale
agent is the first sign of a data-plane connectivity problem (see
Troubleshooting's 503/504 entries).
Kubeconfig
$ adj kubeconfig
/home/dana/.config/adj/kubeconfigs/prod.yaml
Prints the path to the kubeconfig adj cluster provision wrote for the
active context — resolution order is --kubeconfig, then
$ADJ_KUBECONFIG, then that generated file. Use it directly:
$ export KUBECONFIG=$(adj kubeconfig)
$ kubectl get pods -A
Your own ~/.kube/config is never touched by adj cluster provision.
Rotating the join token
$ adj cluster rotate-token prod
⚠ Agent join token (shown once — the prior token is now invalid):
x7Dq2m4T9vKcW1pZa8Rf3sLn0uYbEjH5gOiA6dNqVwM
Invalidates the cluster's previous agent join token immediately. Use this if
a token may have leaked, or as routine credential hygiene. The agent running
in your data plane keeps its existing connection; the new token only matters
the next time an agent needs to (re)join. Note that adj cluster bootstrap
and adj cluster provision also rotate the token as a side effect — if
you've re-run either of those recently, rotate-token isn't the first thing
that changed it.
Deprovision vs. rm — two different operations
These are easy to conflate. They do different things:
adj cluster deprovision |
adj cluster rm |
|
|---|---|---|
| What it does | Destroys the AWS infrastructure (EKS, FSx, every clone on it) via a local OpenTofu destroy | Deletes the cluster's control-plane record |
| Blocked by active instances? | No — it destroys them along with everything else | Yes — 409 while any instance is still registered against it |
| Requires login? | Only if you pass a cluster name | Always |
| Requires local tools? | tofu + aws on this machine |
No |
Deprovision
$ adj cluster deprovision prod
This DESTROYS the AWS data plane (EKS + FSx + all clones) for cluster prod. Proceed? [y/N]: y
Runs the OpenTofu destroy locally (including the two-pass teardown needed
for stuck Kubernetes-provider resources). It does not deregister the
cluster record — that's a separate step (cluster rm).
Image defaults and the version gate
Both adj cluster provision and adj cluster deprovision fetch
the control plane's client config before showing
their confirmation prompt:
-
Image defaults.
--agent-image/--operator-imageno longer default to a fixed ref baked into the binary — an unset flag resolves to the image the control plane currently publishes at provision time (a baked-in fallback applies only if the control plane can't be reached). The resolved refs are printed before you're asked to confirm:Images: agent=registry.stagdb.com/adjoint-agent:v0.4.0-a8f670b operator=registry.stagdb.com/adjoint-operator:v0.4.0-a8f670b This provisions real AWS infrastructure for cluster prod — ... Proceed? [y/N]: -
Version gate. A release build of
adjbelow the control plane's minimum supported version refuses to runprovisionordeprovisionat all, printing the update one-liner instead of prompting:$ adj cluster provision prod adj v0.1.0 is below the minimum supported release v0.2.0 for cluster provisioning — update first: curl -fsSL https://stagdb.com/adjoint/install.sh | shUnreleased/dev builds (anything that isn't a clean
vX.Y.Ztag) are exempt from the gate — a notice is printed and the command proceeds. If the control plane can't be reached, the gate fails open: a warning is printed and the command proceeds rather than blocking on a policy it couldn't fetch. Caveat: settingADJ_CLI_MIN_VERSIONabove the newest releasedadjblocks allprovisionanddeprovisionoperations for release builds with no override flag; keep the floor at or below the latest published release. See Client config for the wire contract and Install for the separate, non-blocking update nag that other commands print.
If you pass a UUID cluster id instead of a name, deprovision needs no login —
the only control-plane contact is an unauthenticated GET /v1/clientconfig call for
the version gate, which proceeds (fail-open) if the control plane is unreachable —
and then goes straight to the local tofu destroy using your local AWS credentials.
That's deliberate: a logged-out admin, or a break-glass script, can still tear down
infrastructure even if the control plane is unreachable. Passing a cluster name
instead requires resolving it via the API first, which does require being logged in.
--aws-profile selects which AWS profile tofu and the AWS CLI use (default:
ambient credentials / AWS_PROFILE); --yes skips the confirmation prompt.
By default deprovision (like provision) uses the OpenTofu module embedded
in adj — no checkout needed. --module-dir <path> is the developer/
from-source override: point it at a local module checkout instead (needed if
you originally provisioned with --module-dir; see "Where provisioning
state lives" below). Deprovision requires only tofu and aws on this machine, in both embedded and --module-dir modes.
Where provisioning state lives
Embedded-mode provision/deprovision extract the module to
~/.config/adj/provision/bundles/<cluster-id>/ (honoring $XDG_CONFIG_HOME),
and the OpenTofu state (terraform.tfstate, .terraform/) lives inside that
same directory. Two things follow from that:
- Deprovision must run on the machine that provisioned — the state isn't
synced anywhere else. This was already true for
--module-dirruns (state lived in the checkout) and is unchanged. - Upgrading
adjrefreshes the module's HCL in place against that existing state the next time you provision or deprovision that cluster — you'll see a one-line notice on stderr when this happens. tofu then reconciles any HCL changes against the state as normal.
Use adj cluster bundle export <dir> to get a look at the module without
provisioning anything:
$ adj cluster bundle export ./adjoint-module
exported adj v0.2.0 bundle to ./adjoint-module
module: ./adjoint-module/deploy/tofu/adjoint-cluster
It refuses to write into a non-empty directory. Two uses: read the HCL
before you run it, or seed a --module-dir workflow (e.g. to carry local
patches across provisions instead of relying on the embedded module).
Deregistering
$ adj cluster rm prod
Deletes the cluster's record from the control plane. A cluster with active
instances cannot be removed — you'll get a 409 until every instance
against it is destroyed. This is the record-keeping step; it doesn't touch
AWS infrastructure at all. The normal teardown order is: destroy instances →
adj cluster deprovision → adj cluster rm.
Next
- Troubleshooting — what to check when
adj cluster statusshows astaleagent. - Usage and audit —
adj org usage's CLUSTERS section is the same health data asadj cluster get, for every cluster at once.