The Release Process and Policy
A merged, two-approval PR on main is not yet released. Between your merge and a user running the
change lies the release machinery: a versioning scheme, a cadence and support window, release branches,
the CHANGELOG, and — for the highest-stakes case — the security-fix path that ships a CVE fix across
multiple supported lines at once. This chapter is how Firecracker ships, governed by a single
authoritative document, docs/RELEASE_POLICY.md, which you should read in full and treat as ground
truth over anything here.
Unlike a foundation project, there is no release vote, no rotating community release manager, no
opensearch-build-style separate control plane. The AWS maintainer team owns the release: they
decide what ships, cut the branch, tag the version, and publish. Your job is to understand the policy
well enough to get your fix onto the right train — and to know why a CVE fix moves on a completely
different schedule from a feature.
cd ~/fc-src
# The authoritative policy. Everything in this chapter defers to it (verify on your branch):
sed -n '1,200p' docs/RELEASE_POLICY.md
# The shipped record of what every release contained:
sed -n '1,80p' CHANGELOG.md
# The tags and branches that are the releases:
git tag --list 'v*' | tail -20
git branch -r | rg -i "firecracker-v|release" | tail -20
SemVer: The Version Number Is a Promise
Firecracker follows Semantic Versioning. The version MAJOR.MINOR.PATCH is a compatibility
contract, and which digit moves tells a downstream operator exactly what they're in for:
| Bump | Example | Carries | Compatibility promise |
|---|---|---|---|
| MAJOR | 1.x → 2.0 | Breaking API/CLI/snapshot changes, removed deprecations | May break compatibility (within policy); rare and deliberate |
| MINOR | 1.14 → 1.15 | New features, additive API, new device support, deprecations | Backward compatible within the major |
| PATCH | 1.15.0 → 1.15.1 | Bug fixes and security fixes only | Strict compatibility; no new features |
This maps directly onto the maintainer mindset: the reason a breaking API change or a snapshot-format break is so hard to land is that, by SemVer, it can only ship in a MAJOR — and Firecracker majors are infrequent. A feature rides the next MINOR; a bug or security fix rides a PATCH on a supported line. The version number you're targeting is determined by what your change is, not by when you want it out.
Note: Snapshot compatibility has its own stated guarantees in the policy and the snapshotting docs — which versions can restore which snapshots — layered on top of SemVer. A snapshot taken by one supported version is expected to restore on others within a defined window. Read the exact guarantee in
docs/RELEASE_POLICY.mdanddocs/snapshotting/rather than assuming; it's the contract the snapshot compatibility gate protects.
Cadence and the Support Window
Firecracker ships minor releases on a regular cadence (historically roughly every couple of months
— verify the current cadence and dates in docs/RELEASE_POLICY.md), and supports a defined window
of recent releases. The support window is the operationally critical part:
- A set of the most recent releases is supported — they receive bug fixes and, crucially, security fixes as patch releases. Older releases fall out of support and stop receiving fixes.
- The window is what determines which lines a CVE fix is backported to. When a vulnerability is fixed, it ships as a patch on every supported line, not just the latest — which is exactly why the PCI-transport CVE produced two patch releases (see below).
- The support window is a promise to downstreams (Lambda, Fargate, firecracker-containerd, Kata) about how long they can stay on a given minor before they must upgrade to keep receiving security fixes.
cd ~/fc-src
# How many minors back are supported, and the cadence (verify — this is policy, it changes):
rg -n -i "support|maintain|cadence|window|month|release.*branch|backport" docs/RELEASE_POLICY.md
Release Branches
Firecracker uses release branches to maintain supported lines independently of main:
main ──●──●──●──●──●──●──●──●──●──●──► (development; next minor's features land here)
\ \
\ firecracker-v1.15 ──●──────●─────► 1.15.0, 1.15.1 (patches)
\ ▲
firecracker-v1.14 ──●──────●─────┘─► 1.14.0, ..., 1.14.4 (patches)
(Branch names are illustrative — confirm the real naming with git branch -r on your checkout.)
mainis the development line. New features and the next minor accumulate here.- A release branch is cut for each minor (e.g.
firecracker-v1.15). Patch releases for that line (1.15.1,1.15.2) are tagged off its release branch. - A patch ships from its release branch. When a bug or security fix needs to reach a released line,
it is backported: merged to
mainfirst (if applicable), then cherry-picked onto each supported release branch, where it becomes the next patch tag.
This branch model is why backports exist as a distinct workflow, and why a fix can be on main for a
while before it reaches the version an operator is actually running.
The CHANGELOG Is the Release's Source of Truth
Every user-visible change adds a CHANGELOG.md entry under the unreleased section, in the right
category. At release time, the accumulated entries for the version being cut become the release notes.
This is the most direct line between your PR and the release.
cd ~/fc-src
# The unreleased section accumulates your entries until the next release rolls them up:
sed -n '1,60p' CHANGELOG.md
# How security and breaking changes are categorized — match this exactly:
rg -n "^### (Added|Changed|Deprecated|Removed|Fixed|Security)" CHANGELOG.md | head
Consequences for you (the mechanics are in code-style-trust.md; the release meaning is here):
- A change with no CHANGELOG entry is invisible in the release notes — and downstream operators read those notes to decide whether to upgrade. Reviewers block on a missing entry.
- The category encodes the SemVer impact. A
### Removedor breaking### Changedentry signals a MAJOR;### Addedsignals a MINOR;### Fixed/### Securitysignal a PATCH-eligible change. Mis-categorizing misrepresents the compatibility impact of your change. - On a backport, the entry travels to the patch release's notes for that line.
What Blocks a Release
A release doesn't ship until the maintainers are satisfied it's safe to put under Lambda and Fargate. The hard blockers:
| Blocker | Why it blocks |
|---|---|
| CI red on the release branch | fmt/clippy/build/pytest/Kani must be green (github-review.md) |
| A known regression vs the prior release | Boot-time/memory/IO/density regressions are release-blocking (maintainer-mindset.md) |
| A broken compatibility contract | API, snapshot, or config breakage outside a MAJOR |
| An open release-blocking issue | See issue-roadmap stage 12 |
| Failing cross-version snapshot/restore tests | The snapshot guarantee is part of the release promise |
| A licensing/attribution problem | A dependency or header issue (licensing-and-dco.md) |
The highest-stakes version of "release-blocking" is a known, unfixed security issue on a supported line — which routes into the path below.
CVE Handling: A Different Track Entirely
Security fixes do not flow through the normal public PR pipeline, and understanding the difference is a mark of a serious contributor. The flow:
flowchart TD
A[Vulnerability reported PRIVATELY to AWS Security per SECURITY.md] --> B[Triaged privately; embargo]
B --> C[Fix developed on a private branch by maintainers]
C --> D[Fix prepared for EVERY supported release line]
D --> E[Coordinated patch releases tagged across supported lines]
E --> F[Public disclosure: CHANGELOG Security entry + GitHub Security Advisory + CVE]
F --> G[Downstreams (Lambda/Fargate/containerd/Kata) upgrade]
The concrete, real example — use it as your mental model:
CVE-2026-5747 lived in the virtio-PCI transport (the opt-in
--enable-pcipath — exactly the kind of new attack surface the maintainer mindset warns about). It was fixed and shipped as patch releases 1.14.4 and 1.15.1 — two patches, one per supported line, on the same day, because both the1.14and1.15lines were within the support window and both needed the fix. (Verify the exact versions and details on your branch / the advisories — security facts are version-sensitive.)
That single example teaches the whole model:
- Private until fixed. No public issue, no public PR, no early warning to attackers — the inverse of the normal public-by-default workflow.
- Backported to every supported line. The support window defined in
docs/RELEASE_POLICY.mddetermines the set of patch releases a CVE produces. That's why one bug became 1.14.4 and 1.15.1. - Disclosed only after the fix is available everywhere it's owed. The CHANGELOG
Securityentry, the GitHub Security Advisory, and the CVE all land together, after the patches.
cd ~/fc-src
# Read the output of this pipeline — published advisories and their fix versions:
gh api repos/firecracker-microvm/firecracker/security-advisories --jq '.[] | {summary, severity}' 2>/dev/null | head
rg -n -i "CVE-|GHSA|security advisory" CHANGELOG.md | head
git tag --list 'v1.1*' | tail # see the patch tags CVEs produce
Backports: How a Fix Reaches the Version People Run
Because supported lines live on release branches, getting a fix to a user on an older minor is a
backport, not just a merge to main. The general shape (confirm the exact convention in
CONTRIBUTING.md / docs/RELEASE_POLICY.md):
- The fix lands on
mainvia the normal two-approval PR process, with its CHANGELOG entry. - It is cherry-picked onto each supported release branch that needs it, re-reviewed there, with the CHANGELOG entry placed in that line's notes.
- The next patch tag on that release branch ships it.
Warning: A fix merged to
maindoes not automatically reach the version an operator is running. If your bug fix matters to people on a released line, it must be backported to that line's release branch before the next patch is cut, or it waits. For an ordinary contributor this is usually maintainer-driven, but flag it explicitly in your PR if the fix is important to a supported release — say so, link the affected versions, and make the case.
Getting Your Change Into a Release: The Operational Checklist
To land a change in a specific release:
-
Know which train it can catch. Feature → next MINOR off
main. Bug/security fix → PATCH-eligible, backportable to supported lines. (Determined by what the change is — SemVer.) -
Merge to
maincleanly (two approvals, green CI, CHANGELOG entry) before the relevant release branch is cut. - Flag backport-worthiness in the PR if the fix should reach a supported older line.
- Put the CHANGELOG entry in the right category (it encodes the SemVer impact and becomes the release note).
- For anything snapshot- or API-touching, prove cross-version compatibility with tests — or it blocks the release (maintainer-mindset.md).
- For a security issue, do none of the above publicly — report privately and let the coordinated process run.
Prove You Understand This
- State Firecracker's versioning scheme and what each of MAJOR/MINOR/PATCH is allowed to carry. Which one can a new device feature ride?
- What is the support window, and how does it determine the set of patch releases a single CVE fix produces? Use the 1.14.4 / 1.15.1 example.
- Why does a fix merged to
mainnot automatically reach a user on an older supported minor? What makes it reach them? - Describe the CVE pipeline end to end and name three ways it differs from the normal public PR flow.
- Name four things that block a release, and tie each back to a guarantee Firecracker is making.
- You have a bug fix that matters to operators on the previous minor. List, in order, what has to happen for them to get it.
Next: Project Governance and the AWS Team — who owns the merge button, the release, and the direction, and what that means for how far you can rise.