Four Debian 13 Boxes, One Brief: 1,923 Packages on Metal, 328 in the Cloud A developer benchmarked Debian 13 on four machines—one laptop and three cloud instances from AWS, GCE, and Azure—revealing that cloud images ship with far fewer packages (328-350) than a full install (1,923) and often lack tools like nvme-cli and dmidecode, leaving hardware health unreadable. The test also found that AWS and Azure use the same upstream image, while GCE builds its own, and highlighted a pitfall where cloud-init scripts can skew measurements. This article covers one afternoon of running the same Debian 13 on four machines — one laptop and three clouds — and asking each of them the same question about its own hardware. A cloud image is not a small Debian. It is a different bet about what you will need. The laptop install carries 1,923 packages. The three cloud images carry 328, 337 and 350. That is not a rounding difference, and the interesting part is not the count — it is that all four are booting from NVMe, and only two of them ship anything that can read an NVMe health log. What that creates is a gap with two halves. The hardware is present and the kernel can see it. The default toolset cannot. And because nothing fails, nothing tells you: nvme-cli dmidecode and Vulnerability Retbleed: Vulnerable GPT:16777215 = 41943039 , still wrong at the boot I measured. GCE emits the identical warning and That last pair is the whole article. Same kernel, same message, opposite correct answers. https://github.com/xbill9/local-bench https://github.com/xbill9/local-bench Four hosts, all Debian 13 trixie , all measured on 2026-09-09: | | Image | Shape | CPU | Kernel | |---|---|---|---|---| | local | installed, bare metal | Lenovo 83B1 | i7-1360P, Raptor Lake, 16 threads | 7.1.8+deb13-amd64 backports | | AWS | ami-0871da4641e8b4413 | c7i.large | Xeon 8488C, Sapphire Rapids, 2 vCPU | 6.12.107+deb13-cloud-amd64 | | GCE | debian-13-trixie-v20260908 | c4-standard-2 | Xeon 8581C, Emerald Rapids, 2 vCPU | 6.12.107+deb13-cloud-amd64 | | Azure | Debian:debian-13:13-gen2:0.20260831.2587 | Standard D2s v7 | Xeon 6973P-C, Granite Rapids, 2 vCPU | 6.12.107+deb13-cloud-amd64 | One detail decides how to read everything below. The AWS and Azure images are the same upstream Debian build — 2587 , dated 2026-08-31 — running the same kernel. GCE builds its own, a week newer. So where AWS and Azure differ, the distro is not the variable. The platform is. ssh , and a key you are willing to put on a throwaway host Steps 0 through 2 are reads and launches. Step 3 installs software, and Step 3 is the point of no return for the baseline — see Step 1 for why that ordering is not optional. An image family is a moving target, and "Debian 13" is not a measurement. Resolve it to an ID before you launch anything, and write the ID down. AWS — the official Debian account is 136693071363 aws ec2 describe-images --owners 136693071363 \ --filters "Name=name,Values=debian-13-amd64- " "Name=state,Values=available" \ --query 'sort by Images,&CreationDate -1 .{Name:Name,ID:ImageId,Date:CreationDate}' GCE — resolve the family to the image it currently points at gcloud compute images describe-from-family debian-13 \ --project debian-cloud --format="value name " Azure — the stable offer is debian-13; debian-13-daily is a different thing az vm image list --publisher Debian --offer debian-13 --sku 13-gen2 --all \ --query " ?offer=='debian-13' | sort by @,&version -1 .urn" -o tsv That third one has a trap in it. Asking Azure for --offer debian-13 with --all will happily return debian-13-daily URNs alongside the stable ones, because the filter matches loosely. The ?offer=='debian-13' clause is doing real work — without it you will pin a daily build and not notice. This is also where the AWS/Azure coincidence shows up: both resolved to build 2587 , dated 2026-08-31. That is not a fluke, it is the same upstream Debian cloud image, and it is the single most useful fact for interpreting the rest. This step exists because I got it wrong the first time. The first EC2 instance I launched had a user-data script that installed about twenty packages before I ever logged in. Then I scanned it and wrote down what was present. Every one of those findings was worthless: I was measuring my own cloud-init, not Debian's image. That instance had to be discarded and relaunched with no user-data at all. A default-install analysis has exactly one chance to be correct, and it is before the first apt-get install . The scanner has to run on a machine where nothing is installed, which means it can only use what a minimal cloud image actually ships — bash, coreutils, /proc , /sys , dpkg . No jq . No lspci , because pciutils might not be there. Absence is the data, so nothing may fail: hardware, without needing dmidecode for f in sys vendor product name bios version bios date; do -r "/sys/class/dmi/id/$f" && printf '%-16s %s\n' "$f" "$ cat /sys/class/dmi/id/$f " done what the image actually ships, by dpkg, not by PATH for p in pciutils usbutils dmidecode nvme-cli smartmontools lm-sensors \ ethtool intel-microcode fwupd build-essential gcc git unzip; do s=$ dpkg-query -f '${db:Status-Status}' -W "$p" 2 /dev/null "$s" = installed && echo "PRESENT $p" || echo "absent $p" done Check packages with dpkg , not with command -v . This is the second thing I got wrong, and it is worth more than the first. My initial pass over the laptop reported dmidecode , nvme-cli , smartmontools , hdparm , ethtool , powertop and turbostat as missing. All seven were installed. My login PATH contained no /usr/sbin , no /sbin , no /usr/local/sbin , and a bare command -v cannot see a binary it has no path to. Seven false negatives from one unstated assumption, on the machine I know best. The full scanner is evidence/baseline-scan.sh in the repo. Run it, keep the output, then install things. No user-data , no startup script, no custom image. Lock SSH to your own address rather than the world — every one of these CLIs will happily open port 22 to 0.0.0.0/0 if you let it. MYIP=$ curl -s https://checkip.amazonaws.com /32 AWS aws ec2 run-instances --image-id