← Blog

On-Demand Dev Environments With Coder

I needed a development server I could spin up cheaply and tear down when not using it. Self-hosted Coder turned out to be the answer. The Coder control plane runs on a tiny Scaleway Stardust that costs almost nothing to keep online; the actual workspaces are ephemeral Hetzner VMs cut from Packer-built snapshots. Learning Packer and Terraform was the second motivation, and that part has paid off in unrelated client work since.

Why Self-Hosted

A laptop is fine for most day-to-day work, but some things want a real server: a long Docker build, a noisy compile, a service that wants to listen on a real public IP. Hosted dev environments (Codespaces, Gitpod) are convenient but priced for whole-day use, and I did not want to leave a bill running while I read documentation.

Self-hosted Coder is open source, the control plane is small, and Hetzner Cloud is the cheapest sensible VPS in Europe. The pieces fit.

The Control Plane

Coder itself runs on a Scaleway Stardust instance. It is the smallest production-grade VPS I could find: one shared vCPU, 1 GB of RAM, costing in the very low single euros per month. Coder does not need much when it is not provisioning anything. It serves the dashboard, holds the templates, and talks to Hetzner only when a workspace is created or destroyed.

I keep the Stardust always-on. The cost is in the workspaces.

The Image (Packer)

The workspace images are built with Packer. The template was inspired by jktr/hcloud-packer-templates, which I had used earlier for Arch and NixOS images and then refactored for my own use. The current set covers a Manjaro base image and a Manjaro image with the full development toolchain, plus an Ubuntu image that I keep around for occasional cross-distribution work.

The build runs against the Hetzner builder, so the artifact is a Hetzner snapshot rather than a generic cloud image. The shell provisioner installs the toolchain (build tools, Ansible, Docker, fonts, browsers, the bits I want available the moment a workspace boots) and the snapshot is labelled with os-flavor and a build timestamp. The Terraform side later picks the most recent snapshot for a given flavour by querying those labels.

The Workspace (Terraform)

The Coder template is a small Terraform module that Coder runs to provision a workspace. It exposes parameters for the server type from the CX/CPX matrix (cx11 through cpx51), the OS flavour, the hostname, and a Tailscale auth key. The image is resolved by label, not ID, so a fresh Packer build is picked up automatically the next time a workspace is started.

Cloud-init does the rest: installs the Coder agent as a systemd service, mounts the persistent volume, and starts code-server (VS Code in the browser) on a local port that Coder proxies through. After about a minute and a half from the click in the dashboard the workspace is up and the editor is open.

Persistence

The workspace VM is ephemeral, but two things around it are not.

A Hetzner Volume named data is mounted as /home. Stop the workspace, the server is destroyed; start the workspace, a new server is cut from the latest snapshot, the volume is reattached, and /home comes back intact. Shell history, dotfiles, project clones, build caches and any state I want across restarts live on that volume.

A Hetzner Primary IPv4 named ak IPv4 is reused the same way. The workspace always comes up at the same address, which means SSH config, DNS, and any service I have running there do not need updating between sessions.

Tailscale closes the loop. The cloud-init script joins the workspace to my tailnet at boot using an ephemeral auth key from the Coder template parameters. I reach the workspace by its tailnet name from the laptop without exposing anything additional on the public IP.

In Practice

Day-to-day cost depends on how many hours I leave a workspace running. On a cpx21 it is in the very low single euros per active day. The Scaleway Stardust running Coder itself is a rounding error.

Spin-up time is short because the Packer image already has the toolchain. There is no apt install race when a workspace starts. The slowest part is the Hetzner VM boot, then cloud-init, then a few seconds of code-server install on first startup.

The secondary motivation, learning Packer and Terraform properly, has been the part that stuck. Both have come up in client work since, and having built something non-trivial with them on my own time made the later projects faster.

The repository the Packer templates were forked from is at github.com/jktr/hcloud-packer-templates. Coder is at coder.com.