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), the CHANGELOG.md, and the NSDI 2020 paper. This chapter walks each, and how they fit together.


Where Design Actually Lives

SourceWhat you get from itHow 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 threadsThe 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.mdThe 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.mdThe project's stated scope, threat model, and self-imposed constraints — why some things will never be added.read at repo root
The NSDI '20 paperThe original "why Firecracker exists" — the serverless isolation/density problem, the rejected alternatives, the security argument.external; cited below
docs/ API-change runbookThe 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:

DocEncodes 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.mdThe isolation barrier — what it does and the order it must do it
docs/seccomp.mdThe 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.mdThe host hardening the threat model assumes (SMT off, KSM off, egress drop…)
SPECIFICATION.md, FAQ.mdThe 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

MistakeConsequenceFix
Treating a deliberate choice as a bugPR reverts a decision; rejected; credibility costBlame → PR → review thread before changing behavior
Reading only the PR description, not the threadYou miss the rejected alternatives and the real constraintsAlways --comments; the objections are the design
Ignoring docs/You re-propose something already designed or documentedSurvey docs/ for the area first
Skipping the CHANGELOGYou miss that a field was deprecated/renamed, not removedrg the symbol in CHANGELOG.md
Not reading SECURITY/SPECIFICATION/FAQYou propose something out of scope by designRead the scope-boundary docs once, early

Validation: Prove You Understand This

  1. Pick any non-obvious constant, check, or error message in src/vmm/src/. git blame it, find its PR (#NNNN), read the review thread, and state in two sentences why it exists.
  2. Find one entry in CHANGELOG.md under a "Deprecated" or "Removed" heading and trace it to the PR that did it; explain the migration path the maintainers offered.
  3. 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.
  4. Open one Type: Enhancement issue with gh issue view --comments and name two constraints the maintainers placed on the proposal before any code.
  5. Locate the API-change runbook under docs/ and summarize the one rule it enforces most strictly.
  6. 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.