Workstation
A development machine Ringleader creates and manages for you: a VM on your laptop or in your cloud account.
A Workstation is a development machine that Ringleader creates and manages for you: a VM on your laptop (vz on macOS, WSL2 or Hyper-V on Windows, or qemu on Linux) or in your cloud account. You describe the machine you want (OS, tools, code, settings) and Ringleader builds it and keeps it matching that description.
So you can delete the machine and get an identical one back, hand a teammate or a coding agent the same environment from the same file, and change the environment by editing the description instead of shelling in.
This page is the field-by-field reference. spec is the description you write;
status is what the control plane and the in-machine agent report as actually
true.
apiVersion: workstations.ringleader.dev/v1
kind: WorkstationA workstation is namespaced. Its effective configuration is the merged
configuration of every WorkstationConfig that attaches to it (by selector or
explicit reference) plus the workstation’s own inline spec. That merge is computed
on demand rather than stored in status. Read it with
rl workstation get-resolved-configuration.
Minimal example
Everything is implicit: a provider is chosen by automatic placement, and configs attach by selector.
apiVersion: workstations.ringleader.dev/v1
kind: Workstation
metadata:
name: hello-ws
namespace: dev
labels:
tier: dev
spec: {}Spec fields
You author these. Many are config-layer fields: they can also be set on a WorkstationConfig and merged in.
Placement & provider
| Field | Type | Description |
|---|---|---|
requirements | []string | Machine requirements in precedence order (e.g. ["provider:gcp"]); matched against discovered provider capabilities. |
provider | string | Explicit provider pin, shorthand for requirements: [provider:X]. |
providerConfig | object | Per-provider opaque config (e.g. memory/cpus for local VMs, or {gcp: {project, zone, machineType}}). Not merged from WorkstationConfig config layers, though the matching CloudIdentity’s defaultProviderConfig and overrideProviderConfig are folded around it. |
machineType | string | Provider machine size class (e.g. e2-micro for GCP). |
securityProfile | string | Named security profile (e.g. baseline, privileged). |
Lifecycle
| Field | Type | Description |
|---|---|---|
stopped | bool | true stops the workstation. The machine is halted and rests in the Stopped phase. A workstation created with stopped: true boots once, applies its first configuration, and then powers off. |
restartNonce | string | Change this on a running workstation to trigger a Stop→Start restart. |
lifecycle | object | When the workstation stops or is deleted on its own: {idle, maxRunTime, ttl, ttlAction}. See How long a workstation lives. |
ttl | string | The older top-level spelling of lifecycle.ttl, still accepted. Declaring a lifetime here and in lifecycle is rejected. |
ttlAction | string | The older top-level spelling of lifecycle.ttlAction, still accepted: delete (the default) or stop. Only consulted when ttl is set. |
exchangeSecretNonce | string | Change this to rotate the workstation’s own credential, which is what to do if one leaks. Like restartNonce, only the change matters, never the value; absent means nothing to rotate. |
How long a workstation lives
A workstation nobody turned off keeps costing money, and a stopped cloud VM still bills
for its disks. spec.lifecycle declares deadlines the control plane enforces on its own,
with no client involved. It exists for the case where nothing is left to clean up after
you: a CI job killed outright, or a machine you meant to keep for an afternoon.
There are three things you can ask for, and they combine. Stop the workstation when
nobody is using it, with idle. Stop it once the current run has gone on long enough,
with maxRunTime. Delete it at a deadline, with ttl.
apiVersion: workstations.ringleader.dev/v1
kind: Workstation
metadata:
name: ci-run-4821
namespace: dev
spec:
lifecycle:
idle:
timeout: 30m # stopped after 30 minutes with nothing going on
action: stop
sshSessions: true # an SSH session means somebody is using it
maxRunTime: 8h # stopped once this run has lasted 8 hours
ttl: 7d # deleted a week after it was createdIf that is all you need, you are done. The rest of this section says what each field does on its own.
Every duration is a plain unit string (45s, 90m, 2h, 720h) or a whole number of
days (7d). It must be positive. A value that cannot be read, or a zero or negative one,
is rejected when you apply it rather than being silently ignored.
Stopping an idle workstation. lifecycle.idle powers the machine down after a period
in which nothing you named as activity happened. A probe inside the workstation reports
what it sees, and the control plane decides.
spec:
lifecycle:
idle:
timeout: 45m
action: stop
sshSessions: true
processes: ["pytest"]
ports: [5432]The block takes a timeout, an action, and the conditions that say what “in use” means for this workstation.
timeoutis how long every condition must agree the workstation is idle before it stops.actionaccepts onlystop. A workstation that should go away declaresttlwithttlAction: deleteinstead. A machine that reported itself idle and then went quiet keeps its last verdict and its clock, so the timeout still elapses, which is safe only because a stop can be undone.sshSessions: truecounts a person’s SSH session as activity.processeslists executable names as they appear in/proc/<pid>/comm, at most 15 characters each. A path or a wildcard is rejected when you apply it, because the probe compares the executable name and never the command line.portslists ports on the machine, and counts established connections rather than listening sockets. A dev server nobody has connected to is exactly the workstation this is meant to power down.checkCommandis the last resort, for activity none of the others describes. A non-zero exit means the machine is in use. It runs as root on a repeating timer, so it may use only letters, digits, spaces and- _ . / = : , @ + %, at most 256 characters. Put anything longer inspec.filesand name the script.
Every condition you declare must agree the workstation is idle before the timeout runs, and there is no setting that weakens that. Declaring more conditions makes a stop less likely, never more. A timeout with no condition is rejected, and so is a condition with no timeout.
Stopping a workstation after a fixed run. lifecycle.maxRunTime powers the machine
down once the current run has lasted that long, whatever is happening on it. The anchor
is the run rather than the creation time, so starting the workstation again gives it a
fresh period: one that stops in the evening can be started the next morning. The status
message names the field that stopped it and tells you to start the workstation to begin
another run.
It is rejected together with ttlAction: stop, which is the older spelling of the same
thing.
Deleting a workstation at a deadline. lifecycle.ttl is the delete deadline,
measured from when the workstation was created. Its action is ttlAction.
ttlAction: deleteis the default. It removes the workstation and destroys the machine and its disk, exactly asrl workstation deletewould. The clock starts at creation and nothing moves it, not a stop, a start, or a restart. Editing the value is the one thing that moves the deadline, and it takes effect immediately.ttlAction: stopis the older spelling ofmaxRunTime. It powers the machine down and leaves it there, so its disk and state survive and you can start it again. Its deadline is anchored on the current run rather than on creation.
Two things hold whichever deadline you declare. Declaring a lifetime twice is
rejected when you apply it: the lifecycle block is read whole, so spec.ttl beside
spec.lifecycle.ttlAction is rejected as well, not only the same field written in both
places. And enforcement is periodic, so a workstation can sit briefly past its
deadline before the action lands. Treat a deadline as “at or shortly after this”, not as
a precise instant.
The next deadline is published as status.expiresAt, and
rl workstation get grows an EXPIRES column, counting down or reading expired, as
soon as any listed workstation declares one.
A lifetime is per workstation, never per config
lifecycle, ttl and ttlAction are Workstation fields only. They are rejected on a
WorkstationConfig: a config attaches by
selector, so a lifetime declared there would expire workstations its author does not
own. Set it on each workstation that should expire.Configuration attachment
| Field | Type | Description |
|---|---|---|
configs | []object | Explicit WorkstationConfig references: {name, priority}. The priority is taken from the reference verbatim, with no default, so an omitted one is 0, the lowest layer of all. See merge order. |
Machine contents (config-layer fields)
| Field | Type | Description |
|---|---|---|
image | string/object | OS image: a shorthand string, or {os, distribution, version}. |
packages | [] | Packages to install: each a bare name or {name, version, updatePolicy}. |
devtools | []object | Curated install recipes: {name, version} (e.g. nodejs, claude-code, vscode-web). |
toolconfigs | []object | Per-tool config channel: {id, name, config} (opaque config) for already-installed tools. |
packageRepositories | []object | Third-party repos: {name, type, urls, key, …} (apt/ppa). |
aptSources | [] | Legacy apt repos + keys (normalized to packageRepositories). |
scripts | []object | Provisioning scripts: {name, phase, content, …}. |
sources | []object | Code/repos synced into the workstation: {name, git|local, path, updatePolicy, …}. A source can also run its repository’s own devcontainer. |
files | []object | Declarative files materialized on the machine. |
services | []object | User-declared managed services (systemd units / command-mode). |
ports | []int | Declared listening ports. |
dotfilesRepo | object | A dotfiles repository cloned into the machine and installed by running its own install script. |
trustedFolders | []string | Absolute paths the machine’s coding tools pre-trust, on top of every sources[].path. |
sshKnownHosts | [] | Outbound SSH host-key trust pinned into ~/.ssh/known_hosts: a bare hostname, or {host, keys}. |
tailscale | object | Join the machine to a Tailscale tailnet. The join credential is always a Secret reference. |
egress | object | Restrict what the workstation may connect out to: {enforcement, destinations[]}. Absent means no restriction: everything your network routes. See Restricting outbound connections. |
System settings (config-layer fields)
| Field | Type | Description |
|---|---|---|
identity | object | The login user: {user, uid, gid, group, groups, shell, sudo}. The only place the login user is declared; the agent owns user creation. Default shell /bin/bash; the user gets passwordless sudo by default (sudo: false to opt out). |
environment | map | System-wide env vars → /etc/environment + profile.d. |
dotfiles | map | Shell-dotfile fragments: path → content (managed blocks). |
sysctls | map | Kernel sysctls → /etc/sysctl.d/. |
limits | [] | pam_limits entries → /etc/security/limits.d/. |
hostname | string | Machine hostname. |
timezone | string | IANA timezone (e.g. America/New_York). |
locale | string | Locale (e.g. en_US.UTF-8). |
securityUpdates | bool | Enable unattended security upgrades (Debian: unattended-upgrades). |
diagnostics | object | Ledger config: {ledger: {enabled, retain, maxOutputBytes}}. maxOutputBytes limits captured stdout and stderr for each step. A value above 128 KiB resolves to 128 KiB. |
defaultLocalBinding | object | Device-local forwarding template; the daemon seeds a LocalBinding from it once when the workstation first reaches Running (honor-delete). |
capabilities | []string | What the workstation’s own identity may do: self:read, self:power, sa:assume. Both the admin and member tiers must declare a capability for the machine to hold it. See Workstation capabilities. |
Status fields
The system writes these. They are read-only.
| Field | Type | Description |
|---|---|---|
phase | string | Pending, Provisioning, PostInstall, Running, Reconfiguring, Starting, Stopping, Stopped, Failed, or Terminating. |
message | string | The one human headline: what is going on with this workstation, in a sentence. |
provider | string | Selected provider (e.g. gcp, vz, qemu, lima). |
cloudIdentity | string | On a cloud workstation, the CloudIdentity whose selector its labels match. <none matched> when none does, and the workstation then uses the deployment’s own cloud credential. Empty on a local workstation. |
providerID | string | Provider-specific machine ID (e.g. the GCE instance name). |
os | string | The operating system the workstation’s merged configuration selects, as a short name such as debian or ubuntu. |
osVersion | string | The version from the same selection, such as 13. Empty when the selection names none. |
image | string | The base image the machine was actually created from. |
machineType | string | The size the provider reports the machine running as. It changes on the first status read after a resize. |
rootDisk | object | {observedBytes}: the size of the root filesystem the machine measured. See RootDiskShortfall. |
address | string | SSH address reachable by the daemon (absent for a NAT’d VM the daemon can’t dial). |
internalAddress | string | The machine’s address inside your cloud network. Empty on a local workstation. |
networkTags | []string | The cloud network tags the machine carries, including ones Ringleader adds. On GCP they decide which firewall rules apply to it. |
managementAddress | string | The host:port on an edge instance that reaches this workstation’s SSH while the edge instance routes its traffic. Ringleader connects there instead of address. Empty on every other workstation. |
user | string | Resolved SSH login user. |
observedGeneration | int | The spec generation the controller last observed. |
expiresAt | string | When the next lifetime deadline falls due, as a timestamp: the delete deadline, or the run limit while the workstation is running, whichever comes first. Absent on a workstation that declares neither. |
config | object | Configuration state: {effectiveHash, appliedHash, appliedAt}. effectiveHash is the configuration the control plane wants; appliedHash is what the machine last reported applying. |
egress.inputs | string | On an hcs, vz or qemu workstation served by its own network process, the fingerprint of the store facts its Edge confirmed. It never contains a credential value or digest. |
agent.seenAt | string | When the control plane last heard the in-VM agent keep its connection open, as a timestamp rounded down to a five-minute boundary. rl workstation describe prints it as Last heard. Present only on a workstation whose agent connects out to Ringleader for its configuration, and only once the agent has connected. See AgentNotReporting. |
conditions | []object | Conditions, each with a reason and a message. See Conditions and their reasons. |
configState | object | Present while a configuration pass runs, and when the pass keeps failing. See Fields that report a problem. |
nonFatalConfig | object | Present only while an optional configuration step is failing. See Fields that report a problem. |
ambientAgent | object | Present only while ambient SSH_AUTH_SOCK delivery has come up short. See Fields that report a problem. |
diagnostics | object | Ledger rollup (path + last-run counts). |
sshHostKeys | []string | The workstation’s SSH host public keys, for pinning into known_hosts. |
Fields that report a problem
Three status fields report trouble. A healthy workstation carries none of them, apart from
configState while a configuration pass runs. Their absence is normal, and does not by
itself prove that anything succeeded.
configState reads {state: configuring} while a configuration pass runs, and
{state: failed} when the in-VM agent reports that its configuration pass keeps failing. The
failed state carries the same fact as the Configured condition’s ConfigurationFailed
reason, which rl workstation describe prints. It also names the failing step
({kind, target}) and a cause: disk-full, permission-denied, read-only-filesystem,
network-unreachable, step-timeout or other. The Configured message and
rl troubleshoot both name the cause and what to do about it.
nonFatalConfig appears while an optional step is failing: a step from a
personal layer,
a dotfiles fragment, a dotfiles repository, or a personal
Integration’s endpoint file. An
optional step blocks nothing, so the workstation still reports Configured.
rl workstation describe prints it as Optional configuration, and
rl troubleshoot has the failing step and its output. It clears on recovery, and
on a power cycle along with the rest of the configuration verdict.
ambientAgent appears while ambient SSH_AUTH_SOCK delivery has come up
short, as {profile, sshd, reason, detail, observedAt}: the two booleans say which
of the env files are in place, and reason names the precondition that stopped the
other. A workstation your daemon has never connected to also carries no
ambientAgent, so absence alone does not prove delivery worked.
rl workstation describe prints it as Ambient SSH agent, and rl troubleshoot
confirms the live state with its ssh-agent-env probe. See
SSHKey.
Status does not carry the merged configuration
status.resolved. Status carries a fingerprint of the effective
configuration (status.config.effectiveHash), not the configuration itself: the
merge is derived data, re-computed on demand. To read what a workstation actually
resolves to, use
rl workstation get-resolved-configuration,
which prints the whole merged spec including the environment, files, scripts,
sources, devtools, toolconfigs, and dotfiles.Lifecycle phases
Stopping and Starting are real phases, not instant transitions: a power
operation is issued, and it rests in them until the machine is confirmed
down (or back up). Starting means an existing machine is powering on, as opposed
to Provisioning, which means one is being created.
A workstation is fully configured when status.config.appliedHash equals
status.config.effectiveHash and its Configured condition is True.
A configuration verdict never outlives the boot that produced it: stopping a
workstation clears appliedHash and sets Configured to False, so a stopped
workstation never claims a configuration it applied on a previous boot.
Conditions and their reasons
Available, Ready and Configured are always published. The others appear only when
there is something to report. Each condition carries a reason (a short machine token) and
a human message. Available is the one to gate on: it is the aggregate that answers
“is this workstation usable yet?”, and the CLI, the TUI and the desktop apps all fold it
into the single status they show. Available takes Configured and ToolsReady into
account. The conditions listed after ToolsReady below are not taken into account, so a
workstation can be Available while one of them reports a problem.
| Condition | Meaning |
|---|---|
Available | The aggregate readiness signal: True only when the workstation is Running and fully configured. |
Ready | The machine itself is up and reachable. Says nothing about configuration. |
Configured | The in-VM agent has applied the configuration the control plane wants. |
ToolsReady | Devtool installation, and usually absent: it is published only once a tool has failed to install. A workstation whose tools all installed cleanly carries no ToolsReady condition at all. |
EgressEnforced | Whether the workstation’s declared egress policy is actually being enforced. Published while egress is declared, and while an attached credential injection Integration asks an edge instance to inject or inspect for the workstation. Removed when neither holds. |
EgressApplied | On an hcs, vz or qemu workstation served by its own network process, whether that process holds the rule set and credentials that its current Integration, Secret and policy inputs resolve to. True has reason Applied; False has reason InputsOutstanding and names what remains. Run rl workstation wait <name> --for=EgressApplied after one of those inputs changes. |
EgressCredential | On lima only: whether an attached credential injection Integration’s key is being added, for a workstation with no egress policy. See Credential injection. |
SteeredWithoutServing | On AWS and Azure: an edge instance routes this workstation’s subnet and holds no rule for it. Present only while that is true. |
RootDiskShortfall | The root filesystem is much smaller than the disk size declared. |
AgentNotReporting | The control plane has not heard the in-VM agent for more than 15 minutes. |
SSHAdmissionMissing | Ringleader could not put its own firewall rule admitting SSH in place, on a cloud workstation. |
PublicAddressChangeNotApplied | The public address setting changed after the machine was created, and the machine keeps its original setting. |
Available reasons
| Reason | Meaning |
|---|---|
Ready | Running and fully configured, so the workstation is usable. |
Configuring | Running, but the configuration is still being applied. |
ToolsInstalling | Running and configured, but declared tools are still installing. |
ProviderUnavailable | The provider this workstation was created on is not available on this machine. A local workstation that names no provider stays on the one it was created with, even when your default changes. It carries on once that provider is available again. |
| (a phase name) | Not running. The reason is the phase itself (Provisioning, Stopped, Failed, …). |
Configured reasons
| Reason | Meaning |
|---|---|
Applied | The in-VM agent reported an applied configuration matching the one requested. |
Configuring | Waiting for the in-VM agent to apply the current configuration. |
ConfigurationStalled | The configuration has been waiting past the grace period and the agent has reported nothing at all. |
ConfigurationFailed | The agent reported that its own configuration pass keeps failing. |
ConfigurationNotCompleting | The agent reported that a pass is running, and it has been outstanding past the grace period without finishing. |
ConfigurationNotDelivered | The control plane’s configuration update has not reached the workstation for some time. The workstation is healthy and still applying its previous configuration. |
Stopped | The workstation is powered down, so nothing is being applied. |
UserConfigFailed | The login user, groups or sudo could not be configured. |
The first three differ in what is actually known, and that is what decides where to look:
ConfigurationStalledis silence, so it stays a diagnosis rather than a verdict. A failed boot-time agent bootstrap is the usual cause, but a slow install looks identical from outside, and the workstation is neither failed nor destroyed.ConfigurationFailedis a report, so it names a real problem. Read the agent log and the diagnostic ledger (status.diagnostics), or runrl troubleshoot.ConfigurationNotCompletingmeans the agent is alive and a pass is outstanding, so the boot console and the agent service are the two places not to look. A slow step such as a large image build and a pass that will never return look the same from outside; the workstation’s own configuration log and on-box ledger name the step.
ConfigurationNotDelivered is the only reason here that does not send you inside
the machine, because the problem is upstream of it. UserConfigFailed is about the
login user rather than the applied configuration, so a workstation can carry it
while its configuration hashes match exactly.
While ToolsReady is present its reason names the step that failed; it flips to
Installed once a workstation that previously failed has every declared tool present.
EgressEnforced reasons
The reasons on the False arm say the declared policy is not in force, and why:
| Reason | Meaning |
|---|---|
ProviderCannotEnforce | This workstation’s provider enforces nothing outside the machine: lima and wsl2. The restriction is recorded on the workstation and not in force. To enforce it, name a cloud provider, name hcs on Windows, name vz on a Mac, or name qemu on Linux. |
NoEdge | The policy names a host, or an attached Integration injects a credential or inspects a host, and no Edge serves this workstation. On a cloud there is none for its provider and region. On hcs, vz and qemu it means neither the workstation’s own network process nor an edge VM can serve it, because it was created before its provider gave each workstation its own address, or, on qemu, before Ringleader could set up the network cards its own Edge needs. With a bestEffort policy, nothing in the policy is enforced, including your cidr destinations, because a rule set is built whole or not at all. A workstation that was already enforcing an earlier policy keeps that one instead, and the message says which. If that earlier policy was enforced behind an edge instance, keeping it restricts nothing. With a strict policy the workstation is held closed. To fix it on a cloud, a namespace administrator declares an Edge, or you declare every destination as a cidr. On hcs, vz and qemu, delete the workstation and create it again. |
GatewayNotServing | An Edge serves this workstation and is not holding its policy yet. On hcs, vz or qemu with no Edge declared, that Edge is the workstation’s own network process. Otherwise the Edge’s VM may still be being built. In either case the Edge may not have confirmed the policy, or it may not be able to serve part of what you declared. On AWS and Azure, a common cause is a workstation in a subnet that already has a route table of its own: move it to the subnet your onboarding reserved for the workstations an Edge serves. On a cloud, a workstation with an egress policy is held closed meanwhile, whatever its enforcement. On hcs, vz and qemu the message says which state the workstation is in: still on the open network, behind its Edge and reaching nothing yet, or, behind an edge VM, one that is not forwarding. Ringleader restarts an edge VM that is not forwarding. For a declared Edge, run rl edge describe <name> to see its own status. |
SteeringHeld | On a cloud, the edge instance is deliberately not routing this workstation yet: its management port is not in place, or, on AWS and Azure, another machine in its subnet is holding the subnet back. The workstation stays reachable at its own address, and is held closed meanwhile. The Edge’s message names what it is waiting for. |
SubnetSteeredElsewhere | On AWS and Azure, another edge instance already routes this workstation’s subnet, and a subnet can be routed by only one. Nothing in the workstation’s manifest fixes this, so ask a namespace administrator. The workstation is held closed meanwhile. |
BoundaryIncomplete | Every declared destination is held, and the workstation can still reach undeclared addresses inside its own network without going through the edge instance, because the edge instance could not read the network’s address ranges. It is reported instead of any reason on the True arm. Check the permissions of the cloud identity that builds the Edge, change the network’s addressing, or remove the Edge. |
EndpointUnreachable | Everything you wrote fits, and an endpoint an attached Integration points at could not be given to the firewall: it did not resolve, or it resolved to nothing an address rule can hold. So that it can still reach its Integration, the workstation runs unrestricted. A strict workstation behind an edge instance is held closed instead. What has to change is the endpoint, or the DNS resolver that could not answer for it. |
DeploymentNotConfigured | Your policy is fine and the deployment is not set up to hold it. Nothing you can write fixes it, so ask whoever runs the control plane. A strict workstation behind an edge instance is held closed while this lasts. |
RuleSetNotAttached | The firewall objects exist and this workstation is not on them, so nothing is filtering its traffic. Re-applying usually rebuilds nothing, because nothing has been edited. What has to change is whatever is blocking the attach, which the message names. |
RuleSetMissing | The firewall objects that were holding the policy are gone, most often deleted in your cloud account. The workstation is not restricted at all. Restart it (rl workstation restart <name>) to rebuild them: powering a workstation on re-creates its objects. Re-applying does nothing, because nothing you wrote has changed and an unchanged manifest never reaches the provider. |
RuleSetIncomplete | Some of the objects have been deleted while the rest stand, so the workstation is not restricted. Ringleader does not repair a partly deleted set by itself, because a delete in progress looks the same. Restart the workstation (rl workstation restart <name>) to rebuild it. Seen on gcp only, where a rule set is several objects sharing one name. On aws and azure it is a single object. |
RuleSetDrifted | The objects are still there and no longer match the policy (a rule widened, a deny switched off) and Ringleader issued the repair and was refused. Rebuilding is not the fix, since the objects already exist under the right names: what has to change is whatever is denying the repair, which is usually a permission. Ringleader retries on its own cadence, so a corrected grant clears this without touching the workstation. |
On a cloud, a workstation that is held closed reaches only what it needs to stay managed by Ringleader and the endpoints of its attached Integrations, until an edge instance serves its policy. It keeps running, and you can still edit it and reach it over SSH. On hcs, vz and qemu, a workstation that is held closed reaches nothing until an Edge serves it.
The reasons on the True arm say the policy is in force:
| Reason | Meaning |
|---|---|
Enforced | The restriction is real. On a cloud workstation the message names the provider, how many destinations it holds, and the identifier of the firewall objects holding them, so you can check them in your own cloud console. On vz and qemu with a declared Edge, it names the edge VM on your machine that holds it. With no Edge, including on hcs, it names the workstation’s own network process. |
CredentialOnly | The workstation declares no egress policy, and an edge instance, an edge VM or, on hcs, vz and qemu, its own network process serves it because an attached Integration injects a credential or inspects a host. Its policy restricts nothing, and only the hosts the Integration names are decrypted. |
InterceptionPending | On hcs, vz and qemu: its Edge serves the workstation and restricts it as declared, and a host an attached Integration names is not being injected or checked. The message names the host and the cause: the rule’s allowDaemonPlacement gate is closed on this workstation (the rule sets false, or leaves it unset and the workstation’s owner is not the Integration’s author), the Secret is missing or empty, the author cannot read it, or the Edge is not reading that host yet. |
InboundUnreachable | The workstation is enforced, and routing its traffic through the edge instance took away its own inbound path, so it no longer answers at its public address and rl shell, rl code and the live checks cannot reach it. You see this when the Edge sets inboundManagement: false or publicAddress: false, or on AWS and Azure when the edge instance began routing the subnet before this workstation’s management port was in place. Reach the workstation over your own private network. |
DestinationUnreachable | The workstation is enforced, and a cidr destination cannot be reached while an edge instance routes it: on GCP and Azure any private address range, and on AWS a range inside the VPC. The message names each one. Declare a range outside those, or ask a namespace administrator whether this workstation needs an Edge. |
When more than one of these applies, the reason is the first that applies of
BoundaryIncomplete, InboundUnreachable, DestinationUnreachable, InterceptionPending
and Enforced, and the message carries every sentence.
A strict policy is checked when you apply the workstation. The workstation must name its
provider, and the namespace must hold an Edge for that provider wherever the policy needs one,
which is for a host on a cloud. An hcs, vz or qemu workstation needs no Edge, because its own network
process serves it. The check is on the merged policy. It does not require a
matching region or a ready edge machine, and an Edge that is being deleted does not count.
EgressEnforced reports what is actually held once the workstation is set up. See
Where it can be enforced.
Conditions that report a problem
These conditions are absent on a healthy workstation, except EgressCredential.
RootDiskShortfall, SSHAdmissionMissing, PublicAddressChangeNotApplied and
AgentNotReporting turn True when something needs your attention, and False once it
has cleared, so their last transition time records when it cleared.
SteeredWithoutServing is present only while it applies. EgressCredential runs the
other way: True is the healthy state.
| Condition | Reason while it applies | What to do |
|---|---|---|
RootDiskShortfall | RootDiskSmallerThanDeclared: the root filesystem measured under 80% of the declared size (rootVolumeGiB on AWS, diskGiB on GCP, osDiskGiB on Azure), or a disk was grown and the filesystem has not followed yet. The message gives both sizes. | On AWS and GCP a larger disk takes effect at the next start, so restart the workstation. On Azure the message says what happened. It clears as RootDiskMatchesDeclared. |
AgentNotReporting | AgentSilent: the control plane has not heard the workstation’s in-VM agent for more than 15 minutes. The message gives the time it last did. The workstation itself may still be reachable. | If you can reach the workstation, run rl logs <workstation> to read the agent’s log. It clears as AgentReporting once the agent is heard again, and the condition is removed when the workstation leaves Running. |
SSHAdmissionMissing | SSHAdmissionNotWritten: Ringleader could not create the firewall rule it keeps admitting SSH to this cloud workstation. The message has what the cloud refused. A rule your own onboarding created may still let you in. | Usually a missing permission in your cloud onboarding. It clears as SSHAdmissionInPlace. |
PublicAddressChangeNotApplied | PublicAddressSetAtCreate: the public address setting now resolves to a different value, because a CloudIdentity or the CloudAccount changed it. A public address is set when the machine is created. | Recreate the workstation to apply it. An edit to the workstation’s own setting fails the workstation instead. It clears as PublicAddressMatchesWorkstation. |
SteeredWithoutServing | An edge instance routes this workstation’s AWS or Azure subnet and holds no rule for it, so it reaches nothing, and nothing outside can reach it: SteeredFromAnotherNamespace, NotBoundToSteeringEdge, or NotServedBySteeringEdge. | Put the workstation in another subnet, or, as the message says, declare a policy that edge instance serves. The condition is removed when it clears. |
EgressCredential | On lima, CredentialPending: a host an attached Integration names is reaching the API without the key. The message names the host and the cause. | If the message says the allowDaemonPlacement gate is closed, set true on the rule to open it on every local workstation. Credential injection says what each value does. It reads True with CredentialHeld while the key is being added. |
AgentNotReporting applies to a workstation whose in-VM agent connects out to Ringleader
to fetch its configuration, which is the default for cloud workstations. A workstation
that Ringleader configures over SSH never carries it. Ringleader counts the 15 minutes from
the later of the last time it heard the agent and the moment the workstation reached
Running, and that last time is the agent.seenAt status field. The condition counts
toward neither Configured nor Available, so a workstation whose agent has stopped can
still be Available.
See also
- WorkstationConfig: the reusable layers a workstation merges.
- Integration: attaching a shared service, and the file that lets a program on the workstation find it.
- Edge: the VM that enforces a hostname destination and adds injected credentials.
- Configuration tutorial: a worked browser-IDE example.