← Back
Home › Systems › CI/CD and developer tooling

cloud-dev-env

Gives developers a workstation running on remote machines that they reach from an editor or browser, set up consistently for each project.

Codespaces, Gitpod

Open the cloud-dev-env pagecloud-dev-env Hdr histogramdata-structures: Log-linear bucketed histograms with bounded relative error for latency percentilesDeterministic simulationtestingdistributed: Run the whole system in one thread under a seeded scheduler with simulated network, disk and clock, so every failure replays from its seed.›Primary-backup replicationdistributed: One node orders all writes and ships them to standbys; the acknowledgement rule (none, one, all) sets what a crash loses, and an external arbiter decides who takes over.›Cgroups and namespaceshardware-os: Linux namespaces limit what a process can see; cgroup v2 limits and accounts what it may use. Containers, sandboxes and job schedulers are built from the two.›Checkpoint and restarthardware-os: Periodically save enough state to resume a computation after a crash, preemption or move, with an interval that balances save cost against lost work.›File system watchinghardware-os: Treat OS change notices as hints, debounce them, and rescan and diff a snapshot whenever notices overflow or may be lost.›Hardware virtualization kvmhardware-os: VT-x/AMD-V via KVM: vCPU run loop, VM exits, MMIO emulationProcess supervisionhardware-os: A parent process starts services, reaps their exits and restarts them under a delay and burst-limit policy, then stops them with SIGTERM before SIGKILL.›Pseudo terminalshardware-os: Pseudo-terminals, job control and terminal escape handlingVm snapshot restorehardware-os: Snapshot an initialised VM/isolate heap and restore it for fast cold startsReverse proxynetworking: A server-side intermediary that terminates client connections and forwards each request to one of a pool of upstream servers, with routing, health checks, timeouts and safe retries.›WebSocketsnetworking: Full-duplex message framing over one upgraded HTTP connection; the standard way to push real-time data to browsers.›Coverage-guided fuzzingsecurity: Mutate inputs, run an instrumented target, and keep any input that reaches a new code edge; feedback turns blind guessing into incremental search.›Mutual TLSsecurity: TLS where the client also proves a certificate key, so every connection carries a verified, rotatable machine identity.›OAuth 2 and OpenID Connectsecurity: Delegated authorization (OAuth 2) and login on top of it (OIDC); use authorization code + PKCE for every user-facing client.›Secrets managementsecurity: Keep keys and passwords out of code and logs, give workloads short-lived identity instead of static keys, and make rotation routine.›SSH protocol (transport, auth,channels)security: A three-layer protocol on one TCP connection: encrypted packet transport, user authentication, then multiplexed channels with windows for shells, files and port forwards.›WireGuardsecurity: A UDP tunnel whose peers are public keys, with a 1-RTT Noise handshake, one fixed cipher suite and a key-to-address routing table.›Copy on write snapshotsstorage: Point-in-time snapshots by sharing pages/extents and copying only on writePoint-in-time recoverystorage: Periodic base backups plus a continuously archived change log let you rebuild the state as of any instant, then branch from there.›Graceful drainingsystems: On a termination signal, stop taking new work, finish in-flight work until a deadline, tell clients to reconnect elsewhere, then exit; clients resume from a token.›Layered image buildsystems: Build container images as cached, content-addressed layers from a build graphLazy image pullingsystems: Start containers before full download by fetching image chunks on demandWarm pool provisioningsystems: Keep pre-initialized instances ready to cut cold startsCooperative schedulingconcurrency: Green threads/coroutines yielding at await points on a user-space schedulerFault injectiondistributed: Inject latency, errors, kills and partitions via proxies, netem or hooksRecord replaydistributed: Record every nondeterministic input (time, randomness, I/O, model and tool outputs, per-tick player input) and re-execute deterministically for debugging, replays and state rebuildLeasesdistributed: A lock that expires on its own: bounded-time ownership that survives crashes and partitions, given bounded clock drift and fencing tokens on writes.›Copy-on-write forkhardware-os: fork() gives the child the parent's whole address space by sharing pages read-only and copying one only when someone writes it; vfork and posix_spawn skip even that.›Dirty page trackinghardware-os: Write-protection or PML logs of modified guest/process pagesPtrace process controlhardware-os: Ptrace: attach, read and write registers and memory, single-step, intercept signalsmmap and the page cachehardware-os: How read() and mmap reach file data through the kernel page cache, what readahead and madvise do, and why databases use their own buffer pool instead.›PRNGs (PCG, xoshiro)numerics: Small, fast, statistically strong non-cryptographic generators: PCG32 and xoshiro256**, with seeding, parallel streams and unbiased ranges.›HTTP/1.1 message parsingnetworking: Parse HTTP/1.1 requests and responses incrementally from a byte stream: RFC 9112 body-length rules, chunked decoding and the strictness that stops request smuggling.›HTTP/2networking: Binary multiplexed framing for HTTP over one TCP connection: streams, per-stream and per-connection credit windows, HPACK header compression and the limits that contain abuse.›Trie url routingnetworking: Radix-tree HTTP routers with params and wildcards; matching priority+ 24 moreTransparent interceptionnetworking: Redirect traffic to a local proxy (iptables TPROXY/REDIRECT, eBPF) without app changesSyscall interceptionsecurity: Intercept syscalls via ptrace, seccomp user notify or a user-space kernelFencing tokensdistributed: A number that grows with every lock grant and is checked by the protected resource, so a paused ex-holder's late write is rejected.›Virtual memory and the TLBhardware-os: How page tables and TLBs translate every address, what TLB reach means, and when 2 MiB or 1 GiB huge pages pay off on Linux.›+ 15 moreEbpfhardware-os: Verified in-kernel programs on hooks (kprobes, XDP, tc) with mapsSeccomp bpfsecurity: Syscall filtering with seccomp BPF programs+ 12 more+ 2 more

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:

 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 onCreateCommand and updateContentCommand (not postCreateCommand) 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 with MAP_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).

  1. The user picks a repository and a machine type; the control plane authenticates them and creates a build job.
  2. 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.
  3. The platform injects per-user data: Git credentials via external authentication, user secrets, dotfiles.
  4. The agent starts, runs postCreateCommand-style setup and startup scripts, and reports Running.
  5. 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.
  6. The editor server and terminal run in the workspace; file edits stay on the workspace disk.

Idle stop and resume.

  1. 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.
  2. Ephemeral resources are destroyed; the persistent disk remains.
  3. On next open the compute is recreated, the disk reattached, postStart and 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

  1. 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.
  2. Cost per idle workspace: auto-stop on inactivity, persistent disks only, quotas per user.
  3. Host density: containers pack tightly; microVMs trade density for isolation.
  4. 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).
  5. Storage: copy-on-write bases, snapshot retention rules, deletion of abandoned workspaces.
  6. Image distribution: a registry near the compute and layered images bound the cold-host case.

Failure modes

FailureSymptomCauseFix
Slow startminutes before the editor opensno prebuild or pool, large image, setup in postCreateCommandmove setup to prebuild-time commands, lazy pulling, warm pool
Prebuild is staleopens quickly but has outdated dependenciesprebuild not refreshed after a pushrefresh on push or schedule, updateContentCommand to catch up
Snapshot clones share identityduplicate random numbers or tokens across workspacesresumed from one snapshot many timesre-seed entropy, rotate identifiers on restore
Workspace unhealthyagent not connectingfirewall blocks outbound HTTPS or UDP, agent crashedoutbound rules, supervised agent, direct vs relay diagnostics
Direct connection failshigh latency, always relayedNAT or firewalls block UDP, no STUN pathallow UDP to STUN and high ports, run regional relays
Terminal dropssession lostproxy without WebSocket support or short idle timeoutenable upgrade, timeouts above heartbeat
File watcher errorseditor stops updatinginotify watches exhausted on a big treeexclude large folders, raise the kernel limit in the image
Credential leaktoken in a prebuild or logsecrets used in onCreateCommand, baked into image layerssecrets only after claim, short-lived tokens
Port exposureprivate service reachableport visibility set to publicprivate by policy, org-only sharing

Real implementations

namewhat it isrepolicencenotes
Coderself-hosted CDE platform: Terraform templates, agent, WireGuard networkingcoder/coderAGPL-3.0some features (prebuilds, workspace proxies) need a premium licence
Gitpod (Classic)workspaces from a .gitpod.yml, prebuildsgitpod-io/gitpodAGPL-3.0renamed Ona; the README no longer recommends this version
DevPodclient-only tool that runs dev containers on any backendloft-sh/devpodMPL-2.0glue between local IDE and a machine
Eclipse CheKubernetes-based CDE platformeclipse-che/cheEPL-2.0enterprise teams, Kubernetes
code-serverVS Code in the browsercoder/code-serverMITan editor server, not a platform
OpenVSCode Servera VS Code server reachable from a browsergitpod-io/openvscode-serverMITsame architecture as Gitpod and Codespaces, per its README
Eclipse Theiaextensible cloud and desktop IDE frameworkeclipse-theia/theiaEPL-2.0 OR GPL-2.0-only with Classpath exceptiondifferent editor core from VS Code
FirecrackermicroVM monitor for strong isolation and snapshotsfirecracker-microvm/firecrackerApache-2.0building block, not a CDE
Dev container specthe portable environment definitiondevcontainers/specCC-BY-4.0implemented 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

Similar systems

Share the most building blocks with this one.