GitHub·7 min read·

GitHub Codespaces: Dev Environments as Configuration

Your dev environment belongs in version control. A devcontainer.json plus GitHub Codespaces reproduces the exact setup on any machine in minutes.

NB

Netbay Cloud Team

Netbay Engineering

On this page

"Works on my machine" is a versioning problem dressed as a personality flaw. The fix is to make the machine a file: devcontainer.json declares your environment — image, tools, scripts, ports, extensions — in a single JSON document, and GitHub Codespaces is one well-tested runner for that same standard. The same file drives a local container on your laptop and a cloud workspace for a new teammate, so there is no separate "setup" to drift.

The devcontainer contract

A devcontainer.json at the repository root describes how to build the container the project expects:

javascript
{
  "name": "netbay-api-dev",
  "image": "mcr.microsoft.com/devcontainers/javascript-node:20",
  "features": {
    "ghcr.io/devcontainers/features/docker-in-docker:2": {}
  },
  "postCreateCommand": "npm ci",
  "forwardPorts": [3000],
  "portsAttributes": {
    "3000": { "label": "app" }
  },
  "customizations": {
    "vscode": {
      "extensions": ["esbenp.prettier-vscode"],
      "settings": { "editor.formatOnSave": true }
    }
  },
  "remoteUser": "node"
}

Read it as a contract: "image" is the base toolchain, "features" bolt on extras without hand-writing a Dockerfile, "postCreateCommand" runs once when the workspace is first created, "forwardPorts" and "portsAttributes" publish what a dev needs, and "customizations" carry editor settings and extensions so a new hire inherits your formatting rules without a setup ritual.

Lifecycle hooks: the order that matters

The container lifecycle has three hooks and getting the order right matters:

  • **postCreateCommand** runs after the container is created, on a fresh clone — this is where the full "npm ci" and migrations live.
  • **postStartCommand** runs every time the container starts, on the cheap operations.
  • **postAttachCommand** runs when an editor or terminal attaches — print a banner, keybindings recovery, nothing expensive.

A common mistake is putting a ten-minute build in postStart and then wondering why every restart is slow. Post-Create is the expensive one; post-Start should be able to finish in seconds.

Features and dotfiles

Devcontainer features are composable bundles — docker-in-docker, protoc, a specific Node runtime — pulled from a registry when the container is built. Beyond the container itself, a dotfiles repository supplies shell configuration, so the terminal feels like home no matter which fresh container file created it. Together they are the difference between "here is a container" and "here is the developer experience." Features are versioned by their registry reference, so the same devcontainer.json resolves to the same toolchain next month; that reproducibility is the entire point, and it is why the file belongs in the same review as the code it supports rather than in a private gist nobody updates.

Test it before the cloud

Codespaces is optional; the devcontainer standard is not locked to it. You can preflight the same file on any Docker-capable host using the devcontainer CLI, which is exactly what CI or a stubborn laptop should do:

bash
# build the workspace, run a command inside it, then tear down
npx --yes @devcontainers/cli up --workspace-folder .
npx --yes @devcontainers/cli exec --workspace-folder . pnpm test

If the command works in a plain container on your own machine, it will work in the cloud workspace — same file, same result. For teams whose builds outgrow laptop batteries altogether, the same devcontainer image runs happily on a Linux VPS, where the heavy compiles happen at always-on speed while you edit over SSH.

environment as code devcontainer.json one file, both sides build + mount image + features lifecycle hooks create / start IDE local container same devcontainer.json devcontainer CLI preflights it cloud workspace codespaces, or any runner heavy builds move off the laptop environment is reproducible: image, hooks, extensions, dotfiles one drift source removed from the team

When Codespaces is the right call

Reach for cloud dev environments when onboarding matters more than anything — every new hire starts from the same file within minutes — when the local machine cannot hold the build, and when a clean-room workspace for risky dependency experiments beats wiping your own laptop. Keep the cost question honest: session time adds up, so pair heavy paid machines with cheap, correct defaults rather than defaulting to the biggest option. And keep the file boring: the base image, one or two features, hooks that finish fast. One caveat: a cloud workspace inherits the same access controls as your account, so treat it as a boundary, not a security wall — secrets belong in the environment at runtime, never baked into the image or the dotfiles repository.

Takeaway: devcontainer.json turns your environment into a reviewed, versioned artifact, and Codespaces is just its most convenient runner. The same standard runs in a container on your machine or on a Linux box you control — like a Lucknow DC01 VPS from Netbay, ready in under 60 seconds at netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance