Testing Azure Workloads Locally With Azure Linux 4.0's Fedora-Based ISO

Testing Azure Workloads Locally With Azure Linux 4.0's Fedora-Based ISO

pr0h0•
azure-linuxfedoracloud-infrastructuredevopslinux-distros
AI Usage (89%)

Running the Azure Linux 4.0 Host OS in a Local VM

Azure Linux used to be something you ran into sideways — a Marketplace image string buried in a Terraform file, or an /etc/os-release line you only noticed after SSH-ing in. If the report that Microsoft now ships Azure Linux 4.0 as a downloadable, Fedora-based ISO holds up, that changes how teams can build and test Azure workloads. This post walks through booting that ISO locally, verifying what the image actually contains, and deciding which workload tests are worth running before you pay for a deploy.

My position up front: the ISO is genuinely useful for pre-flight checks, and it is not evidence that a workload will behave the same way on a real Azure host. The gap is narrow in places and wide in others, and the wide parts are the ones that page you at 3am. A local boot is a fast filter, not an approval stamp.

📝

A convention for this post: I did not have a verified Azure Linux 4.0 build in hand while writing it, so I am not going to paste kernel strings or package versions I never read. Code blocks below are labeled as commands or as expected output shape. Anything I could not verify is written as unknown rather than guessed.

What Microsoft Actually Announced for Azure Linux 4.0

The report I started from — NewsBytes, published 2026-10-04 — says Microsoft released Azure Linux 4.0 as an open-source, Fedora-based ISO available for direct download. That is the claim I am working from, and the only part I am treating as given.

Everything else has to come from the project itself. The package set, the kernel flavor, the release notes, the architecture targets, the support lifecycle — I would read all of that from the upstream repository and the official documentation, not infer it from the phrase "Fedora-based." I am deliberately not going to invent a kernel version or a glibc version to make this post look complete. If the project's own release notes do not state something, it is unknown, and "unknown" is more useful to you than a confident wrong number.

What the report does establish is a delivery change: an installable image you can pull down without going through Azure's image pipeline. That framing is the one worth thinking about, because it is the part that changes what teams can do.

Why the Delivery Change Matters More Than the Distro

"Microsoft ships Linux" stopped being news years ago. The interesting shift is that the host OS is now consumable outside the Azure image pipeline. Teams that previously had two options — deploy a Marketplace image into a real subscription, or approximate the host with a generic Fedora or CentOS Stream guest — now have a third: boot the actual thing locally, inspect it, and break it without a billing account.

ApproachWhat it reproducesWhat it cannot reproduceWhere it misleads
Azure Marketplace imageThe real host OS build, platform agents, image-time hardening, the kernel as shippedYour network path, your data, region rollout state, the cost of a bad deployIt feels authoritative, so people skip the workload-side test entirely
Downloaded ISO in a local VMUserspace package set, systemd unit layout, SELinux policy defaults, cloud-init boot pathHost hypervisor and host kernel, accelerated networking, IMDS behavior, platform patching cadenceEncourages "it booted, therefore it deploys"
Container base imageYour app's runtime dependencies, with fast iterationInit system, kernel, SELinux, boot ordering, host agentsHides everything outside the container boundary

The second row is the new capability and the third row is the trap. If your "local reproducibility" story is a container, you are testing maybe a third of the surface the host OS actually exposes.

Booting the Azure Linux 4.0 ISO Locally

The workflow is boring on purpose. Pull the image, verify the checksum before you trust it, boot it with cloud-init attached so you get a login without clicking through an installer.

export ISO_URL="<paste the ISO URL from the project's release notes>"
export ISO="azurelinux-4.0-x86_64.iso"

curl -fL --retry 3 -o "$ISO" "$ISO_URL"
curl -fLO "$ISO_URL.sha256"
sha256sum -c "$ISO.sha256"

The line you want to see:

azurelinux-4.0-x86_64.iso: OK

If that line does not say OK, stop. A checksum failure is the cheapest possible place to learn that your download was truncated or that you are not pulling what you think you are pulling.

Then build a cloud-init seed and boot. cloud-localds comes from cloud-image-utils on Debian/Ubuntu hosts:

cloud-localds seed.iso seed/user-data seed/meta-data

virt-install \
  --name azl4-preflight \
  --memory 4096 --vcpus 2 \
  --disk size=20,format=qcow2 \
  --cdrom "$ISO" \
  --cloud-init user-data=./seed/user-data,meta-data=./seed/meta-data \
  --os-variant detect=on \
  --graphics none --console pty,target_type=serial \
  --noautoconsole

Record the host OS, the hypervisor version, and the ISO build date alongside whatever you observe. Results here are version-sensitive; a finding without that context is a rumor.

Verifying What the ISO Image Actually Ships

This is the part that matters, and it is where local testing can lie to you. Run these inside the guest and keep the output.

cat /etc/os-release
uname -r
rpm -q --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' kernel glibc systemd
dnf repolist --all
systemctl is-enabled waagent cloud-init 2>&1
getenforce

Then read the output rather than skim it:

  • /etc/os-release — the ID and VERSION_ID are the ground truth for what you are actually running. If ID is not what you expected, everything downstream is suspect.
  • uname -r — the running kernel, which may not be the newest kernel package installed.
  • rpm -q — whether glibc and systemd match what the release notes claim. A binary you build locally links against this glibc, and that is a classic way to ship something that only runs on your laptop.
  • dnf repolist — which repositories are enabled. "Fedora-based" does not mean Fedora-package parity. Vendors rebuild and pin subsets, and the moment you pull a package from an upstream Fedora repo to unblock yourself, you have stepped off the supported surface without noticing.
  • waagent and cloud-init enablement — whether Azure-specific userspace is present and expected to start at boot. This is what decides whether your local boot ordering resembles a real first boot.
  • getenforce — Enforcing versus Permissive changes which of your failures are real.

If a vendor value differs from what you assumed, the vendor value wins. That is the entire point of running the check.

Reproducing Azure-Only Behavior

Some things you can simulate locally and some you genuinely cannot.

Simulatable: the cloud-init boot path, systemd unit ordering and dependency resolution, host firewall defaults as shipped in the image, agent startup ordering, and a stand-in for IMDS by binding something to 169.254.169.254 inside the guest and serving plausible metadata JSON.

Not simulatable: physical host networking and accelerated networking, the host hypervisor and host-kernel behavior underneath your guest kernel, the platform's own patching cadence, and the platform's side of the shared-responsibility line.

⚠️

A local IMDS stand-in is a mock. Any code path you "verify" against it is untested-against-production until you confirm it on a real Azure host. Mocks confirm your logic branches; they do not confirm platform behavior.

Testing a Real Workload, End to End

Take one small scoped service — a static HTTP responder, nothing with credentials or private endpoints.

sudo dnf install -y golang
mkdir -p ~/svc && cd ~/svc
cat > main.go <<'EOF'
package main

	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "ok")
	})
	http.ListenAndServe("127.0.0.1:8080", nil)
}
EOF
go build -o svc . && sudo install -m 0755 svc /usr/local/bin/svc

Then run it under a unit and check the result:

sudo systemctl start svc && curl -s http://127.0.0.1:8080/healthz

Output you want to see:

ok

That passes and tells you very little, which is the useful lesson. Now trigger the failure that only exists because this guest is not the production host: bind the service to the host's external interface instead of loopback and watch the shipped firewall policy decide the outcome. Whatever you observe locally is the image's default, not your platform's enforced default. The second failure I look for is an agent or unit that waits on IMDS at boot. Locally, that dependency has nothing to talk to, so the unit either times out or starts late. On a real host it resolves in milliseconds and the ordering bug you just found disappears — or reappears somewhere worse. Both are worth writing into the release checklist.

Failure Modes I Expect Before You Hit Them

Failure modeWhy it bitesStatus
Upstream Fedora drift vs. the vendor's rebuilt setYou unblocked yourself with an upstream package; production runs the vendor's pinned buildInferred
Kernel or glibc mismatchA binary built against local glibc does not load on the production hostInferred, well-documented class
SELinux denials that pass locallyLocal guest is permissive; production policy is stricterInferred
Container base-image mismatchThe container hid the host differences you were trying to testInferred
"Works in QEMU" boot assumptionsTwo vCPUs and no accelerated networking is not a production hostInferred
IMDS-dependent startupNothing to resolve at 169.254.169.254 locallyFailed to reproduce

Defense: Making Local Fidelity Earn Its Place

These are rules I would actually enforce on a team, not aspirations.

  1. Pin the ISO by build hash, never "latest." Record the hash and the download date next to the test result. A finding you cannot date is not a finding.
  2. Verify the checksum and the repo list in CI. Not in a wiki page. If the repo list changes, the test should notice.
  3. Keep a base-image matrix. Local ISO, Marketplace image, container base. Mark which tests run against which, and never let the local guest hold authority over a version number.
  4. Keep the Azure-side integration test as the release gate. The local VM is a pre-filter that catches fast, cheap failures. It does not sign off.
  5. Write down what the local guest cannot answer. A one-line list in the repo is enough, and it stops the next person from treating a green local run as coverage.

This is a workflow opinion and I will defend the reasoning: a local boot costs minutes, and a bad deploy costs hours of rollback plus whatever audience watched it happen. Paying minutes to catch the cheap failures is obviously correct. The danger is not the local VM — it is the team that lets the local VM become the last test before production.

What I Confirmed and What I Did Not Test

Confirmed by the report: Microsoft released Azure Linux 4.0 as an open-source, Fedora-based ISO available for direct download (NewsBytes, 2026-10-04).

Not tested by me, and therefore unknown:

  • The kernel version, glibc version, and package set in any 4.0 build
  • The exact repository set enabled in the image
  • Whether any specific IMDS-dependent code path resolves differently on a real host
  • The architecture targets and support lifecycle

I also want to be explicit about one failure: I could not reproduce an IMDS-dependent startup path locally at all, because there is no platform IMDS on a laptop. Any claim that "the service starts fine here" is therefore partial by construction, and I would rather say that than imply full coverage.

Further Reading

Closing Position

A downloadable, inspectable host OS ISO raises the floor for pre-deploy testing, and I would adopt it for exactly that. It does not shrink the production trust boundary: the host hypervisor, the networking fabric, the platform's patching cadence, and IMDS all stay on Microsoft's side of the line. A team that treats a successful local boot as deploy approval has not removed risk — it has just moved the failure earlier, where it is cheaper to find and easier to ignore.

Share this post

More posts

Comments