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:

BumpExampleCarriesCompatibility promise
MAJOR1.x → 2.0Breaking API/CLI/snapshot changes, removed deprecationsMay break compatibility (within policy); rare and deliberate
MINOR1.14 → 1.15New features, additive API, new device support, deprecationsBackward compatible within the major
PATCH1.15.0 → 1.15.1Bug fixes and security fixes onlyStrict 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.md and docs/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.)

  • main is 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 main first (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 ### Removed or breaking ### Changed entry signals a MAJOR; ### Added signals a MINOR; ### Fixed / ### Security signal 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:

BlockerWhy it blocks
CI red on the release branchfmt/clippy/build/pytest/Kani must be green (github-review.md)
A known regression vs the prior releaseBoot-time/memory/IO/density regressions are release-blocking (maintainer-mindset.md)
A broken compatibility contractAPI, snapshot, or config breakage outside a MAJOR
An open release-blocking issueSee issue-roadmap stage 12
Failing cross-version snapshot/restore testsThe snapshot guarantee is part of the release promise
A licensing/attribution problemA 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-pci path — 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 the 1.14 and 1.15 lines 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.md determines 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 Security entry, 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):

  1. The fix lands on main via the normal two-approval PR process, with its CHANGELOG entry.
  2. 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.
  3. The next patch tag on that release branch ships it.

Warning: A fix merged to main does 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 main cleanly (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

  1. State Firecracker's versioning scheme and what each of MAJOR/MINOR/PATCH is allowed to carry. Which one can a new device feature ride?
  2. 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.
  3. Why does a fix merged to main not automatically reach a user on an older supported minor? What makes it reach them?
  4. Describe the CVE pipeline end to end and name three ways it differs from the normal public PR flow.
  5. Name four things that block a release, and tie each back to a guarantee Firecracker is making.
  6. 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.