top of page
8 hours ago
33 min read

Download SHA-256: 69c761fd6b2debdb03aa6200f6cc7395964f2d9b588935c1a20bc93d4efe60b5


azure infrastructure workshop


Illustrated cover: Private Azure Container Registry — Windows Hands-on Workshop, with Windows publisher and reader servers linked to a secured registry.

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


Architecture: two Windows clients with separate identities reach ACR through private registry and data endpoints. NAT provides bootstrap egress; Azure Run Command provides management.

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 $state

Review 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 $state

Read 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.


Genuine Azure Portal overview showing Premium SKU, West Europe and successful provisioning; private account and resource identifiers are masked.

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


Genuine Azure Portal Networking page with public network access Disabled and trusted-service bypass unchecked.

Genuine Azure Portal private access page showing an Approved, Succeeded connection and dedicated data endpoints enabled.

Genuine private endpoint DNS configuration showing the data endpoint at 10.170.0.68 and login endpoint at 10.170.0.69.

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 json

These commands inspect Azure configuration. A successful ARM query does not prove data-plane access from the operator workstation.


Genuine Azure CLI output confirming Premium, disabled public, admin and anonymous access, no bypass, ABAC repository permissions and enabled dedicated data endpoints.

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 -ReviewedPlan

Preflight 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 -Execute

Bootstrap 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.


Genuine Azure CLI output showing the publisher Windows Server 2025 Azure Edition image, pinned version 26100.33650.261013, Standard D2s v5 size and system-assigned identity.

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


Genuine Azure Run Command output showing Docker server OS Windows, version 29.9.1, process isolation and the Windows Containers feature Installed.

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.


Genuine publisher VM Identity page with its system-assigned identity On and principal identifier masked.

Genuine reader VM Identity page with its separate system-assigned identity On and principal identifier masked.

4. Prove the healthy baseline


./Invoke-AcrWorkshop.ps1 -Stage Validate -StatePath $state -Execute

Validation 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.


Genuine Azure Portal condition code showing Repository Writer reads and writes restricted by a workshop slash repository prefix.

Genuine Azure Portal condition code showing Repository Reader reads restricted to exactly workshop/app.

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.txt

The 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.


Recorded Windows publisher output showing ContainerUser, marker COPY, a successful image build and private workshop/app push with exit code zero and a manifest digest.

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


Recorded Windows reader output showing newly downloaded layers from a digest pull, exit code zero, then process isolation with network none and the expected synthetic marker.

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.


Recorded publisher output showing a successful workshop/control push followed by a rejected outside/proof push with exit code one and requested access denied.

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.


Live external operator check showing public access Disabled, HTTP status 403, explicit network denial True and private endpoint resolution False.

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 -Execute

The 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.


Recorded Windows reader log showing pre-injection healthy marker execution with exit code zero, followed by a different digest pull denied with exit code one and authentication required.

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 -Execute

Repair 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.


Recorded Windows authentication-recovery log showing no active fault, new digest download, exit code zero and successful process-isolated marker execution.

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 -Execute

The 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.


Recorded Windows reader log showing healthy app marker execution, then workshop/control digest pull rejected with exit code one and requested access denied.

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 -Execute

Expected: the same reader can pull a fresh application digest and run its marker, while control-repository access and reader pushes remain denied.


Recorded Windows authorization-recovery log showing no active fault, a new workshop/app digest downloaded with exit code zero and successful isolated marker execution.

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 -Execute

This 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 -NoHostsFile

The 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 -Execute

Repair 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 $state

Cleanup 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.


  1. 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.

  2. Publish a new application image. Record its manifest digest and marker, then prove the reader fetched and ran that exact content using process isolation.

  3. Show that reader push, reader access to the existing control repository, publisher access outside its scope and catalog listing remain denied.

  4. 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.

  5. 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.

  6. 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.


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.


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.


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.


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.


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.


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.


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.


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.



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 — 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 -Execute

The 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 -Execute

The 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 -Execute

The 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


bottom of page