Download SHA-256: 69c761fd6b2debdb03aa6200f6cc7395964f2d9b588935c1a20bc93d4efe60b5
azure infrastructure workshop

AI-generated workshop artwork; not a live Azure capture. Prompt and provenance (download: docs/images/cover-provenance.json).
Private endpoints configured. Sign-in successful. Image pull broken. Learn to prove which layer failed.
Build a private Premium Azure Container Registry (ACR), publish a Windows container image, and pull it using a different Windows VM identity. Then diagnose three deliberately different failures without opening public access or granting broader roles.
This independent workshop follows the guided, evidence-first approach of Lab 05 — Network Core. Use Terraform, PowerShell and visible Azure CLI commands alongside the Azure Portal. Allow 3–5 hours, including Windows installation, restarts and cleanup; the deployment must be deleted within six hours. Browse Azure Workshopz.
Verification status: All 21 required live checks passed, including the three fault/recovery cycles, final no-drift validation, cleanup and repeated cleanup. VALIDATION.md (download: VALIDATION.md) distinguishes these observed results from the local tests. This release includes 19 genuine Portal/CLI screenshots. The final client-resolver fault, recovery and cleanup are documented with retained, redacted text/JSON evidence; four additional screenshots were omitted from the accepted publication scope on 18 October 2026. The capture guide (download: docs/screenshot-guide.md) and image provenance (download: docs/screenshots/provenance.json) identify what was actually captured; expected results describe what your own run must prove.
What you will learn
Provision a Premium registry with public access disabled and a private endpoint connected to private DNS.
Distinguish the registry login endpoint from the regional endpoint that serves image layer downloads.
Bootstrap Windows Server containers through a controlled feature-installation and restart sequence.
Separate publisher and reader privileges using VM managed identities and repository ABAC conditions.
Prove a private image push, a fresh pull by digest and execution of the correct synthetic marker.
Diagnose missing credentials, an unauthorized repository and a client resolver fault independently.
Restore only the owned fault, repeat repair safely, prove recovery, and remove exact recorded resources.
Scenario and boundaries
A publisher VM builds a small Windows image containing a unique workshop marker and pushes it into workshop/app. A reader VM pulls the exact manifest digest and prints the marker. Both clients use Windows Server 2025 Datacenter Azure Edition and their own system-assigned managed identity. They share a VNet, but their credentials and permissions are separate.
The group is disposable. Use synthetic content only and do not add unrelated resources. This is a single-region learning environment, not a production registry architecture or a container orchestrator. It includes no application database, geo-replication, image signing policy or production disaster-recovery design.
No VM has a public IP, and no inbound RDP or SSH rule is opened. Azure Run Command is the management channel. It depends on a working Azure VM Agent and guest connectivity; a failed management command is not evidence of a registry outage. Action Run Command also has execution/output limits, so the scripts run bounded stages and retain detailed private logs. See Windows action Run Command.
The clients have explicit outbound connectivity through a NAT Gateway for approved installers, identity endpoints and the Microsoft Container Registry base image. Registry traffic uses the private endpoint. NAT Gateway permits return traffic for outbound flows; it does not expose a VM administration port. This workshop does not claim to implement destination-filtered egress. See NAT Gateway behavior.
Architecture and address plan

Component | Workshop configuration |
Region | West Europe |
VNet | 10.170.0.0/24; preflight rejects overlap |
Client subnet | 10.170.0.0/26, explicit NAT egress |
Private-endpoint subnet | 10.170.0.64/27 |
Client VMs | Two Standard_D2s_v5, Windows Server 2025 Datacenter Azure Edition, Standard SSD |
Registry | Premium; admin, anonymous and public access disabled; no trusted-service bypass |
Private DNS | privatelink.azurecr.io, linked to the VNet, records maintained by the private-endpoint DNS zone group |
Publisher | Repository Writer, condition limited to workshop/* |
Reader | Repository Reader, condition limited to workshop/app |
Runtime/image | Pinned Docker CE/Moby runtime, Nano Server ltsc2025 image digest, process isolation |
ACR Private Link requires Premium. The initial endpoint has separate private addresses for the registry and home-region data endpoint. Discover the real FQDNs and addresses from Azure; do not infer them from a resource-name prefix. Both must match the private-endpoint NIC configuration. ACR Private Link configuration.
1. Prerequisites, cost and private state
Use a Windows operator workstation with PowerShell 7.5 or later, Azure CLI and Terraform 1.16.4 already installed. The configuration pins AzureRM 5.8.0 and includes a provider lock file. Authenticate with your approved interactive process before starting. The guest container engine is Docker CE/Moby on Windows Server; do not install Docker Desktop on the server VMs. Microsoft's Windows container setup describes the runtime options and required Containers feature.
The operator needs permission to create and remove the isolated resource group, its networking/compute/registry resources, VM Run Command and VM restart operations, and the conditional registry role assignments. Contributor alone does not grant role-assignment write permission. Obtain the narrowly scoped role-management rights through your normal administrator process; the scripts do not elevate your account or register providers silently.
Preflight checks the exact default subscription and tenant, required registered providers, regional quota/SKU/image availability, policy and locks, existing address spaces, tool versions and current retail rates. Two D2s_v5 VMs require four family vCPUs. A successful preflight is still not a guarantee that Azure capacity or every policy evaluation will permit deployment later.
Cost ceiling: EUR20, including EUR5 reserved for cleanup. The conservative six-hour estimate, including Windows licensing, ACR Premium, disks, NAT, public IP, private endpoint and expected data charges, must not exceed EUR15. Stop new exercises at five hours and start cleanup earlier. Actual offer rates, rounding and delayed billing can differ from estimates. This operational limit is not an Azure-enforced spending cap. Deallocating VMs does not remove registry, disk or networking charges.
Open PowerShell in the extracted complete package. Choose a new private state path outside both the package and any Git repository:
$private = Join-Path $env:LOCALAPPDATA ('AzureWorkshopz-PrivateACR-' + [guid]::NewGuid().ToString('N'))
$state = Join-Path $private 'state.json'
# Preview first. The stage runner prepares/reuses only its protected state directory.
./Invoke-AcrWorkshop.ps1 -Stage Preflight -StatePath $stateReview the preflight report privately. Do not continue on missing quota, unresolved policies, context mismatch, an unavailable pinned artifact or an over-budget estimate. Subscription/tenant IDs, generated administrator credentials, plans, Terraform state and raw logs stay in the protected directory. Terraform's sensitive annotation redacts normal displays; it does not encrypt a local state file. HashiCorp sensitive-data guidance.
2. Preview the plan, then deploy
./Invoke-AcrWorkshop.ps1 -Stage Plan -StatePath $stateRead the saved plan and its summary. It must create only this new workshop's resources. Check the VM image/version, size, disk type, private subnet ranges, public access settings, identity assignments and repository conditions. A newly generated plan replaces the previous approval target; review it again.
./Invoke-AcrWorkshop.ps1 -Stage Deploy -StatePath $state -Execute -AcknowledgeCost -ReviewedPlan-Execute authorizes the requested mutation. -AcknowledgeCost confirms the paid run. -ReviewedPlan confirms that you reviewed the saved plan whose hash is recorded privately; it is not permission to apply a different plan. Context and ownership checks remain enforced.
The selected Windows Server 2025 Azure Edition configuration uses patch_mode = "AutomaticByPlatform", reboot_setting = "IfRequired" and hotpatching_enabled = false. Keep these settings together: disabling Hotpatch does not mean choosing an OS-orchestrated patch mode for this image. Platform patching can still restart a VM when needed. The Containers-feature restart later in this workshop is separate. See Azure-orchestrated guest patching and Hotpatch overview.
Portal checkpoint: open the recorded resource group and registry. On Overview, verify Premium. Under Settings → Networking, verify public network access is Disabled and the private endpoint connection is Approved. Open the private endpoint → DNS configuration and compare both FQDN/private-IP mappings with the private-endpoint NIC. The login and data addresses are different; an Approved connection alone does not prove a successful pull.

The captured registry is Premium. The subscription, group and registry identifiers are masked.



This run assigned 10.170.0.68 to the regional data endpoint and 10.170.0.69 to the login endpoint. Discover your own assigned mappings; these screenshots are not hard-coded configuration inputs.
These are useful management-plane diagnostics. Supply the exact names recorded by your run; the stage runner performs the same lookups without guessing:
# Define these from your private run report; never paste the report publicly.
$subscriptionId = Read-Host 'Recorded subscription ID'
$resourceGroup = Read-Host 'Recorded resource group name'
$registryName = Read-Host 'Recorded registry name'
$privateEndpointName = Read-Host 'Recorded private endpoint name'
az acr show --subscription $subscriptionId --resource-group $resourceGroup --name $registryName `
--query '{sku:sku.name,publicAccess:publicNetworkAccess,admin:adminUserEnabled,anonymous:anonymousPullEnabled,mode:roleAssignmentMode}' -o json
az acr show-endpoints --subscription $subscriptionId --name $registryName -o json
az network private-endpoint show --subscription $subscriptionId --resource-group $resourceGroup --name $privateEndpointName `
--query '{connections:privateLinkServiceConnections[].privateLinkServiceConnectionState.status,dns:customDnsConfigs}' -o jsonThese commands inspect Azure configuration. A successful ARM query does not prove data-plane access from the operator workstation.

The visible CLI query cross-checks the Portal settings. The terminal username is masked, and the account header is cropped away.
If Terraform apply stops partway through
Keep the same private state, variables and exact run. First confirm the original Terraform process has ended and inspect the saved failure. A narrowly scoped resume is available only when the journal records an interrupted apply. It reconciles exact live IDs with Terraform state and permits creation of missing owned resources; existing resources must remain unchanged.
# Use the original $state. Do not start a new run over the partially deployed group.
./Invoke-AcrWorkshop.ps1 -Stage Preflight -StatePath $state
./Invoke-AcrWorkshop.ps1 -Stage Plan -StatePath $state -Resume
# Inspect the newly saved plan before confirming it.
./Invoke-AcrWorkshop.ps1 -Stage Deploy -StatePath $state -Resume -Execute -AcknowledgeCost -ReviewedPlanPreflight can refresh an expired price estimate for the same run; it does not reset expiry. Resume requires the recorded pending apply, unchanged context, verified ownership and a newly reviewed plan. It is not a general replay flag, permission to import unrelated resources, or a way to resume a pending destroy. If the cause is unknown, the plan changes existing resources, or the five-hour exercise stop has been reached, reconcile and clean up instead.
3. Bootstrap Windows in resumable stages
./Invoke-AcrWorkshop.ps1 -Stage Bootstrap -StatePath $state -ExecuteBootstrap enables the Windows Containers feature, handles the required restart as a separate controlled operation, waits for the VM Agent, and installs/verifies the pinned runtime and guest tools. If a stage is interrupted, inspect its private journal and rerun the same stage after reconciling its recorded operation. Do not start a second competing installer or manually clear an active lock.
The operator controller requires PowerShell 7.5 or later. Run Command executes the self-contained guest dispatcher using Windows PowerShell 5.1. These are separate runtimes. Bootstrap checks downloaded hashes, the Microsoft MSI signature, actual Docker server and Azure CLI versions, and the Windows engine type. Docker uses its local named pipe; TCP listeners on ports 2375/2376 are rejected.
The pinned Nano Server release matches the Windows Server host for process isolation. A host/image compatibility failure can look like a registry problem if you only read the final docker run error. First prove the image was pulled, then inspect the host build, image OsVersion and isolation setting. Windows container compatibility.
Expected runtime checks on both VMs: Containers feature installed, reboot completed, Docker service running, server OS reported as Windows, and a compatible pinned base image. The learner does not need a local Windows container engine on the operator workstation.

The original Cloud Shell tooltip at the right edge is retained; it does not cover the host-image or identity results.

Cloud Shell is the operator interface in this capture. az vm run-command runs the Docker and feature checks on the Windows Server VM; it does not make a Linux Cloud Shell session a workshop container client.
Portal checkpoint: each VM → Identity shows System assigned → On. Compare its principal with the exact conditional role assignment under the registry → Access control (IAM). Do not assign either guest Contributor, Owner, legacy AcrPush/AcrPull, or Catalog Lister to make a failing exercise pass.


4. Prove the healthy baseline
./Invoke-AcrWorkshop.ps1 -Stage Validate -StatePath $state -ExecuteValidation invokes actual guest commands and changes disposable guest test artifacts, so it requires explicit execution. It must establish all of the following:
Check | Required evidence |
Registry configuration | Premium, private-only data access, admin/anonymous/bypass disabled, intended ABAC mode |
DNS and TLS | Each guest resolves both endpoints to the recorded private addresses; registry TLS remains valid |
Identity | Fresh guest session uses that VM's system-assigned identity |
Publish | Publisher builds a nonce-bearing image and pushes it into workshop/app; manifest digest is recorded |
Consume | Reader pulls that digest, runs with process isolation and prints the exact expected marker |
Least privilege | Reader push rejected; publisher access outside workshop/* rejected; catalog listing rejected |
External access | Public access remains Disabled; fresh external data-plane request is rejected for its network location while ARM management remains available |
ABAC repository conditions are part of the assignment. In this registry mode, legacy AcrPush/AcrPull grants are not the correct repair. Catalog listing has its own role and intentionally remains denied. Repository ABAC roles and conditions.


The existing assignments are displayed in the condition code editor for inspection. The publisher's prefix condition and the reader's exact-name condition remain unchanged; viewing the editor does not require saving a new condition.
Inside the corresponding VM, the underlying flow is shown below. The runner supplies protected temporary Azure/Docker configuration directories and exact metadata; this is a guest-side reading example, not a command to paste into the external operator session:
# Guest-side illustration; never print or export these credential variables.
az login --identity --allow-no-subscriptions --output none
$tokenJson = az account get-access-token --resource https://containerregistry.azure.net --output json
if ($LASTEXITCODE -ne 0) { throw 'Managed identity token request failed.' }
$token = $tokenJson | ConvertFrom-Json
try {
$exchange = Invoke-RestMethod -Method Post -Uri "https://$loginServer/oauth2/exchange" `
-ContentType 'application/x-www-form-urlencoded' -Body @{
grant_type = 'access_token'; service = $loginServer
tenant = $token.tenant; access_token = $token.accessToken
}
$exchange.refresh_token | docker login $loginServer `
--username 00000000-0000-0000-0000-000000000000 --password-stdin
if ($LASTEXITCODE -ne 0) { throw 'Private registry identity login failed.' }
} finally {
$tokenJson = $null; $token = $null; $exchange = $null
}
docker pull "$loginServer/workshop/app@$manifestDigest"
docker run --rm --isolation process "$loginServer/workshop/app@$manifestDigest" cmd /S /C type C:\workshop\marker.txtThe runner obtains an Entra token with Azure CLI, exchanges it at the known private registry endpoint and sends the returned registry credential to Docker on stdin. The null-GUID username identifies this token flow; it does not enable the registry admin account. Using the known hostname avoids granting repository identities ARM registry-read permissions just for discovery. Temporary credential directories are removed after each operation. Managed-identity authentication, ACR OAuth exchange.
docker pull fetches the recorded content; docker run tests Windows compatibility and the marker. The actual runner also verifies ContainerUser, uses --network none during marker execution and checks every exit code. A digest identifies content more precisely than a mutable tag.
The shipped Nano Server manifest is pinned and checked for ordinary registry layers without external URLs. Some Windows images reference externally hosted or nondistributable layers, which can add a separate public download path. Do not casually replace this base image when evaluating the private data endpoint. ACR guidance on nondistributable layers.
The publisher also creates the real workshop/control image used in the authorization exercise. Its existence is recorded before the reader requests it. This avoids treating a missing repository as proof of a permission boundary.
Genuine Windows baseline evidence: the next two captures display actual command logs from the completed publisher and reader baseline. Azure CLI/Run Command reads those logs through Cloud Shell after execution; the screenshots do not claim a new build, push or pull at the moment of capture. The recorded UTC times, exit codes and matching manifest digest remain visible.

The image runs as ContainerUser; the build copies the synthetic marker. The successful push records the digest used by the reader.

The reader pulled the same digest, then executed with --isolation process --network none. The registry name, run prefix and terminal username are masked; the digest and synthetic marker content remain readable.

The existing control repository was published successfully. The same publisher was denied outside its workshop/ prefix; a successful allowed push does not imply registry-wide permissions.

This live request came from outside the workshop VNet. The response identifies a network denial, and the operator resolver did not return either private endpoint IP. This external check complements the Windows private transfer; it does not replace it.
5. Diagnose missing registry credentials
./Invoke-AcrWorkshop.ps1 -Stage Fault -Scenario Authentication -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authentication -StatePath $state -ExecuteThe pre-injection healthy control proves that the reader can authenticate with its managed identity. The fault request then uses an empty, run-owned Docker credential directory and deliberately supplies no registry credentials; it does not retest managed-identity authentication during the anonymous attempt. The registry and data endpoints still resolve correctly and TLS remains healthy. A fresh private pull must fail because the selected Docker configuration has no registry credentials. The fault validator passes only when that underlying pull fails for the expected reason and the DNS/TLS controls still pass.
Do not infer a broken network from a denied request. An anonymous /v2/ authentication challenge can be healthy protocol behavior. Read the Docker error together with the DNS/TLS controls and credential-directory state. Avoid printing tokens, credential JSON or signed download URLs.

The genuine completed Windows log preserves the order: healthy execution first, deliberately anonymous request later. UNAUTHORIZED and authentication required describe the expected failure. Its separate DNS/TLS and cache controls are recorded in the validation evidence; this frame does not show a repaired pull.
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario Authentication -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authentication -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario Authentication -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authentication -StatePath $state -ExecuteRepair clears the owned authentication-fault selection. The subsequent scenario validation creates a fresh protected credential directory and authenticates the reader normally. A new digest pull and marker execution must succeed. Repeated repair is safe and does not change roles, public access or networking. Keep -Scenario Authentication on these healthy validation commands so the journal records authentication recovery rather than only another baseline.

The genuine recovery log shows successful download of a new application digest and successful marker execution. Its digest differs from the anonymous request in the fault capture. These completed Windows results were read through Cloud Shell after execution.
6. Diagnose a repository authorization denial
./Invoke-AcrWorkshop.ps1 -Stage Fault -Scenario Authorization -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authorization -StatePath $state -ExecuteThe reader now requests the existing workshop/control repository. Its identity is authenticated and both endpoint paths remain healthy. The reader's condition permits only workshop/app, so the control image is denied. The fault is the requested repository reference, not a deleted identity or a failed deployment.
The correct repair restores the permitted application reference. It does not grant Writer, remove the condition or enable catalog listing. A valid identity can still be unauthorized for one resource.

Docker's generic message also mentions a nonexistent repository or login. That wording alone cannot identify the cause. The phase's publisher proof establishes that this exact control digest exists, while the authenticated identity and DNS/TLS controls remain healthy. Together with the reader's condition, those controls establish the intended authorization failure. This capture shows completed Windows command logs, not a repaired request.
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario Authorization -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authorization -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario Authorization -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authorization -StatePath $state -ExecuteExpected: the same reader can pull a fresh application digest and run its marker, while control-repository access and reader pushes remain denied.

After restoring the permitted app reference, the reader downloads a new digest and prints its recovery marker. This genuine completed Windows log demonstrates the allowed request succeeding; it does not grant access to workshop/control or alter the role condition.
7. Diagnose the client resolver fault
./Invoke-AcrWorkshop.ps1 -Stage Fault -Scenario DataEndpoint -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario DataEndpoint -StatePath $state -ExecuteThis exercise adds one clearly delimited, owned entry to the reader's Windows hosts file, overriding only the discovered regional image-data endpoint. It is a client resolver fault. Azure Private DNS records, the VNet link, zone group, endpoint approval and publisher are left healthy. Do not describe the screenshot as a broken Azure DNS zone.
The login hostname still resolves privately. Authentication/content discovery can work while a fresh layer download fails. Dedicated data endpoints serve layer downloads; uploads use the login server. Dedicated data endpoint flow.
The publisher builds a new nonce layer for this phase, and the reader must not already have it. A cached image or existing layer cannot prove that the broken endpoint is usable. The validator compares the reader's effective resolver result with a direct DNS-server lookup and the Azure NIC mapping, then checks a bounded failed pull.
The relevant reader guest diagnostics are shown below. The stage runner supplies $dataEndpoint from the recorded Azure metadata and executes them through Run Command:
# Effective Windows resolver result, including the owned hosts-file override.
[System.Net.Dns]::GetHostAddresses($dataEndpoint) | ForEach-Object IPAddressToString
# Ask Azure DNS directly and skip the local hosts file.
Resolve-DnsName -Name $dataEndpoint -Type A -Server 168.63.129.16 -DnsOnly -NoHostsFileThe first result should show the deliberate wrong destination while the fault is active. The second must still return the genuine private mapping. -DnsOnly selects the DNS protocol; -NoHostsFile explicitly excludes the hosts file. In this VNet, 168.63.129.16 provides the Azure platform DNS service used for that independent query. Resolve-DnsName parameters, Windows resolver behavior through .NET, Azure platform IP address and DNS.
Portal checkpoint: refresh the private endpoint DNS configuration and private DNS record sets. They must still show the original private mappings. Pair this capture with the reader's CLI evidence showing its local override, healthy login path and failed new-layer transfer.
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario DataEndpoint -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario DataEndpoint -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario DataEndpoint -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario DataEndpoint -StatePath $state -ExecuteRepair removes only the owned hosts-file block, clears the resolver cache and restarts the Windows Docker service. The subsequent scenario validation publishes a new digest and starts the fresh pull. Expected: the effective data-endpoint result matches the private-endpoint NIC again; that new digest pulls and prints the correct marker. Do not replace the entire hosts file, disable TLS verification or enable public access.
8. Confirm the final state, clean up twice and export
Run healthy validation after all three repairs. The original VM identities, conditional roles, public-access settings, DNS configuration and resource identities must still match the owned baseline. Review the final Terraform plan: no unexpected changes are acceptable.
# Preview cleanup before authorizing deletion.
./Invoke-AcrWorkshop.ps1 -Stage Cleanup -StatePath $state
./Invoke-AcrWorkshop.ps1 -Stage Cleanup -StatePath $state -Execute -ReviewedPlan
./Invoke-AcrWorkshop.ps1 -Stage Cleanup -StatePath $state -Execute -ReviewedPlan
./Invoke-AcrWorkshop.ps1 -Stage Export -StatePath $stateCleanup restores any recorded guest fault before destroying the exact Terraform-managed resources. It checks context, ownership, state and unexpected group members before deletion. A name prefix or tag alone is not deletion authority. Tags are plain-text metadata and do not automatically propagate from the group to every resource. Azure tag behavior.
Expected: the resource group and exact recorded resources are absent, no run-owned residual is found, and repeated cleanup creates nothing. Retain the private ledger until residual checks finish. Export only the sanitized report; inspect it again before sharing. Do not upload Terraform state/plans, VM passwords, raw CLI output, tokens, Docker configuration directories or unredacted screenshots.
The export summarizes 21 named checks, including healthy state immediately before each fault, injection, expected failure, scenario recovery, repeated repair, final drift and cleanup/repeated cleanup. Complete is true only when every required check is recorded. During an active-fault validation, ContractPassed = true means the expected failure was correctly observed; the nested guest operation still reports Passed = false and fault detection. These fields do not mean the broken pull succeeded.
Troubleshooting and safe stops
Symptom | Inspect next | Avoid |
Run Command fails or returns no completed probe | VM Agent status, guest stage log, pending reboot and operation journal | Calling the registry failed without a registry request |
Hostname resolves publicly from a VM | VNet link, zone group, endpoint mappings, local resolver/hosts file | Adding public allow rules |
/v2/ returns an authentication challenge | TLS validity and whether credentials were actually supplied | Treating every HTTP 401 as a network failure |
Login works but fresh pull fails during layers | Data-endpoint effective resolution, TCP/TLS path and new-layer proof | Trusting a cached image or successful login |
Reader app pull is denied | Actual VM principal, ABAC condition, exact repository, fresh credentials and bounded propagation retry | Granting Contributor or removing conditions |
Pull succeeds but docker run fails | Image OS/version, host build, process isolation and marker command | Rebuilding the private endpoint |
Cost/quota/policy preflight fails | The exact reported prerequisite | Silently changing SKU, region, image or budget |
Azure Edition creation reports a patch-mode requirement | Pinned AutomaticByPlatform, IfRequired and disabled Hotpatch settings | Assuming Hotpatch disabled implies AutomaticByOS |
Apply partially fails with owned resources already created | Original journal, exact Terraform/live IDs and reviewed Plan -Resume | Deleting state, adopting objects by prefix or blindly replaying deployment |
Pending operation after interruption | Exact saved target, current Azure state and original process status | Deleting locks or editing the ledger to force success |
A native-process exit code of 124 means the controller reached its local time limit: Azure CLI is bounded to 20 minutes and Terraform to 25 minutes. It terminates only the owned local process tree, retains bounded diagnostic output and preserves the pending operation in the private journal. This does not cancel an Azure operation already accepted by the service. Check the recorded operation and actual resource state before following that stage's recovery path; do not launch a competing apply or clear the pending record to force a retry. Guest processes and their descendants also run with time/output bounds.
A timeout alone does not identify a layer. A failed negative test is useful only when its positive controls remain healthy. The ACR access troubleshooting guide provides additional diagnostic paths.
Challenge and learning pack
Try CHALLENGE.md (download: CHALLENGE.md) before HINTS.md (download: HINTS.md) or SOLUTION.md (download: SOLUTION.md). Review the eight applied questions in KNOWLEDGE-CHECK.md (download: KNOWLEDGE-CHECK.md). The source map (download: docs/source-map.md) links each major design choice to primary documentation; the screenshot guide (download: docs/screenshot-guide.md) describes the evidence to collect and redact.
Complete the practical work and cleanup as well as the questions. A quiz score alone is not proof of a working private registry. This workshop is independent training, not an official Microsoft lab or certification endorsement.
Challenge — Prove a private Windows image supply path
Start only after the healthy baseline in README.md (download: README.md) passes. Use this disposable run's private ledger. Keep the six-hour cleanup deadline and EUR20 ceiling, including the EUR5 cleanup reserve.
Your assignment
You support a Windows build team. One engineer says that a successful registry login proves the private image path works. Another wants to grant Writer to every client because a reader cannot pull a control image. Produce evidence that separates their assumptions.
Prove both Windows clients resolve the registry and image-data endpoint to their recorded private addresses. Show the actual VM identities and repository conditions without exposing tokens or tenant/subscription identifiers.
Publish a new application image. Record its manifest digest and marker, then prove the reader fetched and ran that exact content using process isolation.
Show that reader push, reader access to the existing control repository, publisher access outside its scope and catalog listing remain denied.
Run each controlled fault separately: missing credentials, wrong existing repository, and reader-only data-endpoint resolver override. For each, capture healthy state → fault → expected underlying failure → repair → successful fresh pull.
Explain why the Azure DNS records remain healthy during the client resolver fault. Demonstrate the difference between a direct DNS lookup and the reader's effective application resolution.
Repeat each repair safely. Review the final Terraform plan for unexpected drift, clean up, repeat cleanup and export only sanitized evidence.
Evidence acceptance
Claim | Minimum evidence |
Private access | Approved endpoint plus guest FQDN-to-private-IP comparisons and real successful image transfer |
Correct content | Publisher manifest digest, reader digest and unique marker agree |
Correct authorization | The control image is proven to exist; the authenticated reader is denied; allowed app pull still works |
Cache-safe resolver failure | New uncached marker layer fails to download only on the reader; login and Azure DNS controls remain healthy |
Narrow repair | Only the owned credential selection, repository reference or hosts-file block is restored |
Completion | All healthy checks pass; no unexpected infrastructure drift; exact resources absent after repeat cleanup |
Do not manufacture a pass by changing ACR public access, bypass settings, TLS verification or repository permissions. A failure of Run Command, the Docker service or Windows image compatibility is a separate prerequisite failure and cannot substitute for one of the three intended faults.
Use HINTS.md (download: HINTS.md) progressively. Compare your reasoning with SOLUTION.md (download: SOLUTION.md) after finishing.
Knowledge check — Private ACR on Windows
Choose one best answer for each scenario before opening the answer. Use the linked task to reproduce the behavior. These eight original questions test this workshop's reasoning; they are not Microsoft exam questions.
Practical completion includes successful baseline, three observed fault/repair cycles and verified cleanup. With eight questions: 0–5 correct means repeat the mapped tasks; 6 correct means targeted review; 7–8 correct means move forward once the practical work is complete.
1. Login succeeds, but a new layer cannot download
The reader resolves the registry login hostname to the expected private IP and registry authentication succeeds. A newly built image's uncached marker layer fails to download. Azure Private DNS still has the correct records. What is the best next check?
A. Grant the reader Repository Writer because Reader cannot fetch layers.
B. Compare the reader's effective resolution and connectivity for the regional data endpoint with its private-endpoint NIC mapping.
C. Enable the registry admin account to remove identity as a variable.
D. Treat login success as proof of the full path and retry the cached baseline image.
Answer and explanation
B. ACR uses a distinct data endpoint for layer downloads. A healthy login endpoint does not prove that path works. Check effective Windows resolution, including the hosts file, before changing Azure configuration.
A adds unnecessary write permission; Reader can pull its permitted content.
C introduces shared credentials without repairing a data-endpoint path.
D can reuse cached layers and conceal the failure.
Remediation: README, client resolver exercise (download: README.md#7-diagnose-the-client-resolver-fault). Primary source: Dedicated data endpoints — download flow.
2. A valid reader identity is denied
The publisher proves workshop/control exists. The reader can pull workshop/app, but its authenticated pull of workshop/control is denied. The reader condition is workshop/app. Which repair fits this exercise?
A. Replace the reader's condition with workshop/*.
B. Add legacy AcrPull alongside its current role.
C. Assign Catalog Lister so Docker can find the control image.
D. Restore the intended image reference to workshop/app and keep the current permission boundary.
Answer and explanation
D. The fault is an incorrect repository reference. The successful application pull proves the permitted path. A denied control pull is the intended boundary.
A changes the boundary to accommodate the wrong request.
B is the legacy registry permission model and is not the fix for this ABAC-enabled registry.
C permits catalog discovery, not a pull outside the repository condition.
Remediation: README, authorization exercise (download: README.md#6-diagnose-a-repository-authorization-denial). Primary source: ABAC repository permissions and role model.
3. Docker and Azure CLI disagree about credentials
Azure CLI has a VM managed-identity session. Docker is pointed at a new empty credential directory, and its fresh application pull is rejected. DNS/TLS controls pass. Which action repairs the intended fault?
A. Authenticate the VM identity to the registry using the intended Docker configuration, then repeat the fresh pull.
B. Copy the publisher's Docker credential file to the reader.
C. Recreate the private endpoint to refresh its approval state.
D. Grant the VM Contributor so every Docker configuration inherits its session.
Answer and explanation
A. Azure identity authentication and the registry credentials consumed by Docker are separate steps. Populate the owned reader configuration using its own identity and verify the new request.
B breaks identity separation and shares credentials unnecessarily.
C changes a healthy network resource.
D grants broad management access and does not make an empty Docker configuration contain credentials.
Remediation: README, authentication exercise (download: README.md#5-diagnose-missing-registry-credentials). Primary source: System-assigned managed identity and registry authentication.
4. Pull works but process-isolated execution fails
The manifest digest and downloaded image are correct. On a Windows host, docker run --isolation process reports an operating-system compatibility problem. Which check addresses that evidence most directly?
A. Change the registry's public network setting.
B. Assign the reader the Container Registry Repository Writer role.
C. Compare the Windows host build with the image's Windows release/OS version and the selected isolation mode.
D. Replace the private DNS zone with a public zone.
Answer and explanation
C. Transfer completed, so the next failing boundary is local execution. This workshop matches Windows Server 2025 with Nano Server ltsc2025 for process isolation.
A cannot correct a local host/image mismatch after successful download.
B does not change container compatibility and adds unnecessary permission.
D changes a working name-resolution design.
Remediation: README, Windows bootstrap (download: README.md#3-bootstrap-windows-in-resumable-stages). Primary source: Windows container version compatibility.
5. Which result actually proves layer transfer?
The reader pulled the baseline image before the data-endpoint fault. Which test gives the strongest evidence that the fault has been repaired?
A. Run the existing cached container again and inspect its marker.
B. Publish a new nonce-bearing layer, pull its recorded manifest digest on the reader and verify the new marker.
C. List images from the reader's local Docker cache.
D. Check only that the ACR private endpoint is Approved.
Answer and explanation
B. A new layer forces a new transfer; the digest and marker verify that the intended content arrived. A digest reference alone does not prevent reuse of already-cached layers.
A proves cached execution, not a repaired download path.
C inspects local objects without exercising the registry.
D proves connection configuration, not successful layer delivery.
Remediation: README, baseline evidence (download: README.md#4-prove-the-healthy-baseline). Primary source: Docker pull by digest.
6. Portal cannot list repositories from the laptop
The reader VM successfully pulls the app over private addresses. On an external operator laptop, the Azure Portal opens the registry Overview page but cannot list repositories. Public registry access is disabled. What is the best interpretation?
A. The registry must be unavailable because all Portal pages use the same network path.
B. The private endpoint needs a second connection for the operator's browser.
C. Public access should temporarily be enabled to make the workshop valid.
D. Management-plane access can work while the laptop lacks a private data-plane path; keep public access disabled and inspect the intended private client.
Answer and explanation
D. The control plane and registry data plane have different access requirements. Use the VM's successful transfer as path evidence, and label the laptop's location accurately.
A conflates resource management with repository requests.
B does not create private network connectivity from an arbitrary external laptop.
C removes the very restriction the workshop is intended to prove.
Remediation: Screenshot guide, network vantage point (download: docs/screenshot-guide.md#network-vantage-point). Primary source: ACR Private Link limitations.
7. The run stops during Windows bootstrap
Your terminal closes after the Containers feature installation starts. The private journal records a pending operation. What is the safest next step?
A. Delete the state and redeploy a second group with the same names.
B. Clear the lock immediately and start a separate manual installation.
C. Confirm the original process has ended, reconcile the exact recorded operation and VM state, then resume the supported bootstrap stage.
D. Open public RDP and disable the VM Agent so guest installation can continue independently.
Answer and explanation
C. Feature installation, reboot and agent readiness are separate transitions. Resume only after the pending outcome is understood. A repeated stage must not launch competing work.
A discards ownership/recovery information and can leave billable resources.
B can race an installer that is still running.
D changes the management boundary and undermines the controlled workflow.
Remediation: README, resumable bootstrap (download: README.md#3-bootstrap-windows-in-resumable-stages). Primary sources: Prepare Windows containers, Action Run Command restrictions.
8. The package excludes Terraform state
A learner suggests publishing the saved plan and state because administrator outputs are marked sensitive. Which answer is best?
A. Keep plans and state private; output redaction does not encrypt their stored values. Publish only reviewed sanitized evidence.
B. Commit state after changing its filename so it is not recognized as Terraform data.
C. Publish the binary plan because binary files cannot contain usable secrets.
D. Publish state once the VMs are deleted because deletion removes every stored credential from the file.
Answer and explanation
A. Treat state and saved plans as sensitive artifacts. Deletion does not rewrite every historical private artifact, and renaming a file does not sanitize its content.
B changes a filename, not the data.
C confuses a binary encoding with encryption or redaction.
D ignores retained state versions, logs and plan files.
Remediation: README, private state (download: README.md#1-prerequisites-cost-and-private-state). Primary source: HashiCorp sensitive data in Terraform.
Progressive hints — open one at a time
Hints — Identify the failing layer
Use one hint at a time. The full command sequence is in README.md (download: README.md), and SOLUTION.md (download: SOLUTION.md) explains the reasoning. Stop before the five-hour exercise deadline and keep enough time for cleanup.
Hint 1 — Record a baseline before changing anything
What would distinguish a failed pull caused by a new fault from a client that never worked? Require successful DNS/TLS, managed-identity authentication, a fresh digest pull and marker execution first. Keep the expected denials too: least privilege is part of health.
Hint 2 — Name the endpoints
Use az acr show-endpoints and the private-endpoint NIC. Do not assume that the login hostname and regional data hostname share one private IP. A green private endpoint connection state is configuration evidence, not a packet or image-transfer test.
Hint 3 — Inspect the credential boundary
Is Docker reading the credential directory populated by the last login? An empty selected directory can make the next Docker request anonymous even when Azure CLI itself has an identity session. Never print credential JSON or token-bearing command output to prove this.
Hint 4 — Check the exact repository
The reader's condition is for workshop/app. The publisher can populate another repository within workshop/*, but that does not expand the reader's condition. Establish that workshop/control exists before interpreting its denial. A catalog-listing failure is expected because no Catalog Lister role is granted.
Hint 5 — Make the client download a new layer
Use the phase's new nonce-bearing image and its recorded digest. A digest reference by itself does not prevent layer reuse. If the layer is already cached, a pull can appear successful without touching the broken data endpoint.
Hint 6 — Compare effective resolution with Azure DNS
During the resolver fault, the reader has an owned hosts-file entry for the data hostname. Compare the operating system's effective result with a direct DNS-server lookup and the Azure NIC mapping. Keep the login hostname unchanged. Explain which result Docker uses and why a healthy zone can coexist with an unhealthy client.
Hint 7 — Repair the smallest owned change
Restore the reader's credential selection, choose the permitted app reference, or remove the owned hosts-file block. Preserve unrelated hosts entries. Do not remove ABAC conditions, add Catalog Lister, enable public access or turn off certificate validation.
Hint 8 — Recovery needs new evidence
Repeat healthy validation after repair with a fresh marker layer. Then repeat the repair itself: an already-repaired state should be safe. Cleanup requires the same discipline—preview exact destruction, delete only owned resources and verify absence twice.
Worked solution — open after completing the challenge
Worked solution — Separate authentication, authorization and image transfer
This solution explains the expected evidence. Consult VALIDATION.md (download: VALIDATION.md) for what the release actually executed. It does not replace the private journal, and illustrative output must never be labelled as a live capture.
1. Establish what healthy means
Run Preflight and Plan, inspect the exact saved Terraform plan, then deploy with -Execute -AcknowledgeCost -ReviewedPlan. Bootstrap both Windows VMs with the staged runner. Containers feature installation, restart and runtime verification are separate transitions; a completed ARM VM deployment does not imply a ready Docker service.
The Azure Edition configuration keeps platform-orchestrated patching enabled and Hotpatch disabled. That platform setting does not replace the Containers-feature reboot. If apply stops partway through, preserve this run's state and use the documented Plan -Resume/reviewed Deploy -Resume path only after reconciling the cause. A resume plan may create missing owned objects, not update or replace existing ones.
Run healthy Validate. Record both endpoint mappings, registry security configuration, each guest identity and repository condition, the pushed manifest digest and the reader's successful marker output. Reader push, outside-prefix publisher access, control-repository reader access and catalog listing remain denied. A healthy least-privilege baseline has successful and unsuccessful operations.
The publisher builds a Nano Server ltsc2025 image matching the Windows Server 2025 process-isolation host. It adds a unique phase marker. The reader pulls the recorded manifest digest and runs the image to print that marker. The image ID, mutable tag and manifest digest are different concepts; compare the registry content digest rather than assuming every SHA-256 field means the same object.
2. Missing credentials: inspect authentication
./Invoke-AcrWorkshop.ps1 -Stage Fault -Scenario Authentication -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authentication -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario Authentication -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authentication -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario Authentication -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authentication -StatePath $state -ExecuteThe fault selects an empty Docker credential directory on the reader. DNS and TLS must remain healthy. Azure identity availability and Docker registry authentication are separate checks: merely having a CLI session does not populate every possible Docker configuration directory.
The required negative observation is a fresh pull rejected without the selected registry credentials. The fault-contract validation should recognize that expected denial; it must not treat an unrelated timeout, unavailable Docker daemon or failed Run Command as equivalent evidence. Repair clears the fault selection; the next scenario validation authenticates the reader in a new protected configuration, then fresh app content pulls and runs. No Azure role or network rule changes. Supplying the same scenario after repair records its recovery check in the evidence journal.
3. Wrong repository: inspect authorization
./Invoke-AcrWorkshop.ps1 -Stage Fault -Scenario Authorization -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authorization -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario Authorization -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authorization -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario Authorization -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario Authorization -StatePath $state -ExecuteThe publisher's Writer condition covers workshop/*, so it can establish workshop/control. The reader's Reader condition covers only workshop/app, so the authenticated reader must be denied access to control content. Its successful app pull is the positive control. Check the exact repository string and principal before changing permissions.
Repair returns to the app reference. Granting broader Reader access would hide the deliberate wrong-reference fault and weaken the exercise. Granting Writer is worse because it adds unneeded write access. Catalog listing remains denied: repository-specific access does not require discovery of the whole catalog.
4. Data endpoint: inspect effective client resolution
./Invoke-AcrWorkshop.ps1 -Stage Fault -Scenario DataEndpoint -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario DataEndpoint -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario DataEndpoint -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario DataEndpoint -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Repair -Scenario DataEndpoint -StatePath $state -Execute
./Invoke-AcrWorkshop.ps1 -Stage Validate -Scenario DataEndpoint -StatePath $state -ExecuteThe reader's owned hosts-file block changes the effective result for the regional data hostname. It does not edit Azure Private DNS. Capture a direct DNS-server result and private-endpoint configuration that still agree, then the reader's conflicting effective resolution. The unchanged publisher provides an additional control.
Successful login is compatible with failed image download: authentication/manifest requests use the login endpoint, while layer downloads use the data endpoint. The publisher pushes a new marker layer for the phase; the reader cannot use a cached copy. Require the fresh pull to fail at the affected layer path within a bounded period.
Remove only the delimited owned block, flush relevant caches and use a fresh pull session. Verify the data hostname now resolves to the recorded private address and a new digest downloads and prints its marker. Repeated repair preserves unrelated hosts entries and all Azure networking/role settings.
5. Finish with configuration and absence evidence
Healthy validation after all repairs must still observe the original resource identities, permissions and private-only settings. The final Terraform plan must contain no unexpected drift. Then preview Cleanup, inspect the exact deletion targets and execute it with the reviewed-plan flag. Repeat Cleanup and export sanitized evidence.
Successful deletion is not the same as asking Azure to delete. Query the exact recorded resources/group until absence is established, inspect run-owned residuals and retain the private ledger until checks finish. If an unexpected member or ownership mismatch blocks deletion, stop and reconcile it; do not use a broad prefix search or group-wide shortcut to manufacture cleanup success.
Why the design choices matter
Choice | Reason |
Two Windows VMs | Separates publisher and reader identity/credential boundaries and makes Windows runtime behavior observable |
System-assigned identities | Removes shared registry passwords and binds the guest identity lifecycle to its VM |
Conditional repository roles | Demonstrates the exact allowed operation/resource, rather than broad registry access |
Digest plus unique marker layer | Proves content identity and requires fresh layer transfer |
Client-only resolver override | Creates a narrow, reversible failure while Azure DNS remains a reliable control |
Saved plan and private ledger | Makes the intended mutation reviewable and prevents cleanup by name guess |
Repeated repair and cleanup | Demonstrates useful recovery behavior after the first successful operation |
See the primary documentation in docs/source-map.md (download: docs/source-map.md) for role semantics, endpoint behavior, Windows compatibility and Terraform state security.
Validation record — Private ACR Windows workshop
Live acceptance passed for the documented release scope. All 21 required live checks passed and the Azure resources have been removed. On 18 October 2026 the owner authorized publication with the 19 genuine captures and retained text/JSON evidence described below. Final packaging and hosted-publication results are recorded separately in the release receipt. A passing mock or configuration screenshot does not establish a passing live exercise.
Tested configuration
Item | Recorded configuration |
Azure execution / release review | 17 October 2026 / 18 October 2026 |
Azure region | West Europe |
Windows clients | Two Standard_D2s_v5 VMs; Windows Server 2025 Datacenter Azure Edition |
Marketplace image version | 26100.33650.261013 |
Container base | Nano Server ltsc2025; locked manifest in guest/nanoserver-manifest.lock.json |
Observed Windows/container build | 10.0.26100.33650; process isolation |
Observed runtime / guest CLI | Docker CE/Moby 29.9.1; Azure CLI 2.91.0 |
Infrastructure tools | Terraform 1.16.4; AzureRM 5.8.0; committed provider checksum lock |
Operator / guest shell | Tested operator PowerShell 7.6.7 (minimum 7.5) / Windows PowerShell 5.1 through Azure Run Command |
Preflight / immutable expiry UTC | 2026-10-17T15:58:43Z / 2026-10-17T21:58:43Z |
Conservative six-hour exercise estimate | EUR8.06, including Windows licensing, contingency and traffic/transaction allowance |
Operational budget | EUR20 total; EUR5 reserved for cleanup |
The retail estimate is not an actual invoice or an Azure spending cap. Private identifiers, credentials, plans, Terraform state and raw logs are excluded from the public package.
Local checks
The local checks below passed during the 17 October review. The release procedure repeats them against the final source, records its fingerprint in a separate quality report and refuses to package changed or failing sources. The archive and quality reports remain outside the source tree so that their checksums can identify the exact released bytes.
Check | Observed result |
Terraform | 1.16.4 formatting, locked initialization and validation passed; eight mocked runs passed |
PowerShell analysis | PSScriptAnalyzer 1.25.0; eight script files; zero warnings/errors |
Lifecycle tests | Pester 5.7.1; 166 passed (116 controller + 50 guest), zero failed/skipped/not-run |
Guest locks | Three installer/runtime/CLI checksum pins synchronized; manifest digest checked |
Documentation | Eleven Markdown files and relative references checked; eight questions with four choices and explanations |
Targeted secret scan | Six credential patterns and exact private run identifiers checked across 35 text files; no findings |
Gitleaks is not installed and was not claimed as executed. Binary screenshots have a separate visual privacy review. Final source fingerprint and clean-package checks are enforced by the release scripts.
The same 50 guest tests also passed under Windows PowerShell 5.1.26100.9711 at 2026-10-17T17:50:28Z, with zero failures/skips and an unchanged dispatcher SHA-256 of 18b5779c7431f941fc3bb327e0f80121370a0dc6129adb1285ab58fa8e43516d.
All 21 documented PowerShell code blocks parse successfully under the tested operator shell.
The Terraform runs use a mocked provider; they check security settings, private Windows clients, repository conditions and rejected unsafe inputs. They are configuration tests, not Azure deployment evidence. The publication preparation also has 44 passing structural and release-gate regressions, including integer table dimensions required by the Blog API and hidden source-only release comments. All 27 unique external HTTPS references were reachable; three stale section fragments were corrected.
Observed Azure acceptance
The authoritative public summary is the sanitized evidence export (download: docs/evidence/sanitized-evidence.json). Acceptance requires all 21 named checks, including each healthy control, injected fault, expected failure, recovery, repeated repair, no-drift result and both cleanup audits. Extra journal entries do not replace missing required checks.
Group | Current observed result |
Baseline | Passed: private DNS/TLS, fresh publisher push, uncached reader digest pull, ContainerUser, process isolation and matching marker |
Least privilege | Passed: reader push, publisher outside-prefix access and catalog access denied |
External public boundary | Passed: explicit HTTP 403 network-location denial from outside the private endpoint |
Authentication | Passed: fresh healthy control, empty-credentials denial, two safe repair calls and fresh authenticated recovery |
Authorization | Passed: fresh healthy control, denied existing control repository, two safe repair calls and fresh allowed-repository recovery |
Reader data-endpoint resolver | Passed: fresh healthy control, owned client-only override, exact expected failure, two safe repair calls and fresh digest/marker recovery |
Final Terraform drift | Passed after the final healthy scenario: detailed exit code zero, no unexpected changes |
Cleanup and repeated cleanup | Passed at 2026-10-17T18:09:19Z and 18:09:26Z; zero residuals |
For a deliberate fault, the wrapper must record ContractPassed = true while the guest records the intended failed operation (Passed = false, FaultDetected = true, ExpectedValidationFailure = true). DNS/TLS and cache controls must match the scenario. An unrelated timeout, stopped engine or VM Agent failure is not acceptable fault evidence.
Engineering verification and limits
Windows Azure Edition uses AutomaticByPlatform, IfRequired and disabled Hotpatch. The feature-installation restart is a separate controlled bootstrap stage. A rejected initial patch-mode request was corrected and the recorded partial apply was resumed using a reviewed create-only plan; this was observed live.
Context mismatch, ownership refusal, partial-operation handling, bounded native processes, credential isolation, cache handling, interrupted bootstrap and repeated repair are covered by meaningful local lifecycle tests. Destructive interruption of a working Azure deployment was not performed.
Runtime downloads and the compatible Nano Server manifest are locked by digest/checksum. The locked base has ordinary layers without external image-layer URLs, so the private-download experiment is not satisfied by an unrelated public foreign-layer path.
Guest identities exchange an Entra token at the known private registry endpoint and send the resulting Docker credential on stdin. The flow does not require a broad ARM registry-read grant.
Endpoint discovery uses exact owned NIC mappings. An empty customDnsConfigs field alone is not treated as proof of broken DNS.
The extension reconciler accepts only exact, recorded Terraform Windows parents and canonical Microsoft Run Command or patch-extension children. Patch children additionally require recorded and observed Windows AutomaticByPlatform settings. The patch-extension shape is mock-verified only; unknown instance names remain a deliberate stop for review.
The resolver exercise changes one owned Windows hosts-file block. Direct queries to Azure DNS bypass that block. The observed fault transcript (download: docs/evidence/data-resolver-diagnostic.txt), fault provenance (download: docs/evidence/diagnostic-provenance.json), observed recovery transcript (download: docs/evidence/data-recovery-diagnostic.txt) and recovery provenance (download: docs/evidence/recovery-provenance.json) preserve the real Windows comparison, failed pull and fresh successful pull/run. Identifiers are redacted. These are text records, not screenshots, and do not claim to reproduce an Azure Private DNS service outage.
This is a short-lived, single-region training run. It does not establish production capacity, availability, behavior in every tenant/policy, future artifact compatibility or actual billing totals. Bootstrap egress remains enabled through NAT.
Evidence, package and publication
The sanitized export (download: docs/evidence/sanitized-evidence.json) reports Complete = true, with all 21 required checks passed. A separate read-only Azure CLI confirmation (download: docs/evidence/cleanup-confirmation.json) at 2026-10-17T18:11:07Z found the group absent, zero run-tagged resources and zero role assignments under its exact scope. Cleanup finished about two hours and eleven minutes after preflight, before the immutable six-hour deadline.
This release contains 19 genuine captures: eight Portal and eleven CLI. Their provenance (download: docs/screenshots/provenance.json) records source, phase, exact hash and privacy review. CLI captions distinguish live Windows execution logs from their later display through PowerShell Cloud Shell. The final resolver fault/recovery and Portal/CLI cleanup screenshots are omitted: a saved browser permission blocked further Cloud Shell access, including after an explicit approval attempt. The owner subsequently authorized publication on 18 October 2026 with these four captures omitted. The actual resolver transcripts and cleanup audit above remain retained evidence and are explicitly labelled as text/JSON, not screenshots. The illustrated cover and architecture diagram are explanatory assets, not Azure evidence.
The final ZIP must be created from the passing source fingerprint, extracted into a clean directory, and checked against its SHA256SUMS manifest and relative links. It must exclude private runtime material. Its separate SHA-256 receipt identifies the archive bytes; a checksum embedded in the same ZIP cannot identify its own archive.
The article, public download and Workshopz card are verified after the final ZIP is built. Their public URLs, anonymous download hash and rendered desktop/mobile checks belong in the separate release receipt; this source record does not claim those later publication steps have already succeeded.
Comments