DEV Community

Cover image for How I built an AWS Lambda clone with Firecracker microVMs
Vivek jadhav
Vivek jadhav

Posted on

How I built an AWS Lambda clone with Firecracker microVMs

Ever wondered what actually happens when you invoke a Lambda function? Not the API layer but the execution layer. What runs your code, how it's isolated, and how AWS gets cold starts low enough to be usable?

I wanted to understand that deeply. So I built it.

This is a breakdown of how I built a Firecracker-based serverless runtime from scratch, the architectural decisions I made, and what the numbers look like.

The problem: cold starts

Every serverless platform faces the same fundamental tension. You want functions to start instantly, but strong isolation requires spinning up a fresh environment per invocation.

A standard Linux VM boot takes ~200ms at minimum. At scale, that's unusable.

AWS's solution and the core idea behind this project is VM snapshots.

How snapshot-based cold start works

Instead of booting a VM on every invocation:

  1. Boot the VM once
  2. Load the Node.js runtime (my project not AWS) and function handler
  3. Snapshot the initialized memory state to disk
  4. On every subsequent invocation, restore from that snapshot rather than booting fresh

Restoring from a snapshot takes 1–5ms. A full cold boot takes 200ms. That's a 40–200x improvement.

This is exactly what AWS does with Lambda's Firecracker-based execution model.

Architecture overview

The system has two main components:

Control Plane — handles everything outside the VM:

  • Function deployment (accepts a zip, builds a minimal rootfs)
  • VM lifecycle (create, snapshot, restore, destroy)
  • Per-function request queues with concurrency control
  • Multi-tenant scheduling

MicroVM Runtime — runs inside each Firecracker VM:

  • A minimal Linux kernel + custom rootfs
  • Node.js runtime executing user handlers
  • Deterministic execution: one request → one execution → response

Architecture Diagram

IPC: how the host talks to the VM

This is where a lot of serverless runtimes lose performance. Every round-trip between host and VM has overhead. If you open a new connection per request, that overhead compounds.

I used two mechanisms:

  • vsock (virtio sockets) for host ↔ VM communication. vsock is designed specifically for VM-to-host traffic and avoids the overhead of a full network stack.
  • Unix domain sockets for intra-VM routing. Faster than TCP for local communication, no kernel networking stack involved.

Eliminating per-request connection setup was the key unlock for throughput.

Execution flow

1. User deploys function.zip via POST /deploy
2. Control plane builds a minimal rootfs with user code inside
3. Firecracker VM boots, runtime initializes
4. Memory snapshot is created and stored
5. On invocation:
   a. Pull a warm VM from the pool (if available)
   b. If no warm VM → restore from snapshot
   c. Send request via vsock
   d. Runtime executes handler
   e. Response returned to client
Enter fullscreen mode Exit fullscreen mode

Benchmark results

Benchmarked with autocannon — 10 concurrent connections, 30 seconds:

Metric Result
Throughput 5,400 req/sec
p50 latency 1ms
p99 latency 4ms
Total requests 164,000

Key optimizations that got here: snapshot reuse, persistent runtime across invocations, reduced IPC overhead from connection pooling.

The tradeoffs

Nothing is free.

Runtime reuse introduces shared state. When you restore from a snapshot and reuse the same runtime across invocations, module-level state in the user's code persists between calls. Strong VM-level isolation, but the runtime isn't fully stateless.

This is the same tradeoff AWS makes. Lambda execution environments are reused between invocations, they just don't guarantee it, and they don't tell you when a new one is created.

Throughput vs. isolation purity. You can enforce one invocation-per-VM destroy and recreate for perfect isolation, but your throughput tanks. The snapshot model is the practical middle ground.

What I learned

Building this taught me more about OS-level virtualization than any course or book. Specifically:

  • How Firecracker's works and why it matters for security
  • Why vsock exists and what problem it solves over TCP
  • How rootfs construction works at a practical level
  • Why the IPC layer is the performance bottleneck in VM-based execution, not the VM itself
  • How to think about isolation vs. throughput tradeoffs in real systems

Try it yourself

The full source, architecture diagrams, and setup instructions are on GitHub:

👉 github.com/vivek1504/serverless-runtime

A prebuilt kernel image and rootfs are available in the releases so you don't have to build from scratch. You'll need a Linux host with KVM support (/dev/kvm accessible) and the Firecracker binary in your PATH.

If you've built something similar or have questions about any part of the implementation, I'm happy to go deeper in the comments.

Top comments (0)