“Works on my machine” has a new audience. New hires need a running project before lunch, and coding agents need a setup that works without a person nursing it. Both are the same problem: the environment has to be described in a file and recreated on demand. Three approaches dominate that conversation, and they make different promises. Dev Containers describe a container. Nix describes a build and its entire dependency graph. Devbox puts a friendlier interface on top of Nix.

This comparison covers what each one guarantees, what it costs to adopt, and where it breaks down. If you only need a refresher on the container baseline, our piece on local development environments with Docker covers it.

What each tool actually is

Dev Containers

A dev container is, per the Development Containers specification site, a structured metadata format for a containerized development environment. The core artifact is devcontainer.json, a JSON-with-comments file that usually lives in a .devcontainer folder at the repository root. VS Code, GitHub Codespaces, and JetBrains IDEs support it, and an open-source CLI serves as the reference implementation. The specification also defines Features, reusable chunks of install scripts, and supports prebuilds through CI integrations.

{
  "name": "web-app",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:22",
  "features": {
    "ghcr.io/devcontainers/features/docker-in-docker:2": {}
  },
  "forwardPorts": [3000],
  "postCreateCommand": "npm ci"
}

The environment is a container image plus setup steps. It needs a container runtime, which for most local setups means Docker or a compatible engine.

Nix flakes

Nix builds software from declarative expressions and stores every result by a hash of its inputs. In a development context, a flake defines a devShell, and nix develop drops you into it. The flake.lock file pins the exact revision of every input, such as the nixpkgs package set, so two machines evaluating the same flake see the same package versions.

{
  description = "web-app dev shell";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";

  outputs = { self, nixpkgs }:
    let
      systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
      forAll = f: nixpkgs.lib.genAttrs systems (s: f nixpkgs.legacyPackages.${s});
    in {
      devShells = forAll (pkgs: {
        default = pkgs.mkShell {
          packages = [ pkgs.nodejs_22 pkgs.postgresql_16 ];
        };
      });
    };
}

One caveat belongs up front. The Nix manual flags both the nix-command interface and flakes as experimental features, and nix develop itself is documented as experimental with an interface subject to change. Many teams use flakes in production regardless, but you are accepting that the interface can move.

Devbox

Devbox, from Jetify, generates isolated development environments using the Nix package manager. You list packages in a devbox.json file and run devbox shell. Its documentation says Docker is not required, and it can generate a Dockerfile or a devcontainer.json with devbox generate.

{
  "packages": ["nodejs@22", "postgresql@16"],
  "shell": {
    "init_hook": ["echo 'Environment ready'"],
    "scripts": {
      "test": ["npm test"]
    }
  }
}

The docs describe it as managing operating-system-level packages the way a language package manager handles libraries, and they reference a devbox.lock file for pinning. Because it wraps Nix, you get Nix’s package set without writing Nix expressions.

How they compare

Strength of the guarantee

Containers give you isolation and a consistent Linux userland. They do not, by themselves, give you pinned versions: a Dockerfile that runs apt-get install or pulls a floating tag is only as reproducible as you make it. You earn reproducibility by pinning image digests and package versions, and that discipline is manual.

Nix is the strongest of the three on this axis, because pinning is the default behavior and the lock file covers the full dependency tree. Devbox inherits much of that through Nix, with a simpler surface and a correspondingly smaller set of things you can control.

Setup and learning cost

Dev Containers are the cheapest to start. A person who knows Docker can write a working devcontainer.json quickly, and editor integration handles the rest. Devbox is the next easiest: a JSON file and one command, with no new language to learn. Nix flakes carry the steepest curve. The Nix language is its own thing, error messages can be opaque, and debugging a failing derivation is a skill your team has to grow.

Editor and cloud support

Dev Containers have the broadest first-class tooling, including Codespaces for cloud-hosted setups and the CLI for automation. Nix and Devbox environments run in your normal terminal, which suits people who prefer their own editor setup, but editor integration usually means adding direnv or a similar shell hook. Devbox also supports generating devcontainer files, so the two can coexist.

Performance and platform

Nix and Devbox run natively on the host with no virtualization layer, which Jetify cites as a benefit. Dev Containers on macOS and Windows run inside a Linux virtual machine, and file-sharing performance between host and container is a recurring pain point for large repositories. On Windows, Nix generally means working inside WSL2. Check the current platform support for your OS before committing a team to either.

Matching production and CI

Containers have a natural advantage when production is also containers: the same base image can anchor development, CI, and deployment, with a development layer on top. Nix environments can be used in CI too, and a locked flake means CI and laptops run identical tool versions, but you need a Nix installation on the runner. Devbox can be run the same way or can emit a container definition.

Choosing between them

Rather than a universal winner, match the tool to the constraint you actually have.

Choose Dev Containers when your team already uses Docker, you want cloud development environments, or you onboard people who do not want to learn a new toolchain. Accept that you will need to pin versions deliberately.

Choose Nix flakes when exact reproducibility is worth a learning investment: polyglot repositories, native dependencies that differ across machines, or builds that must be bit-for-bit repeatable. Budget for at least one person who becomes the team’s Nix expert, and accept the experimental-feature caveat.

Choose Devbox when you want most of Nix’s pinning without the language. It suits teams who want a fast, native shell with locked package versions and a path to containers later.

Combine them when it helps. A common arrangement is Devbox or a Nix flake as the source of truth for tool versions, with a generated or hand-written devcontainer.json for people who want an editor-integrated container.

A short evaluation plan

Before standardizing, run a one-repository trial against these questions:

  1. Can a fresh machine reach a passing test run from a clean clone with no manual steps?
  2. After you update one pinned dependency, does the change show up in version control, with everyone else getting the same result?
  3. Does the setup work in CI with the same definition, not a parallel one?
  4. Can a coding agent in a sandbox start from the file alone, without a person answering prompts?
  5. What happens on your least-favorite platform, whether that is Windows, Apple silicon, or an older Linux distribution?

The tool that passes those checks with the least ceremony is the right one for your team. Reproducibility only holds if the environment definition lives in the repository, runs in CI, and is the path everyone uses, so pick the approach people will keep using, then enforce it.