Understanding Design via GitHub
The code tells you what Firecracker does. It almost never tells you why it does it that way — why
the device model is virtio-MMIO and not PCI, why the API is configured over a Unix socket instead of
a config flag, why diff snapshots need track_dirty_pages, why a given validation rejects an
otherwise-reasonable input. Those decisions were made in design discussions, and at a GitHub-native,
single-vendor project like Firecracker, those discussions are almost entirely recoverable. The "why"
is not lost; it is in the repo's history, you just have to dig.
This chapter teaches archaeology: how to reconstruct the reasoning behind a piece of code from the artifacts the project leaves behind. You will do this constantly — before changing existing behavior, when a maintainer asks "did you consider X?", and when you need to know whether a "bug" is actually a deliberate design choice you are about to break.
Note: Firecracker has no JIRA, no separate wiki, no RFC repo. Design lives in five places: committed
docs/, merged PRs and their review threads, issues (including RFC-shaped ones), theCHANGELOG.md, and the NSDI 2020 paper. This chapter walks each, and how they fit together.
Where Design Actually Lives
| Source | What you get from it | How to reach it |
|---|---|---|
docs/ (in-repo) | The current designed behavior, written deliberately: the API, jailer, seccomp, snapshotting, prod-host-setup. The closest thing to a spec. | ls docs/, rg inside |
| Merged PRs + review threads | The decision as it was argued — alternatives weighed, objections raised, the maintainer's reasoning. The richest source. | git log → (#NNNN) → gh pr view |
| Issues (incl. RFC-shaped) | The problem statement, the proposal, community input before code. Larger features open a tracking/RFC issue first. | gh issue list, labels |
CHANGELOG.md | The chronology of user-visible change: what was added, changed, deprecated, removed, and (crucially) what broke compatibility, per release. | rg/read CHANGELOG.md |
SPECIFICATION.md, FAQ.md, SECURITY.md, CHARTER.md | The project's stated scope, threat model, and self-imposed constraints — why some things will never be added. | read at repo root |
| The NSDI '20 paper | The original "why Firecracker exists" — the serverless isolation/density problem, the rejected alternatives, the security argument. | external; cited below |
docs/ API-change runbook | The process for changing the API compatibly — the rules a maintainer holds you to. | find under docs/ |
The instinct to build: before you touch a behavior, find where it was decided. A "fix" that reverts a deliberate decision will be rejected, and the rejection will cost you credibility. Doing the archaeology first is how you avoid that.
Start With docs/ — the Designed Behavior
docs/ is committed alongside the code and reviewed with it, so it is the authoritative statement of
intended behavior. Survey it before anything else:
cd ~/fc-src
ls docs/ docs/snapshotting/
# Read the design intent for the area you're touching:
rg -n "compat|deprecat|backward|breaking" docs/ | head
The high-value documents and what they encode:
| Doc | Encodes the "why" of |
|---|---|
docs/api_requests/ + the swagger (src/firecracker/swagger/firecracker.yaml) | The API contract and how it's allowed to evolve |
docs/jailer.md | The isolation barrier — what it does and the order it must do it |
docs/seccomp.md | The per-thread syscall whitelist model and why filters are baked in at build time |
docs/snapshotting/ | Snapshot/restore design, version compatibility, the UFFD page-fault model |
docs/prod-host-setup.md | The host hardening the threat model assumes (SMT off, KSM off, egress drop…) |
SPECIFICATION.md, FAQ.md | The scope boundary — what Firecracker deliberately will not do |
When docs/ and code disagree, that is itself a finding — usually a docs bug or a regression, and
either is a legitimate, well-scoped PR.
The Core Technique: From a Line of Code to Its PR
This is the move you will make most. You are looking at a line, a check, a constant, or an error message and you want to know why it exists. Six commands get you to the discussion that produced it.
Step 1 — Blame the line to a commit
cd ~/fc-src
# Blame a specific region; -L limits to a line range so output is readable.
git blame -L '/track_dirty_pages/,+5' src/vmm/src/vmm_config/machine_config.rs
# Or follow a moved symbol across renames:
git log -S "track_dirty_pages" --oneline -- src/vmm/
Step 2 — Read the full commit message and find the PR number
git show <sha> --stat | head -40
# Firecracker commits reference the PR: look for "(#NNNN)".
Firecracker squash-merges, so a single commit usually corresponds to one PR, and its message
references (#NNNN).
Step 3 — Open the PR: description, the issue it closed, and the review thread
gh pr view <NNNN> --repo firecracker-microvm/firecracker
gh pr view <NNNN> --repo firecracker-microvm/firecracker --comments # the review discussion
The PR description states the intent. The linked issue (Closes #M) states the problem. The
review thread is the gold — it is where a maintainer asked "what about diff-snapshot
compatibility?" or "this adds a syscall, can we avoid it?" and where the author defended or revised
the design. That thread tells you the alternatives that were rejected and why — which is exactly
what you need before you propose changing the result.
Step 4 — Cross-reference the CHANGELOG
rg -n "track_dirty_pages|enable_diff_snapshots|snapshot" CHANGELOG.md
The CHANGELOG places the change on the release timeline and, for renames or removals, often records
the deprecation and the breaking-change note. The enable_diff_snapshots → track_dirty_pages
rename, for instance, is the kind of history you reconstruct exactly this way: the PR shows the
reasoning, the CHANGELOG shows the deprecation path.
Reading Issues, Including the RFC-Shaped Ones
Large features don't start as code; they start as an issue. Firecracker uses GitHub labels rather than a formal RFC process, but the function is the same — a problem statement and a proposal, debated before implementation. Find them:
# Open enhancement discussions and roadmap items:
gh issue list --repo firecracker-microvm/firecracker --label "Type: Enhancement" --state all --limit 30
gh issue list --repo firecracker-microvm/firecracker --label "Roadmap: ..." --state all --limit 30
# Read one fully, including the back-and-forth:
gh issue view <NNNN> --repo firecracker-microvm/firecracker --comments
Reading a feature's originating issue tells you the constraints the maintainers placed on it up front — "we'll take this only if it's behind a flag," "this must not change the default device model," "we need a snapshot-compat story." Those constraints are the design, made explicit before a line was written. If you are proposing something adjacent, the originating issue tells you the bar.
Tip: Labels are a map.
good first issue,Type: Bug/Enhancement,Status: Awaiting review,Priority: ...,Kani(formal verification),Roadmap: .... Filtering by them is how you find both work to do and the design conversations that matter. See the issue roadmap for how these labels map to difficulty stages.
The NSDI Paper: the Root "Why"
The deepest layer of design intent is the 2020 USENIX NSDI paper, "Firecracker: Lightweight Virtualization for Serverless Applications." It is the document that explains the decisions the code takes for granted: why a new VMM instead of stripping QEMU; why the minimal device model; why Rust; why the jailer plus seccomp plus the KVM boundary as defense in depth; the boot-time and memory-overhead targets that act as gates on every change. When a maintainer says a feature "contradicts the design goals," the paper is where those goals are stated. Read it once, in full, early — it reframes the whole codebase. (the introduction and the minimal-device-model engineering chapter both build directly on it.)
A Worked Archaeology: "Why does loading a snapshot need a memory backend type?"
Suppose you are reading the snapshot-load path and notice the request requires a
mem_backend { backend_path, backend_type: File | Uffd }, and you wonder why Uffd exists at all
and why a plain mem_file_path is deprecated. Reconstruct it:
cd ~/fc-src
# 1. Find the code that handles the backend type.
rg -n "backend_type|MemBackend|Uffd|mem_file_path" src/vmm/src/
# 2. Blame the deprecation of the old field.
git log -S "mem_file_path" --oneline -- src/vmm/ | tail
# 3. Open the introducing/deprecating PR and read the review thread.
gh pr view <NNNN> --repo firecracker-microvm/firecracker --comments
# 4. Confirm the chronology and any breaking note in the CHANGELOG.
rg -n "Uffd|userfaultfd|mem_backend|mem_file_path" CHANGELOG.md
# 5. Read the design doc that explains the lazy-paging model.
rg -n "uffd|userfaultfd|on-demand" docs/snapshotting/
By the end you can state the "why" precisely: UFFD (userfaultfd) lets a separate process serve guest
pages on demand at restore time, which is what makes fast restore-from-snapshot at scale possible;
the standalone mem_file_path was deprecated in favor of the structured mem_backend so the loader
can express both file-backed and UFFD-backed memory uniformly. That paragraph is the difference
between proposing a change that respects the design and proposing one a maintainer immediately
rejects. The mechanics live in the snapshotting deep dive; the
reasoning you just recovered from GitHub.
Common Archaeology Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
| Treating a deliberate choice as a bug | PR reverts a decision; rejected; credibility cost | Blame → PR → review thread before changing behavior |
| Reading only the PR description, not the thread | You miss the rejected alternatives and the real constraints | Always --comments; the objections are the design |
Ignoring docs/ | You re-propose something already designed or documented | Survey docs/ for the area first |
| Skipping the CHANGELOG | You miss that a field was deprecated/renamed, not removed | rg the symbol in CHANGELOG.md |
| Not reading SECURITY/SPECIFICATION/FAQ | You propose something out of scope by design | Read the scope-boundary docs once, early |
Validation: Prove You Understand This
- Pick any non-obvious constant, check, or error message in
src/vmm/src/.git blameit, find its PR(#NNNN), read the review thread, and state in two sentences why it exists. - Find one entry in
CHANGELOG.mdunder a "Deprecated" or "Removed" heading and trace it to the PR that did it; explain the migration path the maintainers offered. - List the five places Firecracker design lives, and which one you'd consult first for: a behavior change, a new feature, and a compatibility question.
- Open one
Type: Enhancementissue withgh issue view --commentsand name two constraints the maintainers placed on the proposal before any code. - Locate the API-change runbook under
docs/and summarize the one rule it enforces most strictly. - State one design decision you can attribute to the NSDI paper, and where the code takes it for granted.
You have absorbed this chapter when, faced with any line of Firecracker code, your reflex is to find the discussion that produced it before you change it. The next chapter — Community Interaction — is how you join those discussions without burning the trust you are trying to build.