Reference Guide

The Ringleader resource model: how the objects relate, and a field-by-field reference for each kind.

Everything in Ringleader is a resource: a Kubernetes-style object with apiVersion, kind, metadata, spec, and (for most kinds) status. You declare the spec; the control plane and the in-VM agent write status as they bring the real machine in line with it. You never edit status.

This guide covers the kinds you author most often:

  • Workstation: one managed development machine, a VM on your computer or in your cloud account.
  • WorkstationConfig: a reusable configuration layer applied to workstations.
  • Devtools: the curated install recipes a config can declare, one page per tool.
  • Tool configuration: declarative configuration for the tools on a workstation, one page per tool.
  • Devcontainers: run a repository’s own devcontainer.json on a workstation, unchanged.
  • ConfigMap: reusable text a config’s scripts and files reference instead of inlining it.
  • Integration: connect workstations to a shared external service, such as an LLM gateway or an MCP server.
  • Credential injection: let workstations call an API that needs a key, without the key being on them.
  • Edge: the VM that enforces egress rules by host name and adds injected credentials, one per cloud and region.
  • LocalBinding: a device-local port/socket forward from a workstation to your machine.
  • CloudIdentity: the credential broker that lets the control plane create cloud VMs.
  • CloudAccount: the link between one organization and one cloud account, recording the cloud identity to use.
  • SSHKey: forward a local private key into workstations as an ambient ssh-agent.
  • Providers: how a workstation is placed on vz, lima, qemu, wsl2, hcs or a cloud.
  • Access control: namespaces, ownership, RBAC roles, and Grants.
  • Policy: constrain what a workstation manifest may name: today, which git repositories it may clone.
  • Organizations: the tenancy layer above namespaces, holding a company, its verified domain, and who administers it.
  • ServiceAccount: a non-human identity CI and automation sign in as, with no stored secret.

Start with the relations below to see how they fit together, then dive into a specific kind.

Relations between objects

A Workstation is the center of the model. The other kinds shape it (configuration, credentials), connect to it (forwards), or filter what it reaches (an Edge).

How they connect

  • Workstation → WorkstationConfig. A workstation receives config either by explicit reference (spec.configs: [{name, priority}]) or by selector match: a WorkstationConfig whose spec.selector.matchLabels match the workstation’s labels attaches automatically. All matching configs plus the workstation’s inline spec are merged as config layers (lower priority applies first) into one effective spec, which rl workstation get-resolved-configuration prints on demand.

  • Workstation → Integration. An Integration does not reach a workstation directly: it compiles to a generated WorkstationConfig named integration-<name>, which then attaches by the ordinary selector rules above. That generated layer sits at a negative priority, so it loses to every config a person writes, but not to the workstation’s own inline spec, which is the base every layer is applied over.

  • Workstation → CloudIdentity. When a workstation routes to a cloud provider, the control plane picks the CloudIdentity in the same namespace whose spec.provider matches and whose spec.selector.matchLabels match the workstation’s labels (highest priority wins). That identity brokers the short-lived credentials used to create the VM, and can inject provider-config defaults/overrides.

  • CloudIdentity → CloudAccount. A spec.cloudAccountRef points at a cluster-scoped CloudAccount, naming which cloud identity to use for the organization that owns the namespace. This selects per-organization federation, in which Ringleader presents a signed, organization-scoped assertion to your cloud’s own trust configuration and stores no credential at all.

    Alternatively, a spec.impersonation block selects the legacy shared-intermediary chain; with neither field, the control plane’s own default identity is used. cloudAccountRef and impersonation are mutually exclusive: they are the credential paths, not layers. Ringleader stores no cloud credential at rest on any of them.

  • Workstation → Edge. A workstation never names an Edge. On a cloud, the Edge in the workstation’s namespace that covers its provider and region serves it, through an edge instance: a VM in your cloud account. On vz or qemu, an Edge with provider: vz or provider: qemu runs an edge VM on your own machine instead. On hcs, vz or qemu with no Edge, the workstation’s own network process serves it. A workstation with an egress policy, or with a key an Integration adds, sends its outbound connections through that VM or process. It applies the workstation’s own egress policy to each connection. It also adds the keys the workstation’s httpProxy Integrations name, read from a Secret, so the keys are never on the workstation. The example below shows both.

  • CloudIdentity → Edge. An Edge has no field naming a CloudIdentity. On a cloud, its edge instance is built with a CloudIdentity for the same provider from the Edge’s own namespace, chosen as the Edge page describes.

  • Workstation → LocalBinding. Forwarding is not implicit. When a workstation with a spec.defaultLocalBinding template first reaches Running, the daemon seeds one device-local LocalBinding from it (once, deletion is honored). You can also author LocalBinding objects directly.

Reading status

Every kind that reports a status carries a top-level status.message: the one human headline for what is going on with that object, in a sentence. It is universal, so you can read any object’s state without first learning that kind’s sub-status keys or condition vocabulary:

rl workstation get my-box -o jsonpath='{.status.message}{"\n"}'
rl binding get -o jsonpath='{.status.message}{"\n"}'

Where a kind also has conditions, the two never disagree: they are derived from one computation. status.message is the headline; conditions[].message is the per-aspect detail, and the condition’s reason is a value from a closed set you can branch on.

Status is server-owned (a client can never write it) and it is curated: an internal failure surfaces as a generic message plus a reference id, never a raw error, so a status is always safe to paste into a ticket.

Namespace scoping

Most kinds are namespaced, and cross-references stay within a namespace. A workstation’s configs, its CloudIdentity, the Edge that serves it, and the Secrets its CloudIdentity and Integrations read all live in the workstation’s namespace. Two exceptions:

  • CloudAccount is cluster-scoped and owned by an organization, not a namespace. A CloudIdentity may only reference an account belonging to the organization its own namespace belongs to.
  • LocalBinding is device-local: it never leaves the machine that materializes it and is never synced or mirrored.

Apply order

When you apply a multi-document manifest, Ringleader writes the objects in order of their kind, so that references resolve. Among the kinds in this guide, the order is:

  1. Organizations
  2. Namespaces
  3. OrgPolicies
  4. ServiceAccounts
  5. Policies
  6. ConfigMaps
  7. WorkstationConfigs
  8. Secrets
  9. Integrations
  10. SSHKeys
  11. CloudAccounts
  12. CloudIdentities
  13. Edges
  14. Workstations
  15. LocalBindings
  16. Grants

An Integration comes after the Secrets, so the key it names already exists when you apply it. A Grant comes last, because it names an object that must already exist.

Example: one Edge, two teams

Two teams share the namespace acme on Google Cloud. The platform team calls the GitHub API with a shared token, and the data team installs Python packages. Each team reaches only its own hosts, and the token is never on a workstation.

A namespace administrator applies the Edge, the Integration, the WorkstationConfigs and the Policy. Each team member applies their own workstations. The example assumes the namespace already has a GCP CloudIdentity that places workstations in a us-east4 zone.

  1. Declare the Edge for the region:

    apiVersion: core.ringleader.dev/v1
    kind: Edge
    metadata:
      name: gcp-us-east4
      namespace: acme
    spec:
      provider: gcp
      region: us-east4

    On AWS and Azure the Edge also names a subnet. See Declare an Edge.

  2. Store the token in a Secret, and apply an Integration that adds it to the platform team’s requests to the GitHub API:

    rl secret create github-pat -n acme --from-string token=<your-token>
    apiVersion: workstations.ringleader.dev/v1
    kind: Integration
    metadata:
      name: github-api
      namespace: acme
    spec:
      type: httpProxy
      selector:
        matchLabels:
          team: platform
      httpProxy:
        rules:
          - host: api.github.com
            inject:
              header: Authorization
              value: "Bearer ${secret:github-pat/token}"

    The Edge sets the Authorization header on every request a team: platform workstation sends to api.github.com. Tools on the workstation call the API as if they had signed in, and the token is not on the workstation. Credential injection covers local workstations, MCP servers and rotating the token.

  3. Apply one WorkstationConfig per team. Each selects its team’s workstations by label and carries an egress policy:

    apiVersion: workstations.ringleader.dev/v1
    kind: WorkstationConfig
    metadata:
      name: platform-egress
      namespace: acme
    spec:
      selector:
        matchLabels:
          team: platform
      egress:
        enforcement: strict
        destinations:
          - host: github.com
          - host: "*.githubusercontent.com"
    ---
    apiVersion: workstations.ringleader.dev/v1
    kind: WorkstationConfig
    metadata:
      name: data-egress
      namespace: acme
    spec:
      selector:
        matchLabels:
          team: data
      egress:
        enforcement: strict
        destinations:
          - host: pypi.org
          - host: files.pythonhosted.org

    With strict, a workstation whose provider cannot hold the policy is refused when you apply it. With bestEffort, it is created and reports what is not enforced.

    The platform layer does not list api.github.com. A host an Integration adds a key for is added to the workstation’s allowlist, on ports 80 and 443. An inspection-only rule would not add its host: it checks a host that the policy already permits. A host destination covers only those two ports, so the platform team clones from GitHub over HTTPS, not SSH.

  4. Give each workstation its team’s label. A strict policy also needs the workstation to name its provider:

    apiVersion: workstations.ringleader.dev/v1
    kind: Workstation
    metadata:
      name: platform-01
      namespace: acme
      labels:
        team: platform
    spec:
      provider: gcp

    A data team workstation is the same, with team: data.

  5. Apply a Policy that limits which destinations any manifest in the namespace may name:

    apiVersion: core.ringleader.dev/v1
    kind: Policy
    metadata:
      name: egress-ceiling
      namespace: acme
    spec:
      priority: 50
      rules:
        egressDestinations:
          default: deny
          allow:
            - github.com
            - "*.githubusercontent.com"
            - api.github.com
            - pypi.org
            - files.pythonhosted.org

    Configuration layers add their destinations together, so without a Policy a member can widen their own workstation’s allowlist with a layer of their own. With it, a Workstation, WorkstationConfig or Integration that names any other destination is refused when it is applied:

    remote: forbidden: spec.egress.destinations[0].host names "pastebin.com",
    which your organization's or namespace's policy does not permit (HTTP 403)

    The list includes api.github.com because the Policy also judges the hosts an Integration names. See Policy.

Check the result

Ringleader builds the edge instance when the first workstation that needs it reports in. Until the edge instance serves a workstation, the workstation is held closed, and its EgressEnforced condition reads False and says why. Once it is served, rl edge get gcp-us-east4 -n acme shows Ready True with reason Serving, and rl workstation describe platform-01 -n acme shows EgressEnforced True with reason Enforced.

From a shell on each workstation, these requests give these results:

Requestteam: platformteam: data
curl https://github.comWorksConnection reset
curl https://api.github.com/userReturns the token owner’s GitHub userConnection reset
curl https://pypi.org/simple/requests/Connection resetWorks
curl https://example.comConnection resetConnection reset

One Edge serves both teams, because it applies each workstation’s own policy to each of its connections. Both workstations also reach what they need to stay managed by Ringleader, which you never list. The Edge and your cloud’s firewall are outside the workstation, so nothing running on it, including an AI coding agent running as root, can undo the restriction. What a denied connection looks like explains the resets.

What a label does not restrict

Labels are written by each workstation’s owner. Within one namespace, a label chooses which workstations get an allowlist or a token, and it does not keep a member out:

  • A member can add team: platform to their own workstation, and the Edge then adds the token to its requests.
  • A member can remove team: data from their own workstation, and the data team’s allowlist no longer applies to it. The Policy refuses any destination outside its list, but it does not make a workstation declare an egress policy, and a workstation with none is not limited to an allowlist.

To keep a token or a host away from some people, give the people who need it a namespace of their own. An Integration reaches only workstations in its own namespace, and that namespace needs an Edge of its own. On AWS and Azure, read Subnets have one owner first, because an edge instance routes a whole subnet.