Back to Blog

Why We Built Our Own VMM (And What 0.551ms Taught Us)

The team at Armored Gate didn't set out to build a virtual machine monitor. We set out to solve a problem. Then that problem led to another. And another. This is the story of how every solution became the foundation for the next question.

The False Choice

For years, the infrastructure world has presented teams with a binary decision: containers (fast, lightweight, weak isolation) or virtual machines (strong isolation, slow, resource-heavy). Pick one. Accept the tradeoff.

The team at Armored Gate refused.

We didn't start with some grand vision of building a unified compute platform. We started with a simple conviction: production workloads deserve both speed and security, and the industry's willingness to sacrifice one for the other was a failure of engineering, not a law of physics.

From the beginning, we committed to the Twelve-Factor methodology — not as a checklist, but as a discipline. Explicit dependencies. Strict separation of build and run. Stateless, disposable processes. Config in the environment. Logs as event streams. These weren't aspirational goals — they were constraints we refused to violate, even when violating them would have been easier.

What followed was a journey that took us from container runtimes to content-addressed storage to building a custom virtual machine monitor from scratch — each step guided by those principles, each step a direct consequence of the last. Here's how it happened.


Act I: Voltainer — Containers Without the Baggage

Our first bet was containers, but done right. The existing container ecosystem had accumulated years of architectural debt — fat daemon processes, sprawling attack surfaces, and runtimes that prioritized developer convenience over production security. Worse, they violated the Twelve-Factor principles we cared about most.

Factor II (Dependencies) says to explicitly declare and isolate dependencies — yet the dominant container runtime requires a privileged daemon process that isn't declared anywhere in your application manifest. Factor IX (Disposability) demands fast startup and graceful shutdown — yet cold starts measured in seconds make true disposability impractical. Factor VI (Processes) demands stateless, share-nothing execution — yet fat runtimes accumulate state in daemon caches, image stores, and build layers that leak between workloads.

We built Voltainer: a container runtime based on systemd-nspawn with Landlock LSM enforcement, zero background daemons, and cold starts measured in milliseconds, not seconds.

50ms cold starts. No daemon. Landlock-enforced isolation from the first syscall.

Voltainer was fast — really fast. It was secure by default, not by configuration. And it honored the Twelve-Factor contract: a single binary with explicit dependencies (Factor II), processes that start in milliseconds and stop cleanly on SIGTERM (Factor IX), all logging flowing through the systemd journal as event streams (Factor XI). No hidden state. No daemon lifecycle to manage. No implicit dependencies leaking in from the host.

But containers are only as good as what you put in them. And that led to the next problem.


Act II: Stellarium — Store Once, Reference Everywhere

When you run thousands of containers across a fleet, you notice something wasteful: the same base layers, the same binaries, the same shared libraries — duplicated across every image, every node, every pull. Traditional registries treat each image as an atomic blob. Efficient for simplicity. Terrible for scale.

Factor V (Build, release, run) demands strict separation between build artifacts and runtime execution. Factor IV (Backing services) says to treat storage as an attached resource, not something embedded in your application. We needed a storage layer that honored both: immutable build artifacts stored once, referenced everywhere, cleanly separated from the runtime that consumes them.

We built Stellarium: a content-addressed storage system where every image, every layer, every blob gets SHA-256 deduplicated. Store a 200 MB base image once. Reference it from a thousand containers. The second pull is a metadata lookup, not a network transfer.

How Stellarium Works

  • Content-addressed: Every object is identified by its cryptographic hash, not a mutable tag
  • Deduplicated at the block level: If two images share 95% of their layers, Stellarium stores the shared content exactly once
  • Zero-copy restore: Objects can be mapped directly into memory without intermediate copies or filesystem extraction
  • Verifiable by default: Every hash is a verification — if the content doesn't match the hash, it doesn't exist

Stellarium wasn't just a storage optimization. It was the foundation for everything that came after. When we later needed to restore full VM snapshots in sub-millisecond time, it was Stellarium's in-memory CAS architecture that made it possible.

But first, we needed to confront the limits of containers themselves.


Act III: The Walls Containers Can't Cross

Voltainer was excellent. For Linux workloads with compatible kernels, it was the best runtime we'd ever used. But the world doesn't run on Linux containers alone.

Customers came to us with workloads that containers fundamentally cannot serve:

  • Windows guests. Enterprise applications, .NET services, SQL Server instances — containers can't run Windows.
  • Legacy SCADA systems. Industrial control software from 2008 that needs a full OS, specific kernel versions, and hardware emulation.
  • Cross-architecture testing. ARM binaries on x86 hosts. RISC-V firmware validation. MIPS embedded systems. Containers share the host kernel — they can't emulate a different one.
  • Compliance-mandated VM isolation. Regulated industries (finance, healthcare, government) where auditors require hardware-level isolation boundaries, not namespace isolation.

We could have told these customers "use something else for VMs." We could have pointed them to the existing hypervisors on the market. But we'd used those hypervisors. We knew their problems intimately.

Factor X (Dev/prod parity) demands that the gap between development and production stay small. But when production requires workloads that containers fundamentally cannot serve — Windows, cross-architecture, compliance-mandated isolation — the gap isn't small. It's a different planet. "Use something else for VMs" means two toolchains, two operational models, two sets of expertise. Parity becomes impossible by definition.

Traditional VMs were slow to boot, expensive in memory, and operationally complex. Micro-VMs like Firecracker were better, but still carried overhead we thought was unnecessary. More importantly, none of them integrated with the container-native workflow we'd built. It was two worlds, two toolchains, two operational models.

So we built our own.


Act IV: VoltVisor — 24,000 Lines of Rust

VoltVisor is a custom KVM-based micro virtual machine monitor written from scratch. Not forked. Not wrapped. Written. ~24,000 lines of Rust compiling to a 3.9 MB binary.

The design constraints were deliberate — and they came directly from the same Twelve-Factor principles that shaped Voltainer:

  • No runtime dependencies beyond the Linux kernel's KVM module — Factor II (Dependencies): declare everything explicitly, rely on nothing implicitly
  • No background daemons — Factor VI (Processes): execute as stateless processes, not as a service that accumulates state
  • Memory efficiency as a first-class architectural concern — Factor VIII (Concurrency): scale out via the process model, which means each process must be lightweight enough to multiply
  • Snapshot restore as the primary boot path — Factor IX (Disposability): maximize robustness with fast startup, taken to its logical extreme

The Innovations That Made It Fast

Raw performance wasn't the goal. The goal was making VMs operationally equivalent to containers — instant enough to schedule on demand, cheap enough to run thousands, secure enough to replace the isolation boundary. The performance was a consequence of the architecture.

1. Demand-paged memory via mmap MAP_PRIVATE

Traditional VMMs allocate all guest memory upfront. A VM configured for 512 MB consumes 512 MB the moment it boots, whether the guest uses 12 MB or 500 MB. VoltVisor maps guest memory with MAP_PRIVATE, allowing the host kernel to demand-page memory as the guest touches it. Pages that the guest never accesses never consume physical RAM.

The result: a VM configured for 512 MB might use 24 MB of actual host memory for a typical workload.

2. Pre-warmed VM pool

The KVM_CREATE_VM ioctl takes approximately 24ms — not slow by most standards, but when your target is sub-millisecond restore, 24ms is an eternity. VoltVisor maintains a pool of pre-created KVM VMs, already initialized with vCPUs and basic configuration. When a restore request arrives, we don't create a VM. We shape an existing one — loading the snapshot state into a pre-warmed shell.

3. In-memory CAS restore via Stellarium

This is where Stellarium's architecture pays its biggest dividend. VM snapshots — memory pages, register state, device state — are stored as content-addressed objects in Stellarium. Restore doesn't read from disk, extract an archive, or copy files. It maps CAS objects directly into the VM's memory space. Zero copy. No filesystem involvement. The restore path is essentially: look up hash → map pages → set registers → resume vCPUs.

The Numbers

MetricFirecrackerVoltVisorImprovement
Snapshot restore (in-memory)~102ms0.551ms185× faster
Snapshot restore (disk)~102ms1.04ms98× faster
Memory per VM36 MB24 MB33% less
Binary size~5.2 MB3.9 MB25% smaller

0.551ms. That's not a boot time — it's a snapshot restore. A full VM, with memory state, register state, and device state, materialized and running in roughly the time it takes light to travel 165 kilometers.

We didn't set out to beat Firecracker by 185×. We set out to make VMs feel instant. The 0.551ms was the result of removing every unnecessary step from the restore path until what remained was mathematically close to the minimum: map memory, set registers, go.


Act V: When You Need More Than a Micro-VM

VoltVisor handles the fast path — lightweight Linux guests, rapid scaling, snapshot-based deployment. But the real world has workloads that need more than a minimal VMM.

Windows guests need UEFI, TPM emulation, and virtio drivers. Legacy SCADA systems need specific x86 hardware emulation. Cross-architecture testing needs full CPU emulation — ARM on x86, RISC-V on ARM, MIPS on anything.

Rather than bloating VoltVisor with every possible feature, we integrated QEMU as a backend for workloads that need its rich device model. The volt CLI presents a unified interface; the backend is selected based on the workload:

BackendUse CaseWhat It Provides
kvm-linuxHeadless Linux guestsVoltVisor native — fastest path, minimal overhead
kvm-uefiWindows, TPM, UEFIQEMU with OVMF firmware, TPM 2.0, virtio-gpu
emulate-x86Legacy x86 softwareFull x86 emulation for ISA-dependent workloads
emulate-foreignARM, RISC-V, MIPS, PPCFull system emulation for cross-architecture testing

One CLI. One workflow. The complexity of backend selection is hidden from the operator. You describe what you need; Volt figures out how to run it. This is Factor X (Dev/prod parity) applied to the infrastructure layer itself — the same tool, the same commands, the same operational model whether you're running a lightweight Linux container or a full Windows VM with TPM emulation.


Act VI: Scale to Zero — Making VMs Cost-Effective

Fast VMs solve the performance problem. Cheap VMs solve the memory problem. But neither solves the cost problem for workloads that sit idle 90% of the time.

Factor IX (Disposability) says processes should be started or stopped at a moment's notice. Factor VII (Port binding) says services export functionality by binding to a port. What if we took both seriously? What if VMs could stop completely when no one was connecting to their port, and restart so fast that the next connection never noticed?

A web application that gets 10 requests per hour doesn't need a VM running 24/7. But traditional VM infrastructure has no concept of "sleeping" — either the VM is running and consuming resources, or it's shut down and unavailable.

We built Volt Edge: a scale-to-zero wake proxy that sits in front of VM workloads and handles the lifecycle transparently.

How Volt Edge Works

  1. Idle detection: When a VM has received no traffic for a configurable period, Volt Edge snapshots it via Stellarium and suspends it. Memory consumption drops to zero.
  2. Wake-on-request: When a request arrives for a suspended VM, Volt Edge holds the connection, restores the VM from its Stellarium snapshot (0.551ms with in-memory CAS), and forwards the request once the VM is ready.
  3. Transparent to clients: The requesting client sees a slightly delayed first response (typically under 10ms total including TCP setup), then normal performance. No cold-start penalties. No connection errors.

This is where VoltVisor's sub-millisecond restore time transforms from a benchmark number into a product feature. Scale-to-zero only works if wake-up is fast enough to be invisible. At 0.551ms restore time, it is.


Act VII: Security as Architecture, Not Configuration

Factor III (Config) says to store in the environment what changes between deploys. Security isn't one of those things. The seccomp filter, the Landlock policy, the capability set — these aren't knobs to turn per-environment. They're load-bearing walls. Making them configurable would be like making the foundation of a building optional per tenant.

Every layer of the Volt platform enforces security by default. Not "enable this flag." Not "configure this policy." Default. Mandatory. Always on.

5-Layer Defense in Depth

LayerMechanismWhat It Enforces
1. Syscall filteringseccomp-bpfAllowlist of 78 syscalls — everything else is denied at the kernel level
2. Filesystem accessLandlock LSMGranular path-based access control — VMs and containers can only touch what they're explicitly granted
3. Capability droppingLinux capabilitiesAll capabilities dropped except the minimum required set. No CAP_SYS_ADMIN. No CAP_NET_RAW. No escalation paths.
4. Namespace isolationLinux namespacesPID, network, mount, user, UTS, IPC — full namespace isolation for every workload
5. Memory boundsKVM + mmap enforcementGuest memory is strictly bounded. No access to host memory. No shared mappings without explicit grant.

These aren't optional security profiles. They're structural. Removing them would require modifying the source code. The Voltainer runtime, the VoltVisor hypervisor, and the Volt Edge proxy all enforce all five layers independently.

Cryptographic Verification (Patent Pending)

Beyond runtime isolation, every artifact in the Volt ecosystem participates in a cryptographic verification and attestation chain. Images, snapshots, configurations, and deployment manifests are signed and verified at every transition point — from build, to storage in Stellarium, to deployment, to runtime attestation.

The attestation chain is patent pending. We'll publish the technical specification when the filing is complete.


Act VIII: One CLI to Run Everything

The final piece was unification. We'd built a container runtime (Voltainer), a storage system (Stellarium), a VMM (VoltVisor), a wake proxy (Volt Edge), and a five-layer security stack. They were great individually. But operators don't want to learn five tools.

The volt CLI is the single entry point for the entire platform:

# Containers
volt run myapp:latest
volt build -f manifest.volt .

# Virtual machines
volt vm create --backend kvm-linux --memory 512M --cpus 2
volt vm snapshot my-vm --store stellarium
volt vm restore my-vm --from snapshot-abc123

# Storage
volt store push myimage:v1.2.0
volt store pull myimage:v1.2.0
volt store gc --deduplicate

# Networking & Edge
volt edge enable my-vm --idle-timeout 300s
volt edge status

# GitOps
volt deploy --from git@github.com:org/infra.git
volt rollback --to v1.1.0

Containers, VMs, storage, registry, networking, scale-to-zero, GitOps — one tool, one authentication model, one configuration format. The operational surface area of managing a mixed workload fleet drops from five dashboards and three CLIs to a single binary.

This is where all twelve factors converge. One codebase (Factor I). Explicit dependencies in a single binary (Factor II). Config stored in the environment, not baked into images (Factor III). Stellarium as an attached backing service (Factor IV). Strict build/release/run separation (Factor V). Stateless processes (Factor VI). Port-bound services (Factor VII). Horizontal scaling via the process model (Factor VIII). Disposable, sub-millisecond lifecycle (Factor IX). Dev/prod parity across container and VM workloads (Factor X). Logs as event streams through the systemd journal (Factor XI). Admin tasks as one-off volt commands (Factor XII).

We didn't build Volt and then check it against the Twelve-Factor principles. We built Volt because of them. Every architectural decision that felt hard — no daemon, no implicit state, no mutable tags, no embedded storage — was hard because the industry had been doing it the easy way for so long. The principles kept us honest.


The Thread That Connects Everything

Looking back, the path from Voltainer to VoltVisor to Volt Edge wasn't planned. It was discovered. Each solution exposed the next problem:

  • Containers needed storage → We built Stellarium
  • Containers couldn't handle every workload → We built VoltVisor
  • VMs needed to be cost-effective → We built scale-to-zero with Volt Edge
  • Security needed to be default, not optional → We built defense-in-depth into every layer
  • Operators needed simplicity → We unified everything under one CLI

Every component exists because the previous one revealed a gap. Stellarium wasn't a storage project — it was the answer to "how do we deduplicate container layers?" VoltVisor wasn't a hypervisor project — it was the answer to "how do we serve workloads that containers can't?" Volt Edge wasn't a proxy project — it was the answer to "how do we make VMs affordable for bursty workloads?"

The best infrastructure is built by following problems to their roots, guided by principles you refuse to compromise on.


What 0.551ms Taught Us

That number — 0.551ms for a full VM snapshot restore — taught us something we didn't expect. It taught us that the boundary between containers and VMs is artificial.

When a VM can restore faster than most containers can start, when it uses 24 MB of memory instead of 36 MB, when it provides hardware-level isolation instead of namespace-level isolation — the traditional reasons to choose containers over VMs start to dissolve.

You don't choose containers because they're fast. You choose them when namespace isolation is sufficient.

You don't choose VMs because you need heavy isolation. You choose them when you need a different kernel, a different architecture, or a compliance boundary.

And with Volt, you don't have to choose at all. Same CLI. Same storage. Same security model. Same operational workflow. The only difference is the isolation boundary — and the platform picks the right one based on what you're running.

That's what 0.551ms taught us. Not that we built a fast VMM. But that we eliminated the last reason to treat containers and VMs as different things.


What's Next

We're publishing detailed benchmarks, architectural documentation, and the Stellarium specification in the coming months. The cryptographic attestation chain patent filing is in progress. And the volt CLI is available now for teams ready to stop choosing between speed and security.

Every problem leads to the next solution. The Twelve-Factor principles tell us which direction to follow. We're still on the path.