Firecracker MMDS: Passing Config Into a MicroVM Safely
You've just booted a fresh microVM. It's identical to every other microVM you'll boot from the same image, because that's the whole point of an image — it's a frozen, reusable template. But this particular VM needs to be a little bit special: it belongs to tenant #4172, it should talk to a specific database, it holds a short-lived credential nobody else should ever see, and it needs to know its own sandbox ID. None of that can live in the image, because the image is shared. So how do you hand a just-born, otherwise-identical guest its unique identity — safely, without baking secrets into the disk and without shipping them over a real network where they can be sniffed or misrouted? Firecracker's answer is the Microvm Metadata Service, or MMDS: a tiny in-VMM key-value store that the guest reads over a link-local HTTP endpoint the host completely controls. This post explains what MMDS is, why it exists, the security model that makes it safe to hand secrets through, the v1-versus-v2 hardening that mirrors what AWS did to its own instance-metadata service, and the one caveat you must respect if the code inside is untrusted.
What MMDS is: a metadata store inside the VMM
MMDS is a small data store that lives inside the Firecracker process itself — not in the guest, not on a network, not on disk. You, the host, populate it with a JSON document through Firecracker's HTTP API before (or while) the VM runs. Firecracker then exposes that document to the guest at a well-known link-local address: 169.254.169.254. Inside the guest, reading your config is a plain HTTP GET to that address. There is no agent to install, no volume to mount, no port to open on a routable interface — the guest's userspace makes an ordinary HTTP request and gets back the data you put there.
If that address looks familiar, it should: 169.254.169.254 is the exact same magic IP that AWS EC2, GCP, and Azure use for their instance metadata services (IMDS). That is deliberate. Cloud instances have long fetched their identity — instance ID, region, IAM credentials, user-data — from a link-local metadata endpoint, and a huge amount of tooling (cloud-init, SDKs, agents) already knows to look there. By mimicking that pattern, Firecracker lets a guest reuse the same well-worn convention: the metadata service is the idiomatic place a Linux guest expects to find 'who am I and what should I do.' MMDS is Firecracker's in-VMM implementation of that pattern, scoped to a single microVM and controlled entirely by the host that launched it.
Why it exists: a clean channel for per-VM identity
Consider the alternatives for getting config into a guest, and why each is awkward. You could bake the config into the rootfs image — but the image is shared across every VM, so anything per-VM or secret can't go there without either building a fresh image per VM (slow, and death to snapshot reuse) or leaking one tenant's data into another's image. You could pass it on the kernel command line — but the cmdline is a single string visible to anyone who can read /proc/cmdline, it's size-limited, and it's frozen at boot. You could open a network connection from the guest to a config server — but now the guest needs networking, DNS, routing, and credentials to authenticate to that server, which is a chicken-and-egg problem when the credentials are the very thing you're trying to deliver.
MMDS sidesteps all of that. The host writes the per-VM data straight into the VMM through the same API socket it already uses to configure the machine, and the guest reads it back over a loopback-grade link-local endpoint that requires no real network, no DNS, and no outbound connectivity. The data never touches the guest's persistent disk, so it doesn't survive in the image or leak into a snapshot's rootfs. It's the cleanest available channel for the things that are genuinely per-VM: a sandbox ID, a bootstrap token, a tenant identifier, a short-lived credential, a pointer to the workload this particular VM should run. Config that is meant to be one-VM-only, delivered by the one party that's allowed to set it.
How the host writes the data (before boot)
The host populates MMDS through Firecracker's API, which is itself a small HTTP server on a Unix domain socket. First you activate the MMDS network stack (telling Firecracker which guest NIC the metadata endpoint should be reachable through), then you PUT the JSON document. Both happen before you resume the guest, so the data is waiting the instant the guest's userspace comes up. The snippet below shows the shape of it against the API socket:
# The host talks to Firecracker's API over its Unix domain socket.
API=/run/firecracker/<id>.sock
# 1. Attach the MMDS network stack to the guest's NIC and pick the version.
# Here we ask for the hardened v2 (session-token required).
curl --unix-socket "$API" -X PUT 'http://localhost/mmds/config' \
-H 'Content-Type: application/json' \
-d '{
"version": "V2",
"network_interfaces": ["eth0"],
"ipv4_address": "169.254.169.254"
}'
# 2. PUT the metadata document. This is the per-VM identity the guest will read.
curl --unix-socket "$API" -X PUT 'http://localhost/mmds' \
-H 'Content-Type: application/json' \
-d '{
"sandbox": {
"id": "sbx_9f21c4",
"tenant": "4172",
"bootstrap_token": "ey...short-lived...",
"workload": "code-interpreter"
}
}'
# Now resume the guest. The blob is in the VMM, waiting to be read.Two things are worth underlining. First, the writer is the host, over the API socket — a channel the guest has no access to. The guest cannot PUT to /mmds; there is no path from inside the VM to the API socket at all. Second, this composes with snapshotting: because MMDS is host-controlled configuration rather than guest state, a platform that restores the same baked snapshot many times can write a fresh, per-sandbox metadata document on each restore. The template stays generic; the identity is injected per instance.
How the guest fetches it: curl to a link-local IP
Inside the guest, reading metadata is just HTTP. With the legacy v1 protocol it's a bare GET — you can walk the JSON tree by path, so GET /sandbox/id returns just that leaf. This is exactly the ergonomics cloud users know from IMDSv1:
# MMDS v1 (guest side): a plain GET. Simple, and that simplicity is the problem.
curl http://169.254.169.254/sandbox/id
# -> sbx_9f21c4
curl http://169.254.169.254/sandbox/bootstrap_token
# -> ey...short-lived...
# Ask for the whole document as JSON with an Accept header:
curl -s -H 'Accept: application/json' http://169.254.169.254/MMDS v2 adds one step: before you can read anything, you must obtain a short-lived session token by making a PUT request, then present that token on every GET. This is the same token dance AWS introduced as IMDSv2. It looks like this from inside the guest:
# MMDS v2 (guest side): the session-token dance.
# 1. PUT for a token, with a TTL (seconds) in a header.
TOKEN=$(curl -s -X PUT 'http://169.254.169.254/latest/api/token' \
-H 'X-metadata-token-ttl-seconds: 60')
# 2. Every read must now carry the token, or it's rejected.
curl -s -H "X-metadata-token: $TOKEN" \
http://169.254.169.254/sandbox/bootstrap_token
# -> ey...short-lived...
# A GET with no token in v2 mode fails — that failure is the whole point.
curl -s http://169.254.169.254/sandbox/bootstrap_token
# -> 401 / rejectedWhy add the ceremony? Because a bare GET is trivially easy to trigger by accident — or by an attacker. If some service inside the guest can be tricked into fetching a URL an attacker supplies (a classic server-side request forgery, or SSRF, bug), the attacker just points it at http://169.254.169.254/ and reads your secrets. Requiring a PUT-first token defeats the simplest version of that attack: an SSRF primitive that only performs GETs (which is most of them, e.g. an image-fetcher or a webhook-poker) can't do the PUT to mint a token, and the token-carrying header is awkward to smuggle through a naive proxy. It's the same reasoning that pushed the entire cloud industry from IMDSv1 to IMDSv2 after a string of high-profile metadata-SSRF breaches.
The security model: read-only to the guest, host owns contents
The property that makes MMDS safe to route secrets through is the asymmetry between who can read and who can write. The guest can read the metadata store; the guest cannot write it. Writes happen exclusively over Firecracker's API socket, which lives on the host and is unreachable from inside the VM. So the contents of the store are entirely host-determined — a guest can never inject, tamper with, or corrupt its own metadata, nor can it forge metadata to fool another component that trusts the endpoint. Whatever the host PUT is what the guest sees, full stop.
That said, MMDS is a configuration channel, not an authentication layer, and it's important to be precise about what it does and doesn't guarantee. The v2 token gates casual and GET-only-SSRF reads, but any code running in the guest with the ability to make a PUT and then a GET can read the store — it's readable by the guest by design; that's its job. So the discipline is at the level of contents, not access control: put in MMDS only what it's acceptable for the guest's own code to see. For a trusted workload, that can include a scoped bootstrap credential the guest is meant to use. For untrusted code, keep the store minimal — an ID, a pointer, maybe a token so tightly scoped and short-lived that reading it buys an attacker nothing. The host controls the blast radius by controlling exactly what goes in.
MMDS vs vsock vs cmdline vs baked image
MMDS is one of several ways to get data across the host-guest boundary, and it's worth placing it against the others, because they solve subtly different problems:
- MMDS — host writes a JSON blob into the VMM; guest reads it over HTTP at 169.254.169.254. Best for per-VM identity and config the guest pulls at boot. Read-only to the guest, no persistent-disk footprint, and idiomatic (cloud-init and cloud SDKs already look there). Caveat: it lives at the SSRF-magnet IP, so use v2 and mind the contents.
- vsock — a bidirectional AF_VSOCK control channel between host and guest, needing no guest network. Best when the host must drive the guest continuously (exec, file transfer, readiness pings) rather than hand it a static blob once. It's a live pipe, not a metadata store; see /blog/vsock-explained for the mechanism.
- Kernel cmdline — a single string handed to the guest kernel at boot. Best for a handful of tiny, non-secret boot parameters. Frozen at boot, size-limited, and visible to anything that can read /proc/cmdline, so it's a poor place for secrets or anything that changes per restore.
- Baked into the rootfs image — data lives in the disk image itself. Best for things that are genuinely common to every VM from that template (the base OS, the guest agent, default tooling). Because the image is shared and reused via snapshots, per-VM or secret data must not go here — that's precisely the gap MMDS fills.
The clean division of labour: bake what's common to every VM into the image, hand what's per-VM-and-static through MMDS at boot, and use vsock for the ongoing host-driven control plane. Cmdline is for a couple of non-secret knobs the kernel needs before userspace exists. Reaching for the wrong one — secrets in the image, per-tenant config on the cmdline — is where platforms get into trouble.
The SSRF caveat for untrusted-code platforms
There's a caveat that matters enormously if your microVMs run untrusted or AI-generated code, and it follows directly from MMDS choosing the cloud-standard IP. Because 169.254.169.254 is where every cloud puts instance metadata, it is the single most-probed target for SSRF attacks on the entire internet — every off-the-shelf exploit kit, every automated scanner, every prompt-injected agent looking to escalate already knows to hit it first. When you expose MMDS at that address inside a guest running code you don't trust, you are placing a metadata endpoint exactly where hostile code will look for one out of pure reflex.
The mitigations are straightforward but you have to actually apply them. Prefer MMDS v2 so a bare GET — the shape of most SSRF and most careless-fetch bugs — is rejected without a minted token. Treat the contents as guest-readable by assumption and put nothing there that the guest's own code shouldn't see; if you need to deliver a credential, make it as narrowly scoped and short-lived as the task allows. And recognize that MMDS is one channel among several: PandaStack, for example, keeps its host-to-guest control plane on vsock (exec, filesystem, readiness — see /blog/vsock-explained), where the channel is point-to-point to the host and structurally can't be reached by a sibling sandbox or wandered into by a stray GET. Metadata endpoints are a convenience; for untrusted code, convenience is exactly the thing an attacker inherits too, so you spend it deliberately.
The whole channel, end to end
Put it together: the host PUTs a JSON document into Firecracker over its API socket before resuming the guest; Firecracker holds that blob inside the VMM and exposes it at the link-local 169.254.169.254; the guest reads its own identity back with a plain HTTP GET (v1) or a token-gated GET (v2). The guest can read but never write, so the host owns the contents absolutely, and nothing lands on the guest's disk to leak into an image or a snapshot. It's the idiomatic, disk-free way to make an otherwise-identical VM unique at boot. Use v2 when the code is untrusted, keep the contents to the minimum the guest legitimately needs, and remember that the endpoint's convenience is exactly why it's the most attacked IP nobody memorizes. Firecracker's docs cover the MMDS API in full; the neighbouring host-to-guest channel, vsock, is the one to reach for when you need a live control plane rather than a static blob.
Frequently asked questions
What is the Firecracker MMDS?
MMDS (Microvm Metadata Service) is a small key-value store that lives inside the Firecracker process. The host writes a JSON document into it over Firecracker's API socket, and the guest reads that document back as HTTP requests against the link-local address 169.254.169.254 — the same magic IP the major clouds use for instance metadata. It's the idiomatic way to hand a fresh microVM its per-VM identity (an ID, a token, a config pointer) without baking anything into the shared rootfs image or requiring the guest to have real networking. The data lives in the VMM, never on the guest's disk, and the guest can read it but cannot write it.
What's the difference between MMDS v1 and v2?
MMDS v1 lets the guest read metadata with a bare HTTP GET against 169.254.169.254 — simple, but easy to trigger by accident or via an SSRF bug. MMDS v2 adds a session-token requirement: the guest must first make a PUT request to obtain a short-lived token, then present that token in a header on every read. This is the same hardening AWS introduced as IMDSv2. It defeats the most common SSRF primitives, which can only perform GETs and therefore can't mint the required token, and makes the token-carrying header awkward to smuggle through a naive proxy. For microVMs running untrusted code, prefer v2.
Why does Firecracker MMDS use 169.254.169.254?
169.254.169.254 is the link-local address that AWS EC2, GCP, and Azure all use for their instance metadata services. Firecracker reuses it deliberately so that existing guest tooling — cloud-init, cloud SDKs, provisioning agents — already knows to look there for 'who am I and what should I do.' The tradeoff is that this same IP is the single most-probed target for SSRF attacks on the internet, since every exploit kit knows the convention. That's fine for trusted workloads, but for untrusted code it means you should prefer MMDS v2 and be deliberate about what you place in the store.
How does the host write data into MMDS?
The host talks to Firecracker's API, a small HTTP server on a Unix domain socket. It first PUTs to /mmds/config to attach the metadata stack to a guest NIC and choose the version (V1 or V2), then PUTs the actual JSON document to /mmds — both before resuming the guest, so the data is waiting when guest userspace comes up. Crucially, that API socket lives on the host and is unreachable from inside the VM, so the guest can never write or tamper with its own metadata. Because MMDS is host-controlled config rather than guest state, a platform that restores the same snapshot many times can inject a fresh per-sandbox document on each restore.
When should I use MMDS instead of vsock or the kernel cmdline?
Use MMDS for per-VM identity and config that's static and pulled by the guest at boot — an ID, a token, a config pointer — delivered without touching the disk image. Use vsock when the host needs a live, bidirectional control channel (exec, file transfer, readiness pings) rather than a one-time blob; vsock is a pipe, MMDS is a store. Use the kernel cmdline only for a couple of tiny, non-secret boot parameters the kernel needs before userspace exists, since it's frozen at boot and readable via /proc/cmdline. And bake into the rootfs image only what's common to every VM from that template — never per-VM or secret data, since the image is shared and reused via snapshots.
49ms p50 cold start. Fork, snapshot, and scale to zero.