Devcontainers

Run a repository's own devcontainer.json on a workstation, without rewriting it as a WorkstationConfig.

Many repositories already describe their development environment in devcontainer.json, the format VS Code, GitHub Codespaces, and most other dev-environment tools read. Ringleader can run that file directly: attach the repository to a workstation as a source, set one flag, and the workstation clones the code, builds the devcontainer, and puts your shell inside it.

The point is that you do not have to translate an environment your team already maintains into a WorkstationConfig. The file runs under the reference devcontainer CLI, exactly as upstream defines it, and Ringleader adds what the format has no words for: the machine itself, its lifecycle, its placement, and who may reach it.

Declaring one

A devcontainer is a property of a checkout, so it is declared on the source that carries the repository:

apiVersion: workstations.ringleader.dev/v1
kind: Workstation
metadata:
  name: app-box
  namespace: dev
spec:
  sources:
    - name: app
      path: /workspaces/app
      git:
        url: git@github.com:acme/app.git
      devcontainer:
        enabled: true
  providerConfig:
    cpus: 4
    memory: 8   # GiB; see Sizing below
FieldTypeDescription
devcontainer.enabledboolRun this source’s devcontainer on the workstation. Exactly one source may set it; a second is refused at apply.
devcontainer.configPathstringOptional path to the devcontainer file, relative to the source’s path. Omit it for the CLI’s own discovery: .devcontainer/devcontainer.json, then .devcontainer.json.

Three things follow from the declaration:

  • The source’s path is the workspace folder. The checkout is mounted into the container there, exactly as the format expects. A source with no path declares nothing usable and is refused.
  • The docker and devcontainer-cli devtools are implied. You do not have to declare them; declare one yourself only to pin its version.
  • A managed service named devcontainer is synthesized. It runs devcontainer up with a readiness probe, and with a start timeout sized for a real image build rather than systemd’s 90-second default.

Where your shell lands

The workstation is a VM, and the devcontainer runs inside it on Docker. Your tools land in the container by default, because that is the environment you asked for:

You runIt lands
rl shell, ssh <box>, VS Code Remote SSH, JetBrains Gatewayinside the container, as the file’s remoteUser
rl shell --script, rl file cpinside the container
rl shell --host, rl file cp --hoston the VM
scp, sftpon the VM (see below)

Two deliberate ways to reach the VM itself:

  • The escape account. Every devcontainer workstation gets a second login user, <user>-host (so login user dev gets dev-host), whose shell is an ordinary VM shell and which accepts the same SSH keys. It exists so a workstation whose container failed to build is still reachable and repairable, and it is the way to point an editor at the VM rather than at the container.
  • --host, or a run-on-host prefix. rl shell <box> --host -- <command> and rl file cp --host run on the VM. With plain ssh, prefixing the remote command does the same: ssh <box> run-on-host uptime.

One asymmetry to know about: scp and sftp use SSH’s file subsystem, which does not go through the login shell, so they land on the VM while rsync and rl file cp land in the container. This matters less than it sounds for the workspace itself: the checkout is mounted, so a file under the source’s path is the same file either way. It is only a real difference for paths outside the workspace folder.

Readiness

The workstation reaching Configured means devcontainer up succeeded and the container passed its readiness probe. A devcontainer that fails to build blocks Configured exactly like any other failed configuration step: the workstation reports the failing step, rl troubleshoot has its output, and the escape account still works, so the machine stays administrable while you fix the file.

Expect the first build to take a while. devcontainer up pulls the base image, builds one layer per Feature, and runs the file’s lifecycle commands, so a fresh workstation with a heavyweight devcontainer is minutes, not seconds.

Ports

The container’s ports are published to the VM. Reaching them from your own machine stays an explicit ports / LocalBinding declaration, exactly as on any other workstation. Nothing inside the container is auto-forwarded to your laptop, including anything the file lists in forwardPorts.

Sizing

hostRequirements in the file describes what the container needs, and Ringleader reads it as a floor on the workstation’s own sizing, never as a substitute for it: a declared providerConfig wins. Size the VM for the container plus the image build plus the Docker engine. Disk is the sharp edge, because an image build plus its layers can be tens of gigabytes and hostRequirements.storage describes only the workspace.

Users

identity governs the VM: SSH login, sudo, home directory. The file’s remoteUser governs the container, applied by the reference CLI as upstream specifies. You log in as identity.user and land in the container as remoteUser; neither is rewritten to match the other.

Changing the file

A changed devcontainer.json does not rebuild the container by itself. A rebuild is a large event (a full image build, on a machine you may be working on), so Ringleader never fires one off a git pull. To pick up a change, restart the devcontainer service on the VM (rl shell <box> --host -- sudo systemctl restart devcontainer), or drive the devcontainer CLI there yourself.

What v1 supports

Because the file is executed by the reference implementation, “supported” means Ringleader does not interfere:

  • All three build shapes: image, build.dockerfile, and dockerComposeFile with service and runServices.
  • Features, including their options and install ordering.
  • The in-container lifecycle commands (onCreateCommand, updateContentCommand, postCreateCommand, postStartCommand) and waitFor.
  • remoteUser, containerUser, and updateRemoteUserUID.
  • workspaceFolder and workspaceMount.
  • containerEnv and remoteEnv.
  • mounts, overrideCommand, init, privileged, capAdd, securityOpt, and runArgs.

What v1 does not support

The format is large, and a page that does not say what is skipped would read as a compatibility promise. These are deliberate, each for a stated reason:

Not supportedWhy
forwardPorts auto-forwarded to your machineContainer ports are published to the VM; reaching them from a laptop stays an explicit ports / LocalBinding declaration.
customizationsTool-specific by definition. customizations.vscode.extensions is the one likely to be revisited.
initializeCommandSpecified to run on the host, and here the host is the VM rather than your laptop, so running it would honor the letter and break the intent. It is refused loudly with a diagnostic.
postAttachCommandrl shell is SSH and has no attach event to hang it on.
shutdownActionThe workstation’s lifecycle is Ringleader’s (stopped, ttl, teardown). A file in a repository must not decide when a billed VM stops.
hostRequirements as sizingRead as a floor on providerConfig and reported when the machine is smaller, never a substitute. See Sizing above.
Image and Feature verificationNo signature or provenance check is performed on the image, the build.dockerfile base, or any Feature artifact. A deliberate deferral, stated here so it is not assumed.
A private registry for the file’s own image/build refsspec.image.pull authenticates the workstation’s image; the devcontainer’s own references have no credential channel yet.

See also