The README's setup section has grown to eleven install commands across three operating systems — brew install, apt-get install, a curl | sh for one vendor CLI — and a new engineer's first day ends with protoc-gen-go: program not found or is not executable because step seven was skipped on Linux. Devbox replaces that list with a single devbox.json that pins every tool through Nix, while nobody on the team writes or reads Nix. This page migrates a typical README setup to Devbox, as part of reproducible dev shells with Nix, Devbox and direnv.

The point is not Devbox itself; it is replacing prose instructions with an executable, versioned declaration — the same principle as README-driven automation, applied to the toolchain.

Diagnostic

Measure how far developers' toolchains have already drifted. Run this on several machines and compare:

#!/usr/bin/env bash
set -euo pipefail
for t in node python3 go protoc protoc-gen-go buf jq psql; do
  if command -v "$t" >/dev/null; then
    v=$("$t" --version 2>&1 | head -1)
    printf '%-14s %-40s %s\n' "$t" "$v" "$(command -v "$t")"
  else
    printf '%-14s MISSING\n' "$t"
  fi
done

Expected bad output collected from two laptops:

laptop A: protoc        libprotoc 25.1                  /opt/homebrew/bin/protoc
laptop A: protoc-gen-go protoc-gen-go v1.34.2           /Users/a/go/bin/protoc-gen-go
laptop B: protoc        libprotoc 3.21.12               /usr/bin/protoc
laptop B: protoc-gen-go MISSING
laptop B: psql          psql (PostgreSQL) 14.12         /usr/bin/psql

Two major versions of protoc, a missing plugin, and a Postgres client from the distribution's default repository. Generated code will differ between the two machines.

Tool Versions on Two Laptops Table comparing tool versions and sources found on two developer laptops before Devbox. Tool Versions on Two Laptops Tool Laptop A Laptop B protoc 25.1 brew 3.21 apt protoc-gen-go 1.34.2 go install missing psql 16.4 brew 14.12 apt node 20.17 nvm 18.19 apt
The same README produced two different toolchains; generated code differs as a result.

Root cause

Each install instruction resolves to "whatever the package manager has today". Homebrew moves fast, Debian and Ubuntu LTS move slowly, vendor install scripts fetch the latest release, and go install ...@latest installs whatever was tagged that morning. The README captures intent ("install protoc") but not a version, and nothing verifies the result. Over months, every developer's machine becomes a unique combination. The problem is invisible until two machines generate different code or a CI job — with its own, third combination — disagrees with both. Devbox fixes this by pinning each package to an exact version resolved through Nix, recording the resolution in devbox.lock, and making devbox shell the only step a developer runs.

Devbox matters here because the obvious alternative, asking the team to adopt raw Nix, often fails on adoption rather than technology. JSON with name@version entries reads like any other manifest, the commands (add, rm, run, shell) are self-explanatory, and the Nix store is an implementation detail most developers never need to look at.

Resolution

  1. Install Devbox and initialise the project:
#!/usr/bin/env bash
set -euo pipefail
command -v devbox >/dev/null || curl -fsSL https://get.jetify.com/devbox | bash -s -- -f
devbox init

devbox init creates an empty devbox.json. The first devbox add installs Nix automatically if it is missing.

  1. Translate each README instruction into a pinned package. devbox search shows available versions:
#!/usr/bin/env bash
set -euo pipefail
devbox search protobuf | head -5
devbox add [email protected] [email protected] [email protected] \
  [email protected] [email protected] [email protected] [email protected] [email protected]
  1. Move setup commands into scripts so the README points at them instead of describing them:
{
  "packages": {
    "nodejs": "20.17.0",
    "python": "3.12.5",
    "go": "1.22.6",
    "protobuf": "25.1",
    "protoc-gen-go": "1.34.2",
    "buf": "1.39.0",
    "jq": "1.7.1",
    "postgresql": "16.4"
  },
  "env": {
    "PGHOST": "localhost",
    "PGUSER": "postgres"
  },
  "shell": {
    "init_hook": ["test -d node_modules || npm ci --silent"],
    "scripts": {
      "generate": "buf generate",
      "test": "go test ./... && npm test",
      "db": "psql shop"
    }
  }
}
  1. Shrink the README to three lines:
#!/usr/bin/env bash
set -euo pipefail
cat > README.setup.md <<'EOF'
## Setup
1. Install Devbox: `curl -fsSL https://get.jetify.com/devbox | bash`
2. Run `devbox shell` (or `direnv allow` if you use direnv)
3. Run `devbox run test` — if it passes, you are set up
EOF
  1. Connect IDEs. devbox generate direnv writes an .envrc that the VS Code and JetBrains direnv plugins read, so the IDE's language servers, test runners and terminal all use the pinned tools.
README Steps to devbox.json Flow from prose install instructions through pinned packages and scripts to a three-line README. README Steps to devbox.json 11 README steps prose, unpinned devbox add name@version scripts generate, test, db 3-line README devbox shell
Every prose step becomes either a pinned package or a named script.

Expected output

$ devbox run -- bash -c 'protoc --version; protoc-gen-go --version; psql --version'
Info: Ensuring packages are installed.
libprotoc 25.1
protoc-gen-go v1.34.2
psql (PostgreSQL) 16.4

Running the diagnostic from inside devbox shell on both laptops now prints identical versions, and every path begins with /nix/store/ or .devbox/nix/profile.

The first devbox shell on a fresh machine takes a few minutes while packages download from the binary cache; later entries take about a second. That first-run cost is worth measuring and publishing to new hires, because an unexplained five-minute pause is exactly the kind of moment where people abandon the documented path and start installing tools by hand again. A line in the README — "the first run downloads about 1.5 GB and takes 3–5 minutes" — prevents that.

It is also worth checking that the regenerated code is now stable. Run devbox run generate on both laptops and diff the output directories; with the same protoc and plugin versions, the generated files are byte-identical, and the pull requests that used to consist of nothing but churn in generated code disappear. This is often the most visible win for the team, and it is a good metric to share when proposing the change: count the generated-file-only diffs in the last quarter's pull requests before and after.

Prevention

  1. Run CI through Devbox. The jetify-com/devbox-install-action installs Devbox and caches the Nix store; then devbox run test uses exactly the laptop toolchain.

  2. Reject unpinned additions. A pull-request check that fails when any package in devbox.json has the value latest keeps the file honest.

  3. Keep system package managers out of setup docs. If a tool is needed, it goes in devbox.json; the onboarding health-check script can warn when a required tool resolves outside the Devbox profile.

Setup Steps a New Hire Performs Bar chart comparing the number of manual setup steps before and after adopting Devbox. Setup Steps a New Hire Performs README before 11 steps README after 3 steps
Measured on one team's README; the remaining steps are installing Devbox and running tests.

Platform caveats

macOS: Devbox installs Nix with a dedicated APFS volume on first use and asks for an administrator password once. On managed Macs, confirm that endpoint-security software allows creating the /nix volume.

Apple Silicon (ARM64): nearly all packages are cached for aarch64-darwin. If devbox add starts compiling, the requested version is not cached for this platform; choose a nearby cached version with devbox search.

WSL2: install Devbox inside the Linux distribution, not on Windows. Keep projects in the WSL filesystem for file-watching performance.

Windows (native): Devbox does not support native Windows; use WSL2 or a dev container, for which devbox generate devcontainer writes a starting configuration.

Rollback

Devbox does not modify system package managers, so removing it leaves previously installed tools untouched:

#!/usr/bin/env bash
set -euo pipefail
git rm devbox.json devbox.lock .envrc 2>/dev/null || true
rm -rf .devbox

Frequently Asked Questions

Do developers need to understand Nix to use Devbox?

No. Day-to-day use is devbox shell, devbox add and devbox run. Nix knowledge helps only when a package is missing from nixpkgs or needs patching, which one person on the team can handle.

What happens when a package version is not available?

devbox search <name> lists the versions Devbox can resolve. If the exact version is missing, pick the nearest available one, or add a custom Nix flake as a package source in devbox.json.

Can Devbox manage services like Postgres?

Yes, through plugins that start services with devbox services up. For multi-service stacks, most teams keep services in Docker Compose and use Devbox only for CLI tools, which keeps one clear owner for each concern.

How is Devbox different from mise?

mise pins language runtimes and some CLIs by downloading upstream binaries. Devbox pins anything in nixpkgs, including system libraries, through Nix. mise is lighter; Devbox covers more of the toolchain.