How it is built
Architecture overview
A cloud development environment (CDE) is a developer workstation that runs on someone else's machine and is reached through an editor or a browser. A user clicks "create" on a repository; the platform finds or builds an image, starts an isolated compute unit with a disk, runs the repository's setup commands, installs an agent inside it, and then relays editor, terminal, SSH and web-port traffic between the user and that agent. When the user stops working, the compute stops and the disk stays. Three problems dominate the design: start time (a person waits for this), isolation (the workspace runs arbitrary code, often with credentials) and reachability (the workspace sits behind NAT and the user is on a laptop). The answers are prebuilt images and warm pools, container or microVM sandboxes, and a control plane that brokers connections. Codespaces, Coder and Gitpod are the shapes referenced below.
Parts:
- Environment definition — what the workspace must contain —
layered-image-build(no page yet), the dev container spec, Cgroups and namespaces - Control plane — templates, workspace state machine, authentication, scheduling — OAuth 2 and OpenID Connect, Secrets management, Primary-backup replication, Point-in-time recovery
- Compute and isolation — container or microVM per workspace — Cgroups and namespaces,
hardware-virtualization-kvm(no page yet) - Fast start — prebuilds, pools, lazy images, snapshots —
warm-pool-provisioning(no page yet),lazy-image-pulling(no page yet),vm-snapshot-restore(no page yet),copy-on-write-snapshots(no page yet), Checkpoint and restart - Workspace agent — runs inside, starts services, exposes the shell and files — Process supervision,
pseudo-terminals(no page yet), File system watching - Access plane — editor, terminal, SSH, port forwarding — SSH protocol (transport, auth, channels), WireGuard, Reverse proxy, WebSockets
- Operations — Graceful draining,
hdr-histogram(no page yet), Mutual TLS, Deterministic simulation testing, Coverage-guided fuzzing
browser / IDE ──HTTPS+WebSocket──► control plane ◄──── provisioner (Terraform / orchestrator)
CLI / ssh │ auth, templates, │ creates, stops, deletes
│ │ workspace state (DB) ▼
│ │ ┌── workspace (container or microVM) ──┐
│ direct WireGuard tunnel │ relay (fallback)│ agent: startup scripts, services, │
└────────────────────────────────┴──────────────────► pty, ports, file watching, sshd │
│ disk: repo + home (persistent) │
└──────────────────────────────────────┘
image build / prebuild ──► registry ──► pulled or snapshot-restored on a fresh host
How the parts fit together
Environment definition
The portable description is the dev container specification: a devcontainer.json that points at an image or a
Dockerfile, adds "features", forwarded ports, and lifecycle commands. The spec splits environment creation into
initialisation, image creation, container creation and post-creation commands, and defines
onCreateCommand, updateContentCommand and postCreateCommand, run in that order on first creation (source:
dev container spec). The three exist because of one design constraint: cloud services may run onCreateCommand
and updateContentCommand while prebuilding, so those commands "will not typically have access to user-scoped assets
or secrets", while postCreateCommand runs once the container is assigned to a user and can use their secrets (source:
dev container spec, reference). That contract is the line between "build once, share" work and "per-user" work.
The image is built with a layered builder (layered-image-build, no page yet; see the Container image builder
page). Alternatively a template can define the workspace
directly, as Coder does: workspaces "are defined with Terraform" and can be EC2 VMs, Kubernetes pods or Docker
containers (source: Coder README). The trade-off: a dev container file lives in the repository and works in many tools (Codespaces, DevPod), while a
platform template can describe disks, networks and quotas that a repository file cannot.
Control plane
The control plane is a conventional service with one important state machine. In Coder, workspaces are created from templates through the CLI, API or dashboard; a create request becomes a build job, and Terraform provisions the resources; the lifecycle states are Running, Stopped (ephemeral resources destroyed, persistent ones idle), Deleted, and the broken Failed and Unhealthy (source: Coder docs, workspace lifecycle). Terraform runs in provisioner daemons that can be built into the server or run externally so a malicious template cannot reach the host (source: Coder docs, provisioners). The split between persistent resources (home volume) and ephemeral ones (compute) is the central cost lever, and Coder's docs say production templates must protect persistent resources against accidental deletion (source: Coder docs, resource persistence).
State (workspaces, templates, users, builds) lives in a database, so Primary-backup replication and Point-in-time recovery apply: losing the table that maps workspaces to disks orphans every user's data. Users log in through OAuth 2 and OpenID Connect; Coder documents OIDC login and OAuth-based "external authentication" for Git providers, which lets the workspace use the user's Git provider identity (source: Coder docs). Secrets management here means short-lived, scoped credentials delivered after the workspace is claimed, not long-lived keys baked into a prebuild.
Compute and isolation
The workspace runs untrusted code (the repository's build scripts, a dependency's install hook) next to the user's credentials. Two isolation levels are in common use.
- Container: Cgroups and namespaces give the workspace its own process tree, mounts and network view and cap CPU and memory. It is cheap and starts in seconds, but the host kernel is shared, so a kernel bug is a cross-tenant bug. Platforms often add a second boundary: Codespaces gives each codespace a dedicated virtual machine, with the dev container running inside it (source: GitHub docs, Codespaces deep dive).
- MicroVM: a minimal virtual machine on
hardware-virtualization-kvm(no page yet). Firecracker's stated mission is secure, multi-tenant, minimal-overhead execution in microVMs on KVM, with a minimalist device model that cuts attack surface and start time (source: Firecracker README). The price is a guest kernel per workspace and memory that is not shared as easily as in containers.
The choice is about who the tenants are: a company running its own developers' workspaces can accept container isolation in its own cluster; a public service running strangers' code needs a VM boundary. Either way the sandbox should hold no standing cloud credentials.
Fast start
Start time is scheduling a host, getting the image there, running setup, starting the agent, and connecting. Each has a lever.
- Prebuilds. Codespaces prebuilds run the configuration up to and including
onCreateCommandandupdateContentCommand(notpostCreateCommand) in a temporary codespace via a workflow, store the container snapshot, and on creation download that snapshot onto a fresh VM and run the remaining commands. GitHub's docs suggest prebuilds help if creating a codespace takes more than two minutes, and note that each prebuild consumes billable storage (source: GitHub docs, prebuilds). That is the standard trade: pay storage and build time to remove user-visible wait. - Warm pools.
warm-pool-provisioning(no page yet) keeps ready workspaces waiting. Coder's prebuilt workspaces are exactly this: the admin defines parameter presets and the number of instances; Coder keeps that many running through a reconciliation loop "similar to Kubernetes" and assigns one when a matching create request arrives, with a TTL on unclaimed ones (source: Coder docs, prebuilt workspaces). The contract is that preset parameters are fixed at provision time, so a request must match a preset to use the pool. - Lazy images.
lazy-image-pulling(no page yet) starts the container while the image is still arriving and fetches files on demand, which only works if the image was written in a seekable format; useful when images are gigabytes. - Snapshots.
vm-snapshot-restore(no page yet) resumes a microVM from a saved memory and device state instead of booting. Firecracker maps the memory file withMAP_PRIVATE, so pages load on demand and writes go to copy-on-write anonymous memory; that gives very fast loading, but the memory file must stay around for the resumed VM's lifetime, and snapshot files are trusted by Firecracker, so the platform must authenticate and encrypt them. Firecracker's docs also warn that resuming one snapshot more than once duplicates "unique" state such as random seeds, identifiers and tokens, which they call insecure without a mechanism to re-seed (source: Firecracker snapshot docs). A pool built on snapshots therefore needs a per-clone identity step. - Copy-on-write disks.
copy-on-write-snapshots(no page yet) lets many workspaces share a base volume and diverge on write, and gives cheap backup points for user data. Checkpoint and restart is the process-level relative: save a running process tree and resume it elsewhere, which could park an idle workspace with its processes intact, at the cost of restoring kernel objects (sockets, devices) that a plain restart avoids.
Workspace agent
One small program per workspace is the platform's presence inside it. It is a supervisor for the things that should be alive (setup scripts, dev servers, the editor server), restarts them under a policy and reaps their exits. In Coder, the agent runs the startup scripts and opens connections to the workspace for SSH, the terminal and IDEs (source: Coder docs, workspace lifecycle). It reports health: a workspace whose agent cannot accept connections is "Unhealthy".
Two services of the agent deserve their own blocks. Terminal access needs pseudo-terminals (no page yet): the agent
allocates a pty, runs a shell on its slave side and streams bytes to the client; xterm.js is the browser front end
and its README says plainly that it is not a shell and must be connected to a process through a library such as
node-pty (source: xterm.js README). Editor and indexer integration needs File system watching:
change notices are hints that can overflow or be lost, so the right design debounces them and rescans on overflow.
VS Code documents the practical symptom, an ENOSPC "unable to watch for file changes in this large workspace" error
when the inotify limit is hit, fixed by excluding large folders or raising the kernel limit (source: VS Code docs,
Linux setup). The workspace image should raise that limit up front.
Access plane
The workspace is behind NAT and the user is somewhere else, so the platform provides the path. Coder's networking docs describe a topology of workspaces, control plane and users in which clients prefer a direct connection between user and workspace and fall back to relaying through the control plane when NAT traversal fails; all user-to-workspace connections are end-to-end encrypted, and the direct path is a WireGuard tunnel between the CLI and the agent, set up with the help of STUN (source: Coder docs, networking). Direct connections are not available from the browser, so web traffic always goes through the control plane or a regional workspace proxy.
SSH protocol (transport, auth, channels) is the lingua franca on top of that tunnel: shells, file
transfer and port forwards are SSH channels, and the editors' remote features (VS Code extension, JetBrains
plugin, plain ssh coder.<workspace>) reuse the platform CLI to establish the connection (source: Coder docs,
networking).
For browser access and forwarded ports the system is a Reverse proxy: it terminates the user's TLS, authenticates the session, and forwards the request to the right workspace app, with WebSockets for the terminal and for the editor's own protocol. Coder's docs state that any reverse proxy or ingress between the control plane and clients must support WebSockets. Port visibility is a policy decision: Codespaces lets the owner set a forwarded port to private, organisation-only or public (source: GitHub docs, forwarding ports). A shared dev server on a public port is the common way a workspace leaks.
Operations
Rolling out the control plane without killing connections is Graceful draining: stop accepting new
sessions, keep established relays until a deadline, tell clients to reconnect. Internal calls (provisioner, agent) use Mutual TLS or token-authenticated TLS; time to
ready and connect latency go in an hdr-histogram (no page yet) so the tail is visible. Deterministic simulation testing
suits the workspace state machine (create, stop, crash, retry, delete under injected failures); fuzzing suits the
parsers on the path (devcontainer files, templates, proxy headers) with Coverage-guided fuzzing.
Data and control flow
Create and open a workspace (prebuilt path).
- The user picks a repository and a machine type; the control plane authenticates them and creates a build job.
- If a matching prebuilt workspace or prebuild snapshot exists, it is claimed (or restored onto a fresh host); otherwise a provisioner builds the resources and the image is pulled.
- The platform injects per-user data: Git credentials via external authentication, user secrets, dotfiles.
- The agent starts, runs
postCreateCommand-style setup and startup scripts, and reports Running. - The client connects: the CLI tries a direct WireGuard tunnel (STUN for discovery), else relays through the control plane; the browser always uses the control plane or a workspace proxy.
- The editor server and terminal run in the workspace; file edits stay on the workspace disk.
Idle stop and resume.
- After a period without activity (Codespaces default: 30 minutes, with a maximum lifetime of 12 hours; Coder: an inactivity timeout set by the template admin; sources: GitHub docs, Coder docs) the platform stops the workspace.
- Ephemeral resources are destroyed; the persistent disk remains.
- On next open the compute is recreated, the disk reattached,
postStartand attach commands run (per the dev container spec), and the user is back where they left off.
Prebuild refresh. A push triggers the prebuild job; Codespaces runs one per configuration at a time and keeps only the last queued push, so frequent pushes do not stack builds (source: GitHub docs, prebuilds).
Scaling
- Time to ready first, since users feel it: prebuilds, pools sized by demand and time of day (Coder prebuilds can scale on schedules), lazy pulling, snapshots.
- Cost per idle workspace: auto-stop on inactivity, persistent disks only, quotas per user.
- Host density: containers pack tightly; microVMs trade density for isolation.
- Control-plane connections: every browser terminal and relayed session holds a long-lived WebSocket. Scale the proxy tier horizontally and add regional workspace proxies for distant teams (Coder offers these as a premium feature).
- Storage: copy-on-write bases, snapshot retention rules, deletion of abandoned workspaces.
- Image distribution: a registry near the compute and layered images bound the cold-host case.
Failure modes
| Failure | Symptom | Cause | Fix |
|---|---|---|---|
| Slow start | minutes before the editor opens | no prebuild or pool, large image, setup in postCreateCommand | move setup to prebuild-time commands, lazy pulling, warm pool |
| Prebuild is stale | opens quickly but has outdated dependencies | prebuild not refreshed after a push | refresh on push or schedule, updateContentCommand to catch up |
| Snapshot clones share identity | duplicate random numbers or tokens across workspaces | resumed from one snapshot many times | re-seed entropy, rotate identifiers on restore |
| Workspace unhealthy | agent not connecting | firewall blocks outbound HTTPS or UDP, agent crashed | outbound rules, supervised agent, direct vs relay diagnostics |
| Direct connection fails | high latency, always relayed | NAT or firewalls block UDP, no STUN path | allow UDP to STUN and high ports, run regional relays |
| Terminal drops | session lost | proxy without WebSocket support or short idle timeout | enable upgrade, timeouts above heartbeat |
| File watcher errors | editor stops updating | inotify watches exhausted on a big tree | exclude large folders, raise the kernel limit in the image |
| Credential leak | token in a prebuild or log | secrets used in onCreateCommand, baked into image layers | secrets only after claim, short-lived tokens |
| Port exposure | private service reachable | port visibility set to public | private by policy, org-only sharing |
Real implementations
| name | what it is | repo | licence | notes |
|---|---|---|---|---|
| Coder | self-hosted CDE platform: Terraform templates, agent, WireGuard networking | coder/coder | AGPL-3.0 | some features (prebuilds, workspace proxies) need a premium licence |
| Gitpod (Classic) | workspaces from a .gitpod.yml, prebuilds | gitpod-io/gitpod | AGPL-3.0 | renamed Ona; the README no longer recommends this version |
| DevPod | client-only tool that runs dev containers on any backend | loft-sh/devpod | MPL-2.0 | glue between local IDE and a machine |
| Eclipse Che | Kubernetes-based CDE platform | eclipse-che/che | EPL-2.0 | enterprise teams, Kubernetes |
| code-server | VS Code in the browser | coder/code-server | MIT | an editor server, not a platform |
| OpenVSCode Server | a VS Code server reachable from a browser | gitpod-io/openvscode-server | MIT | same architecture as Gitpod and Codespaces, per its README |
| Eclipse Theia | extensible cloud and desktop IDE framework | eclipse-theia/theia | EPL-2.0 OR GPL-2.0-only with Classpath exception | different editor core from VS Code |
| Firecracker | microVM monitor for strong isolation and snapshots | firecracker-microvm/firecracker | Apache-2.0 | building block, not a CDE |
| Dev container spec | the portable environment definition | devcontainers/spec | CC-BY-4.0 | implemented by Codespaces and others |
GitHub Codespaces is the best-documented hosted example, but a closed service.
The alternative: a local container plus a good laptop, or a plain remote machine reached over SSH. Choose a CDE when onboarding time, source-code control, GPU or large-machine access, or ephemeral per-branch environments matter more than the platform cost and the loss of offline work.
Sources
- Dev container specification: reference, devcontainer.json reference
- GitHub Codespaces docs: prebuilds, deep dive, idle timeout
- Coder docs: networking, workspace lifecycle, prebuilt workspaces, external authentication, resource persistence
- Firecracker: README, snapshot support
- xterm.js: README; VS Code: Linux setup, file watcher