We had several shared non-production environments running 24/7. Engineers would book one, test their changes, and release it for someone else. As the organization grew, so did the contention.
At the same time, we had several other problems on the table. We needed to materially reduce non-COGS costs after years of incremental optimization. We wanted to invest further in automated testing as we moved toward continuous deployment. And with AI agents becoming part of engineering, we needed them to validate their work autonomously rather than wait for infrastructure.
These looked like separate problems, but increasingly they had the same underlying constraint: our approach to testing infrastructure.
One natural solution to the contention was ephemeral environments. Instead of a fixed pool, every PR could get its own temporary cloud environment, which would be torn down afterwards. We eventually built this capability.
But that raised another question:
If we can provision dedicated compute for every change, why does that compute need to be in the cloud?
Heavy testing in the deployment pipeline would hurt throughput and make the merge queue slower and less reliable. Ephemeral environments solve the availability problem, but at scale you're still paying for all that cloud compute. And if AI-assisted engineering increases the number of changes we produce, infrastructure shouldn't become the next bottleneck.
So we explored making local testing the default.
Our product is distributed and event-driven, with many services, infrastructure dependencies, and integrations across a wider portfolio. “Just run it locally” sounds simple until you actually try to do it.
We built local infrastructure capable of running Kubernetes and the services needed by the application on a developer's machine. Getting there exposed a long list of impediments. Some required trivial changes. Others needed meaningful engineering work. In many cases, we didn't even know upfront what would need to change until we tried.
That uncertainty created a fair amount of skepticism. Could we realistically reproduce enough of the system locally? Would the services behave the same way? What about dependencies outside our product? And even if we solved all of that, could a developer laptop actually run it?
Some of those concerns turned out to be assumptions rather than real constraints. Others became concrete engineering problems we could solve. And some were genuine constraints that simply weren't worth removing.
Developer hardware was a good example. Local machines initially looked like a hard constraint. But once we compared the cost of upgrading developer hardware with the recurring cost of cloud infrastructure, it became an economic question rather than an architectural one.
The economics worked.
We also avoided turning “local first” into dogma. Some integrations genuinely weren't worth reproducing locally. The goal wasn't to prove that everything could run on a laptop. It was to remove the constraints that mattered.
We ended up with roughly 90% of everyday testing happening locally. Most remaining scenarios could use ephemeral environments created for a PR and automatically decommissioned afterwards. For a small number of deeper portfolio integrations, we kept shared infrastructure.
This reduced our non-production cloud costs by 50% by allowing us to decommission most permanent non-production environments.
But cost was only part of the outcome.
Engineers largely stopped queuing for testing infrastructure. Local, isolated environments made it practical to move more automated and integration testing into the development loop. Heavy testing could happen before deployment instead of making the merge queue progressively slower as test coverage grew.
It also created much better conditions for AI-assisted engineering.
An agent isn't very autonomous if it needs to book a shared environment and wait for infrastructure before it can validate its work. With the application and its dependencies available locally, agents can do substantial testing as part of their own development loop. And if engineering output increases, our cloud compute bill doesn't have to increase with it.
Getting there required persistence, but also knowing when not to push further. Large changes expose plenty of reasons why something can't be done. Some constraints need to be challenged. Others are perfectly reasonable to keep.