A Reproducible Pentest Workstation: NixOS Instead of a Kali VM You’re Afraid to Rebuild
Most offensive practitioners have the same artifact somewhere: a Kali VM that has been running for a year, accreting installed tools, manual tweaks, half-finished configs, and undocumented changes, until it has become a precious, fragile thing nobody dares rebuild because nobody remembers everything that went into it. That VM is a liability dressed as a convenience. A declarative workstation — built on NixOS — flips the relationship: the environment is code, the running machine is disposable, and rebuilding produces an identical setup every time.
This is the case for building your offensive tooling environment declaratively, and what it changes about how you work.
The precious-VM problem
The accreting-VM failure mode is worth naming precisely, because its costs are easy to ignore until they bite:
- It is undocumented. A year of manual changes means the environment’s actual state lives nowhere but the VM itself. You cannot reproduce it, and you cannot fully reason about it.
- It is fragile. Because rebuilding would lose undocumented state, you avoid rebuilding — which means you avoid clean updates, you carry forward cruft, and the environment degrades over time.
- It is a single point of failure. Lose the VM (corruption, hardware, mistake) and you lose an environment you cannot reconstruct. The “backup” is a disk image of an unknown state, not a description you can rebuild from.
- It is contaminated. Offensive work touches hostile material. A long-lived VM that has handled samples, payloads, and target data accumulates contamination and state you would rather not carry between engagements — especially between clients, where cross-contamination is a real concern.
The precious VM is convenient right up until any of those costs lands, at which point it is expensive in exactly the way you cannot afford mid-engagement.
The declarative flip
A NixOS-based workstation inverts every one of those properties:
The environment is the config. Your tools, your configurations, your tweaks — all described declaratively in version-controlled code. The running machine is built from that description. There is no undocumented state, because the description is authoritative and complete. What you have is what you wrote down.
Rebuilding is routine, not feared. Because the environment is fully described, rebuilding produces an identical setup. Rebuilding stops being a dreaded loss of state and becomes a routine operation — update the config, rebuild, get a clean identical environment. The fear evaporates because there is nothing undocumented to lose.
The machine is disposable. Since the environment is code, the specific running instance is disposable. Corrupted, contaminated, or just stale — discard it and rebuild from the config. The value lives in the description, not the instance, so losing an instance costs nothing.
Per-engagement freshness becomes practical. Because rebuilding is cheap and reproducible, a fresh environment per engagement — the contamination-control discipline that is good practice but usually too painful to actually do — becomes practical. Each client, each engagement, gets a clean, identical, known environment built from the same description, with no carried-over state from the last target. The hygiene you know you should practice becomes the path of least resistance.
What this changes about how you work
The declarative workstation is not just tidier; it changes the working posture:
Tooling changes are reviewable and trackable. Adding a tool or changing a config is a change to version-controlled code — visible, attributable, revertable. Your environment’s evolution has a history. When something breaks after a change, you can see exactly what changed and roll it back, rather than trying to remember what you did to the precious VM last week.
Environments are shareable and consistent. A team can share the description, and everyone gets the identical environment. No more “works on my setup” — the setup is code, and the code is the same for everyone. For a consulting team, this means consistent tooling across practitioners without per-person manual setup drift.
Experimentation is safe. Want to try a tool, a config, a risky change? Do it on a disposable instance built from a branch of the config. If it breaks things, discard and rebuild from the known-good description. Experimentation no longer risks the precious environment because there is no precious environment — only the description and disposable instances of it.
Recovery is trivial. Lose a machine and rebuild from the config. The disaster-recovery story for your working environment is “rebuild from version control,” which is the story you want and rarely have with an accreted VM.
The honest friction
Declarative tooling is not free, and the offensive-tooling context has specific frictions:
Not every tool is packaged cleanly. Offensive tools are a sprawling, fast-moving ecosystem, and not all of them fit a declarative packaging model neatly. Some require effort to express declaratively — wrapping, packaging, or accommodating tools that assume imperative installation. The convenience of Kali’s “everything pre-installed” is a real thing you are trading away for reproducibility, and bridging that gap is work.
The learning curve lands during a time you may not have it. Adopting the declarative model has a ramp-up cost, and if you are mid-engagement-heavy, carving out time to build the declarative environment competes with billable work. The payoff is real but deferred.
Some workflows genuinely want mutable state. Not everything fits the disposable-instance model. Engagement data, collected loot, work-in-progress — these need persistent, deliberately-managed storage separate from the disposable tooling environment. The declarative model pushes you to separate the environment (disposable, declarative) from the data (persistent, managed), which is good discipline but requires actually setting up that separation rather than letting everything live in one precious VM.
The takeaway
The long-lived offensive VM that accretes state until you are afraid to rebuild it is a liability — undocumented, fragile, a single point of failure, and contaminated across engagements. A declarative NixOS workstation flips every one of those: the environment is version-controlled code, the running instance is disposable, rebuilding is routine and identical, and per-engagement freshness — the contamination control you should practice but usually skip — becomes the easy path.
The reframe to carry: stop maintaining a precious environment; maintain a description of one, and treat instances as disposable. The value moves from the fragile machine you are afraid to lose to the version-controlled config you can rebuild from anywhere, any time, identically. You trade Kali’s pre-installed convenience and a learning curve for reproducibility, reviewable tooling history, safe experimentation, and trivial recovery — and for offensive work, where contamination control and clean baselines actually matter, that trade is usually the right one.
An independent piece by johlem.net — IT security, Luxembourg. Declarative offensive-tooling environments and OSCP-grade workstation setup.