Build1 publisher3 min readPublished
AWS and Azure ship Debian 13 cloud images with no nvme-cli on NVMe-only hosts
One laptop and three clouds ran the same Debian 13 on 2026-09-09. The three cloud images sit within 22 packages of each other and still disagree about which disk and CPU facts a default install can read at all.
The Engineer · Build desk

What happened
- The same Debian 13 trixie, measured on 2026-09-09, ships 1,923 packages on a laptop install and 328, 337 and 350 packages across three cloud images.
- All four hosts boot from NVMe, but only the laptop and the GCE instance ship a tool capable of reading an NVMe health log.
- Azure's host reports Vulnerability Retbleed: Vulnerable, and the AWS and GCE hosts do not.
- On AWS the backup GPT header still recorded the pre-resize geometry at the boot measured, while GCE emitted the identical warning and corrected it during that same boot.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Any disk-health check that shells out to nvme must be installed before it reports anything on AWS or Azure, so monitoring specified by distro name inherits a tool gap the image never declares.
- capability Two clouds running one upstream build give you an A/B with the distro held constant, which is the only way a single-boot comparison can attribute a difference to the platform rather than to Debian.
- exposure Partition tooling that falls back to the AWS host's backup header reads the pre-resize geometry, and because no command errors, the discrepancy surfaces only when someone goes looking for it.
- contradiction Images carrying no firmware packages at all still report different CPU mitigation states, so the mitigation you get is decided somewhere the image does not control and this measurement cannot name.
On an AWS or Azure Debian 13 host the NVMe device is present and the kernel enumerates it. What is missing is the userspace tool that speaks the admin command set, so a health check that shells out to `nvme` fails at the binary lookup, not at the device [4]. The error names your script. That is the shape of every finding here: the hardware works, the default toolset cannot interrogate it, and nothing returns non-zero to tell you [13]. GCE is the only cloud of the three that ships both `dmidecode` and `nvme-cli` [5].
The comparison holds together because of one number in the setup. Both the AWS and Azure images resolved to the same upstream Debian cloud build, on the same kernel, so when Azure's sysfs reports Retbleed as vulnerable and AWS's does not, the distro is not the variable [10][6]. That is the author's own reading, and it is the right one [12]. Every cloud image ships zero firmware packages and logs zero firmware load attempts, which is consistency of a sort, and it also means whatever decides that mitigation string lives outside the image [9]. The measurement does not identify what.
The GPT numbers say how stale the backup header is. Subtract: 41,943,039 minus 16,777,215 is 25,165,824 sectors [22]. At 512-byte logical sectors that is 12 GiB of volume the backup header has never heard of, an 8 GiB layout recorded on a 20 GiB disk [22]. The source does not print the sector size, so treat the byte figure as conditional on the common default.
The package counts are the least interesting number and the easiest to over-read. 1,595 packages separate the laptop from the leanest cloud image, while 22 packages separate the three clouds from each other [19][20]. Three vendors broadly agree on what minimal means. They disagree on which two diagnostic tools you get.
Reproducing this costs one instance per mistake. The first EC2 host ran a `user-data` script that installed about twenty packages before first login, which turned every finding into a measurement of cloud-init, and the instance was discarded and relaunched clean [14]. A default-install audit has exactly one chance, and it closes at the first `apt-get install` [15]. So the scanner uses only bash, coreutils, `/proc`, `/sys` and `dpkg`: no `jq`, and no `lspci`, because `pciutils` may not be there at all, and when absence is the data nothing is allowed to fail [16].
One line in the setup earns its keep. Asking Azure for `--offer debian-13 --all` returns `debian-13-daily` URNs alongside the stable ones, so the `[?offer=='debian-13']` clause is what stops you pinning a daily build and comparing it against two stable images [17]. Resolve the family to an ID and write the ID down before launching anything [23].
Four hosts, one date, one boot each [11]. The findings transfer to your fleet if your instances came from those same image IDs with nothing baked on top. If your pipeline already installs `nvme-cli` and `dmidecode`, half the gap is already closed, and what remains is the platform behaviour the shared upstream build was there to isolate [10][12].
What to watch
- Whether a Debian cloud build newer than 2587 relocates the backup GPT header on AWS at first boot, or whether the repair stays a GCE behaviour.