A dev environment set up carelessly causes weeks of lost time — subtle version differences between machines, dependencies that install differently on macOS and Linux, "it broke on CI but works here" investigations that consume entire afternoons. The investment in a reproducible, code-defined setup pays back every time someone new joins the team or a machine needs rebuilding.
What changed in 2026
mise (formerly rtx) replaced asdf as the preferred polyglot version manager — faster, written in Rust, reads .tool-versions and .mise.toml files.
- Devcontainers are mainstream — VS Code, Cursor, JetBrains Rider, and GitHub Codespaces all support the devcontainer spec natively.
- Nix +
nix develop is the choice for teams that need hermetic, byte-identical environments, especially those doing reproducible builds.
- Docker Desktop 5.x ships with Compose v2 built in (
docker compose not docker-compose).
- AI coding assistants work best in well-structured environments; a working LSP, formatter, and test runner pay off more than ever in 2026.
Step 1 — runtime version management with mise
# Install mise
curl https://mise.run | sh
# .tool-versions (in project root — checked into git)
node 22.4.0
python 3.12.4
go 1.23.0
# Install all versions declared in .tool-versions
mise install
# Verify
node --version # 22.4.0
python --version # 3.12.4
mise activates the correct versions automatically when you cd into the project directory. No more nvm use before every command.
Step 2 — services via Docker Compose
# docker-compose.yml
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: myapp_dev
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:
docker compose up -d # start services
docker compose down -v # stop and remove volumes
docker compose logs -f # follow logs
Run the language runtime on the host (via mise), run dependencies in containers. This gives you fast native code execution and clean service isolation.
Step 3 — environment variables
# .env.example — COMMIT THIS to git
DATABASE_URL=postgres://dev:dev@localhost:5432/myapp_dev
REDIS_URL=redis://localhost:6379
API_KEY=your_key_here
LOG_LEVEL=debug
# .env — DO NOT COMMIT (add to .gitignore)
# Copy from .env.example and fill in real values
cp .env.example .env
Use dotenv-cli, direnv, or your framework's built-in .env loading. direnv auto-exports variables when you enter the directory:
# .envrc
dotenv .env
Step 4 — devcontainer for full reproducibility
// .devcontainer/devcontainer.json
{
"name": "My App",
"image": "mcr.microsoft.com/devcontainers/javascript-node:22",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"forwardPorts": [3000, 5432, 6379],
"postCreateCommand": "npm ci",
"customizations": {
"vscode": {
"extensions": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}
}
}
GitHub Codespaces picks this up automatically. New team members are one button click from a working environment.
Comparison of environment approaches
| Approach |
Reproducibility |
Complexity |
Best for |
| System packages + mise |
Medium |
Low |
Small teams, personal projects |
| Docker Compose + mise |
High |
Low-medium |
Most production teams |
| Devcontainer |
Very high |
Medium |
Teams with diverse OS/machines |
Nix + nix develop |
Near-perfect |
High |
Reproducible builds, infra teams |
| Remote dev (Codespaces) |
Very high |
Low (hosted) |
Onboarding, ephemeral work |
VS Code / Cursor extensions to install
| Extension |
Purpose |
esbenp.prettier-vscode |
Auto-format on save |
dbaeumer.vscode-eslint |
Inline lint errors |
ms-python.python |
Python LSP, debugger |
golang.go |
Go LSP, test runner |
mtxr.sqltools |
Database query UI |
ms-azuretools.vscode-docker |
Docker Compose UI |
Store these in .vscode/extensions.json so VS Code recommends them to teammates.
How to pick
- Solo project or prototype → mise +
.env + docker compose for services. Done in 30 minutes.
- Team of 3–10 with mixed OS → Add a devcontainer spec. Eliminates cross-platform issues.
- Monorepo with many services → Nix or devcontainer per service, orchestrated with Tilt or Skaffold.
- Regulated environment, auditable builds → Nix with flakes for byte-identical reproducibility.
Common mistakes
node_modules on a Docker volume mount. Bind-mounting source code into a container while the container also installs node_modules inside it leads to platform-mismatched binaries. Either build a proper image or develop on the host.
No .env.example. New engineers spend hours figuring out required variables. The template takes 5 minutes to write and saves hours across the team's lifetime.
Installing runtime versions globally with Homebrew. brew install node gives you one version system-wide. When project A needs Node 20 and project B needs Node 22, things break.
Not committing editor config. .editorconfig, .vscode/settings.json, and .vscode/extensions.json should be in git — they ensure consistent formatting before the linter even runs.
Long onboarding docs instead of automated setup. If setup requires reading a 20-step document, replace it with a make bootstrap or ./scripts/setup.sh that runs those steps.
What to skip
- Vagrant for new projects — heavy, slow to boot, largely replaced by devcontainers and Colima.
- Installing Postgres directly on macOS with Homebrew for team projects — Docker Compose ensures everyone runs the same version.
- A different
.env per person with no .env.example — environment sprawl and missing variable bugs follow.
FAQ
What is the difference between mise and nvm/pyenv/rbenv?
Those are runtime-specific managers. mise replaces all of them with one tool and one config file (.tool-versions). It is faster and polyglot.
Should I run my app inside Docker or on the host?
For development, running the app on the host (via mise) gives faster iteration — no rebuild cycle. Run services (DB, cache, queues) in Docker. Run the full stack in Docker only for integration/E2E testing.
How do I share environment variables across the team securely?
Use a secrets manager (1Password Secrets Automation, Doppler, or AWS Secrets Manager) and never put real secrets in git. The .env.example is the template; real values come from the secrets manager.
What about Windows developers?
WSL2 (Windows Subsystem for Linux) with a devcontainer is the recommended path — the container runs on Linux inside WSL2, giving full parity with macOS/Linux setups.
Where to go next