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, hcs or WSL2 on Windows, or qemu on Linux) or in your cloud account. On Windows, hcs is the default when it registers. WSL2 is the fallback when hcs does not register and WSL2 does. 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 | Not used. No provider reads this field, so setting it sizes nothing. Size a cloud workstation with providerConfig.<cloud>, or with --machine-type on rl workstation create. |
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. |
recycleNonce | string | Change this to have the agent on the workstation rewrite the block of authorized keys the configuration owns and run every script declared with runPolicy: onRecycle, once. A WorkstationPool writes it when it recycles a member. The outcome is reported in status.recycle. Like restartNonce, only the change matters, and the value is letters, digits, dots, underscores and hyphens, up to 512 bytes. |
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 | object | OS image: {distribution, version}. A string is read as no image. A cloud workstation that names no image boots Debian 13. |
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}. This is the only place to declare the login user. The default shell is /bin/bash, and the user gets passwordless sudo unless you set sudo: false. Without user, the login name is your account name on the computer you created the workstation from. On Windows that name is turned into a Linux login name: any DOMAIN\ part is dropped, letters are lowercased, and only letters, digits, _ and - are kept, so CORP\John.Doe becomes johndoe. If nothing usable is left, the name is dev. |
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, and it takes effect only on a deployment that turns workstation identity on. 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. |
recycle | object | The result of the last recycle spec.recycleNonce asked for: {nonce, outcome, failedScript, finishedAt}. outcome is Succeeded or Failed, and failedScript names the first onRecycle script that failed. Absent until a recycle has run. |
skippedMounts | []string | On vz, the folders in providerConfig.mounts that the last start left out because the folder was not on your Mac. Absent when every folder was shared, and on every other provider. |
user | string | The SSH login user. Without identity.user, it is the name described under identity. |
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. |
idle | object | {state, condition}: the workstation’s last report against its idle policy. state is idle or busy. A busy report names what kept the workstation in use in condition, such as sshSessions or ports. |
idleSince | string | When the control plane first saw an unbroken run of idle reports, as a timestamp. The idle timeout counts from here. A busy report clears it, and so do removing the idle policy and any stop or start. |
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. |
egress.recorded | []string | ["allowed"] when the workstation’s allowed connections are recorded, and absent otherwise. An Edge that sets records.allowed lists it on every workstation it serves, whatever version that Edge runs, so it can appear a little before the first connection is counted. An audit policy lists it only once whatever serves the workstation can record: behind an edge instance, once that Edge reports a proxy from the same release as your control plane; behind an edge VM, once that VM reports a current proxy for this workstation; and on hcs, vz or qemu with no Edge, once the workstation’s own network process runs the same build as the Ringleader daemon. Until then an audit workstation lists nothing. See What an Edge records. |
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. |
userConfig | object | Present only while Ringleader cannot set up the login user. See Fields that report a problem. |
jobResult | object | {done, code}: the exit status the workstation’s job reported. Absent until a job reports one, and cleared by a stop or start. See Finished. |
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
Four 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.
userConfig appears while Ringleader cannot set up the login user, its groups or its
sudo setting, as {state, reason, since, message}. since is when the failure started and
message describes it. reason says which of two problems it is:
PushFailing: Ringleader reaches the workstation, and setting up the login user keeps failing. TheConfiguredcondition reports it asUserConfigFailed.Unreachable: the workstation does not answer SSH as the login user its configuration declares, so nothing can be set up. No condition reports this one.
rl workstation describe prints it as User config. It clears once the login user is set up.
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. It ignores every condition listed after ToolsReady below, 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. |
Finished | Whether the job the workstation runs has ended. Absent until a job reports an exit status. See Finished reasons. |
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. |
StoppedResolveStuck | The workstation is stopped, and its configuration or its provider has failed to load for long enough that it cannot start. The message names which one and why. It clears the first time it loads again. |
| (a phase name) | Not running. The reason is the phase itself (Provisioning, Stopped, Failed, …). |
Ready reasons
Ready is True with reason Running once the machine is up. While the workstation is on its
way there, or on its way down, the reason is the phase it is in, such as Provisioning,
Starting or Stopped. When Ready is False because something needs you, the message says
what, and the reason is usually one of these:
| Reason | Meaning |
|---|---|
ImmutableFieldConflict | You changed a field the machine can only take when it is created. The message names the field. Revert the change, or delete the workstation and create it again. |
StopRequiredChangeNotApplied | A change that needs the machine stopped, such as a resize, was still not in place after a full stop and start. The message names the field. Edit or revert it to try again. |
UnknownProviderName | The workstation names a provider this version of Ringleader does not know. Check the spelling in provider or requirements. |
ProviderMismatch | The workstation’s requirements now name a different provider from the one its machine is on. To keep the machine, remove the provider requirement or name the provider it is on. To move it, delete the workstation and create it again. |
CloudIdentityNotMatched | Your cloud refused the request, and no CloudIdentity in the namespace selects this workstation, so the request used the deployment’s own credential. The message lists the workstation’s labels. Label it to match a CloudIdentity’s selector. |
StoppedResolveStuck | The same as the Available reason of that name, for a stopped workstation that has never run. |
TerminatingFailed | The workstation is being deleted and removing its machine failed. It stays in Terminating, and Ringleader keeps trying. The message names the step that 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. |
ConfigurationIncomplete | The control plane could not put together the whole configuration the workstation declares, for example because something it refers to is not there yet. Until it can, Configured stays False. Nothing is wrong with the workstation, and this clears on its own once the configuration is complete again. |
Stopped | The workstation is powered down, so nothing is being applied. |
UserConfigFailed | The login user, groups or sudo could not be configured. See userConfig. |
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.
Finished reasons
Finished tells you when a job you run on the workstation, such as a CI build, has ended and
what it exited with. To report one, have the job write its exit status as a number from 0 to
255 to ~/.ringleader/job-exit in the login user’s home:
mkdir -p "$HOME/.ringleader"
make test; echo $? > "$HOME/.ringleader/job-exit"Then wait for it from anywhere, whatever the job exited with:
rl wait workstation my-box --for=condition=Finished| Reason | Status | Meaning |
|---|---|---|
JobSucceeded | True | The job exited with status 0. |
JobFailed | True | The job exited with a non-zero status, which the message gives. |
NoJobResult | False | The workstation reported a result and then stopped or started, so no result stands for the machine as it is now. |
Finished is True for a failed job too, because it reports that the job is over. The
reason and the message carry the result. A failed job says nothing about the workstation’s
health, so Finished counts toward neither Configured nor Available. A workstation that
has never reported a result carries no Finished condition.
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 is audit, 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 or audit policy the workstation is held closed. To fix it on a cloud, a namespace administrator declares an Edge. With a strict or bestEffort policy you can instead 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. You can check those objects in your own cloud console. On hcs, vz and qemu with a declared Edge, it names the edge VM on your machine that holds it. With no Edge, it names the workstation’s own network process. |
Audit | The policy is audit and an Edge serves the workstation: a declared Edge or, on hcs, vz and qemu, its own network process. The route is live and no destination is enforced, which the message says. The Edge records the workstation’s allowed connections. See What an Edge records. |
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. On an audit policy, Audit takes
Enforced’s place in that list.
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.
An audit policy is checked when you apply the workstation too. The workstation must name its
provider, though a WorkstationConfig that declares audit need not. The provider must be GCP,
AWS, Azure, hcs, vz or qemu, and lima and wsl2 refuse the policy. On a cloud, the namespace must
hold an Edge for that provider that is not being deleted, and the region is not checked. An hcs,
vz or qemu workstation needs no Edge. Where you apply it matters too:
- On a control plane, the operator must have turned audit on. Until then the apply is refused with a message that says so, and only whoever runs the control plane can change that.
- On your own machine, a one-shot
rlrefusesaudituntil a daemon that supports it is running, and its message tells you to runrl daemon start. If that daemon cannot serve the workstation’s own network process, the message tells you to runrl daemon restartif the daemon is older than yourrl, updaterlif the daemon is newer, or declare an Edge.
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.
- WorkstationPool: a set of workstations built from one template and leased out one at a time.
- 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.