On this page
I’ve written before about turning a laptop into a production server and the $0 stack that grew around it. This is the sequel, and it’s the more interesting test: not hosting my projects, but standing up a complete, isolated mirror of a client’s production stack — on my own hardware, at zero incremental cost.
The client is a UAE e-commerce platform. The stack is not small: an Angular storefront and admin (an Nx monorepo), a Node/Express backend, a separate PDF invoice microservice, Redis, and Elasticsearch. The need was a full parallel environment — somewhere features could be tested end to end without touching the client’s infrastructure, and without waiting on anyone to provision it.
So the whole thing went onto the Lenovo i7 in my office, deployed through Coolify, published to the world through Cloudflare Tunnel, on subdomains of my own site.
Why a mirror, and why mine
The value of a staging mirror isn’t the hardware — it’s the isolation. Its own database, its own search index, its own Redis, its own domains. Nothing shared with production, so nothing you do in it needs permission or carries risk.
Running it on my own infrastructure had a second benefit I didn’t fully appreciate until later: it forced every implicit assumption in the stack out into the open. Production environments accumulate configuration nobody remembers; a from-scratch clone has to rediscover all of it. Every one of the problems below was a fact about the client’s stack that nobody had needed to know until the stack ran somewhere new.
What actually broke
Container DNS: getaddrinfo EAI_AGAIN. The services came up and immediately couldn’t find each other — DNS resolution failing inside the Docker network. The general shape of this bug: a service configured with a hostname that’s only valid on one network topology (a public name, or another environment’s internal name) running on a different one. Getting every service pointed at the name that’s actually resolvable from its network, in this deployment, is tedious detective work, and it’s the single most common failure mode I hit when re-homing containerized stacks.
Elasticsearch timeouts behind the tunnel. Search queries that were fine in the client’s environment started timing out in the mirror. The path from the backend to Elasticsearch now included hops the original deployment never had, and the search client’s request timeout wasn’t written with that in mind. The fix I landed was the durable kind: I made the request timeout configurable (SEARCH_REQUEST_TIMEOUT) instead of hardcoded, so each environment can set what its own topology needs. Hardcoded timeouts are always someone else’s topology.
Cloudflare Pages serving stale builds. Partway through, I moved the Angular admin off the laptop entirely and onto Cloudflare Pages — static hosting is what Pages is for, and it took load off the box. The build pipeline swaps environment configuration at build time via a deploy script, so one codebase produces a mirror-flavored build without client-specific edits leaking into source. The gotcha was caching: Pages happily served stale builds after deploys, which cost me a debugging session that ended in cache configuration rather than code.
Keeping the fork clean. The mirror needed small, environment-specific tweaks. The discipline that mattered: those tweaks must never ride along into upstream pull requests. Mirror-specific configuration lives in environment variables and deploy scripts, not in committed code, so the diff between my fork and the client’s repo stays reviewable and intentional.
The shape of the result
What came out the other side:
- Full stack running under Coolify on the laptop, each service a container, deploys triggered from git.
- Public access through Cloudflare Tunnel — no ports opened, no static IP, TLS at the edge.
- The admin frontend on Cloudflare Pages with per-environment builds.
- Total incremental cost: electricity.
The client got an environment where a tester can exercise credit notes, quotations, and imports against realistic data with zero chance of touching production. I got something too: proof that the laptop-server pattern holds up beyond side projects, at the fidelity level client work demands.
Where the pattern’s edge is
I’ll say clearly what this is not for. This mirror carries no uptime obligation — if the laptop dies, a tester waits a day and nobody’s business stops. The moment an environment has real users or contractual uptime, it belongs on managed, redundant infrastructure, and I move it there. Cheap infrastructure and careless infrastructure are different things; the laptop earns its keep precisely on the workloads where “down for an afternoon” is an inconvenience rather than an incident.
But for staging? Staging is the perfect tenant. It needs to exist far more than it needs to be reliable, it’s idle most of the time, and every dollar not spent hosting it is margin. Most teams under-invest in staging because of exactly the cost and provisioning friction this pattern deletes.
If you’ve got a client stack you keep meaning to mirror, and a decent machine gathering dust: the whole toolchain — Coolify, Tunnel, Pages — is free. The problems you’ll hit are the four above. Now you know them in advance.