Works on My Machine: The Real Cost of Local Environment Drift and How to Finally Fix It
Photo: Lola's Big Adventure!, CC BY 2.0, via Wikimedia Commons
The phrase has become a running joke in software engineering, but the problem it describes is neither funny nor trivial. "It works on my machine" is the verbal signature of environment drift — the gradual divergence between the local development setups maintained by individual contributors on a shared codebase. And in 2024, it remains one of the most persistent sources of wasted engineering hours in American software teams.
This is not primarily a technical problem. It is an organizational one, and it compounds quietly until it becomes impossible to ignore.
How Environment Drift Happens
No team starts a project intending to let their environments diverge. Drift accumulates through small, individually reasonable decisions made over time.
A senior engineer upgrades Node.js to take advantage of a new feature. A junior developer, working from an onboarding guide written eighteen months ago, installs a version two majors behind. A contractor uses a Windows machine while the rest of the team runs macOS. Someone updates their local Postgres instance to 16 while the production database is still on 14. A .env.example file goes out of date and no one notices until a new hire spends half a day debugging a service that refuses to connect to anything.
None of these events is catastrophic in isolation. Together, they create an environment where a failing test might mean the code is broken, or it might mean the developer's local toolchain is slightly out of alignment with everyone else's. Distinguishing between those two possibilities consumes time and attention that could be spent shipping.
Three Scenarios Where Drift Caused Real Delays
Abstract descriptions of the problem are less instructive than concrete examples. The following scenarios are composites drawn from patterns that recur across engineering teams of various sizes.
Scenario one: The overnight regression. A mid-sized SaaS team in Austin ships a feature on a Friday afternoon. By Monday morning, two engineers report that the feature's integration tests are failing locally, while two others report no issues at all. The team spends the better part of a day determining that the failing engineers are running a version of a shared library that handles date parsing differently than the version installed on the passing engineers' machines. The code was never the problem. The fix takes four minutes once the root cause is identified. The diagnosis took six hours.
Scenario two: The broken onboarding. A growing startup brings on three new engineers in the same month. Each one follows the README setup instructions and hits a different wall. One cannot get the Docker network configuration to resolve correctly on an Apple Silicon Mac. Another has a conflicting global Python installation that interferes with a build script. The third runs Windows and encounters a line-ending issue that corrupts a configuration file. The engineering manager spends the equivalent of two full workdays across that month helping new hires get to a working state. That time is invisible in sprint metrics but very real in its impact.
Scenario three: The production-only bug. A team at a fintech company debugs a subtle race condition for two weeks. The bug reproduces reliably in production and staging but never locally. Eventually, they discover it is related to a difference in how their local environments handle file system events compared to the Linux containers running in their cloud infrastructure. The fix is straightforward once understood. The two weeks of investigation represent a significant opportunity cost.
The Standardization Toolkit
The good news is that the tooling available for environment standardization has matured considerably. Teams no longer need to choose between a heavyweight solution and no solution at all.
Docker and Docker Compose
For teams that need to replicate complex multi-service architectures locally, Docker remains the most widely adopted solution. A well-maintained docker-compose.yml that defines service versions, environment variables, and network configuration eliminates entire categories of drift. The tradeoff is overhead — containers introduce latency, consume memory, and require developers to internalize a mental model that is not always intuitive for engineers newer to infrastructure concepts.
Orbstack has emerged as a compelling alternative runtime for macOS users who find Docker Desktop too resource-intensive. It runs Docker containers and Linux virtual machines with significantly lower memory overhead and faster startup times, which meaningfully reduces the friction of container-based development on Apple hardware.
Devbox
For teams that want environment standardization without the full weight of containerization, Devbox — built on top of the Nix package manager — offers a compelling middle path. A devbox.json file at the project root defines the exact versions of every tool and runtime the project requires. Running devbox shell drops any developer into an isolated environment that matches that specification precisely, regardless of what is installed globally on their machine.
The appeal is the granularity. Devbox manages language runtimes, CLI tools, and system-level dependencies with the same version-pinning precision that npm or pip applies to application packages. For teams with heterogeneous machines — a mix of macOS and Linux being the most common case in US engineering teams — it reduces the "what version are you running?" debugging loop substantially.
.nvmrc, .tool-versions, and Configuration Files
For teams not ready to adopt a heavier solution, committing version specification files to the repository is a low-friction starting point. An .nvmrc file pinning the Node.js version, an .tool-versions file for asdf users, and a rigorously maintained .env.example file address the most common sources of runtime drift without requiring anyone to change their toolchain significantly.
These files are not a complete solution, but they are dramatically better than relying on documentation that goes stale.
An Onboarding Audit Checklist
If your team is ready to assess and improve its current state, the following checklist provides a practical starting point.
- Is the minimum required version of every language runtime explicitly specified in a committed configuration file?
- Does the project README reflect the actual current setup process, verified by someone other than its original author?
- Are all required environment variables documented in a committed
.env.exampleor equivalent? - Can a new engineer reach a working local state in under two hours following only the documented setup process?
- Are service dependencies (databases, caches, message queues) version-pinned in a
docker-compose.ymlor equivalent? - Is there a designated channel or process for new hires to report setup friction so the documentation can be updated?
- Has at least one team member completed the setup process from scratch in the last ninety days?
This last item is frequently overlooked. Onboarding documentation that has not been tested recently is documentation that cannot be trusted. Assigning a rotating responsibility for periodic setup verification is one of the highest-leverage investments a team can make in reducing environment drift.
The Case for Treating Environment Parity as Infrastructure
The teams that manage environment drift most effectively share a common disposition: they treat local development environment parity as an infrastructure concern, not an individual responsibility. They allocate time to maintain it, assign ownership over the tooling that supports it, and measure the health of their onboarding process with the same seriousness they apply to their CI pipeline.
The tools exist. The patterns are well understood. The remaining variable, as with most organizational problems in engineering, is the collective decision to treat the issue as worth solving systematically rather than tolerating it as an unavoidable cost of collaborative software development.