From 01647da315de42697d5f9dc2e6cb73225e73b7a7 Mon Sep 17 00:00:00 2001 From: Simon Gallagher Date: Tue, 10 Nov 2026 20:40:41 +0000 Subject: [PATCH 1/2] Add experimental West Europe confidential Container Apps deployment Preserve UAE North defaults, route explicit region selection through deployment and verification, and document the verified West Europe DC4 experiment and cleanup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- azure-container-apps-sample/README.md | 11 ++- .../Deploy-ContainerApp.ps1 | 18 ++-- .../container-apps-acc/DeploymentHelpers.ps1 | 9 +- .../container-apps-acc/README.md | 84 +++++++++++++++++-- .../container-apps-acc/Test-ContainerApp.ps1 | 3 +- .../container-apps-acc/main.json | 4 +- .../container-apps-acc/tests/Test-Sample.ps1 | 34 ++++++-- 7 files changed, 135 insertions(+), 28 deletions(-) diff --git a/azure-container-apps-sample/README.md b/azure-container-apps-sample/README.md index 6418da4..de8d600 100644 --- a/azure-container-apps-sample/README.md +++ b/azure-container-apps-sample/README.md @@ -3,12 +3,14 @@ [Repository overview](../README.md) Two implemented examples demonstrate explicit binding to a **confidential DC4 -workload profile in UAE North**. Choose a guide based on the endpoint and +workload profile in UAE North**. The HTML quickstart also offers an explicitly +experimental West Europe option; the Functions workflow remains UAE +North-specific. Choose a guide based on the endpoint and prerequisites you need: | Sample | Status | | --- | --- | -| [Confidential hello world](container-apps-acc/README.md) | ARM template and PowerShell deployment using a DC4 confidential workload profile in UAE North | +| [Confidential hello world](container-apps-acc/README.md) | ARM template and PowerShell deployment using DC4 in UAE North by default, with an explicit experimental West Europe option | | [Functions on Azure Container Apps](functions-on-azure-container-apps/README.md) | Tested native Functions hello world on confidential DC4 in UAE North: Node.js 24, fresh ACR builds, managed identity, private Blob host storage, deployment and verification | Start with **Confidential hello world**. It uses Microsoft's public quickstart @@ -59,13 +61,14 @@ $subscriptionId = '' $resourceGroup = '' $environmentName = 'cae-acc-hello' $appName = 'ca-acc-hello' +$location = 'uaenorth' # Use 'westeurope' only for the HTML quickstart experiment. Connect-AzAccount -Subscription $subscriptionId . .\azure-container-apps-sample\container-apps-acc\DeploymentHelpers.ps1 $resourceBase = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.App" $app = Get-ArmJson -Path "$resourceBase/containerApps/${appName}?api-version=2025-07-01" $environment = Get-ArmJson -Path "$resourceBase/managedEnvironments/${environmentName}?api-version=2025-07-01" -$appUrl = Assert-ConfidentialContainerApp -App $app -Environment $environment +$appUrl = Assert-ConfidentialContainerApp -App $app -Environment $environment -Location $location $assignedProfile = @($environment.properties.workloadProfiles | Where-Object name -eq $app.properties.workloadProfileName) $revisionName = $app.properties.latestReadyRevisionName @@ -96,7 +99,7 @@ if ($revision.properties.active -ne $true -or ``` The shared guard **throws** unless the app references the expected environment, -both resources report `Succeeded` in UAE North, the assigned profile is +both resources report `Succeeded` in the explicitly expected region, the assigned profile is `confidential` with type `DC4` and minimum/maximum one node, and the latest revision is ready. It also checks the examples' HTTPS ingress settings. The additional revision read requires an active revision, at least one replica, diff --git a/azure-container-apps-sample/container-apps-acc/Deploy-ContainerApp.ps1 b/azure-container-apps-sample/container-apps-acc/Deploy-ContainerApp.ps1 index b0a239b..f9c1643 100644 --- a/azure-container-apps-sample/container-apps-acc/Deploy-ContainerApp.ps1 +++ b/azure-container-apps-sample/container-apps-acc/Deploy-ContainerApp.ps1 @@ -3,6 +3,7 @@ [CmdletBinding()] param( [Parameter(Mandatory)][guid]$SubscriptionId, + [ValidateSet('uaenorth', 'westeurope')][string]$Location = 'uaenorth', [ValidatePattern('^[a-zA-Z0-9._()-]{1,90}$')] [string]$ResourceGroupName = "rg-container-apps-acc-$([guid]::NewGuid().ToString('N').Substring(0, 8))", [ValidatePattern('^[a-z][a-z0-9-]{0,30}[a-z0-9]$')][string]$EnvironmentName = 'cae-acc-hello', @@ -21,9 +22,12 @@ $context = Set-AzContext -SubscriptionId $SubscriptionId -TenantId $currentConte if ($context.Environment.Name -ne 'AzureCloud') { throw 'This sample supports Azure public cloud only.' } -Write-Host "Subscription: $SubscriptionId; tenant: $($context.Tenant.Id); region: uaenorth" +Write-Host "Subscription: $SubscriptionId; tenant: $($context.Tenant.Id); region: $Location" Write-Host "Resource group: $ResourceGroupName; environment: $EnvironmentName; app: $AppName" Write-Warning 'One dedicated DC4 node will incur ongoing charges. Nothing is automatically deleted, including on failure.' +if ($Location -eq 'westeurope') { + Write-Warning 'EXPERIMENTAL regional deployment: public documentation currently lists only UAE North. DC4 advertisement is not a guarantee of West Europe support or capacity.' +} foreach ($namespace in @('Microsoft.App', 'Microsoft.Network')) { $provider = Get-ArmJson -Path "/subscriptions/$SubscriptionId/providers/${namespace}?api-version=2021-04-01" @@ -31,10 +35,10 @@ foreach ($namespace in @('Microsoft.App', 'Microsoft.Network')) { throw "Register $namespace with Register-AzResourceProvider -ProviderNamespace $namespace and wait for Registered, then retry." } } -$regionalBase = "/subscriptions/$SubscriptionId/providers/Microsoft.App/locations/uaenorth" +$regionalBase = "/subscriptions/$SubscriptionId/providers/Microsoft.App/locations/$Location" $types = Get-ArmJson -Path "${regionalBase}/availableManagedEnvironmentsWorkloadProfileTypes?api-version=2025-07-01" if (-not ($types.value | Where-Object name -eq 'DC4')) { - throw 'UAE North does not advertise DC4 for this subscription. No other compute or region will be used.' + throw "$Location does not advertise DC4 for this subscription. No other compute or region will be used." } $usage = Get-ArmJson -Path "${regionalBase}/usages?api-version=2025-07-01" $quota = @($usage.value | Where-Object { $_.name.value -eq 'ManagedEnvironmentCount' }) @@ -42,7 +46,7 @@ if ($quota.Count -ne 1 -or $null -eq $quota[0].currentValue -or $null -eq $quota throw 'Could not determine the regional managed-environment quota. Check Azure quota before retrying.' } if ($quota[0].currentValue -ge $quota[0].limit) { - throw "UAE North managed-environment quota exhausted ($($quota[0].currentValue)/$($quota[0].limit)). Request quota; no fallback is permitted." + throw "$Location managed-environment quota exhausted ($($quota[0].currentValue)/$($quota[0].limit)). Request quota; no fallback is permitted." } $group = Get-AzResourceGroup -ErrorAction Stop | Where-Object ResourceGroupName -eq $ResourceGroupName @@ -53,7 +57,7 @@ if ($group) { } } else { - $null = New-AzResourceGroup -Name $ResourceGroupName -Location uaenorth ` + $null = New-AzResourceGroup -Name $ResourceGroupName -Location $Location ` -Tag @{ sample = 'container-apps-acc' } -ErrorAction Stop } @@ -61,7 +65,7 @@ $parameters = @{ ResourceGroupName = $ResourceGroupName TemplateFile = Join-Path $PSScriptRoot 'main.json' TemplateParameterObject = @{ - location = 'uaenorth' + location = $Location environmentName = $EnvironmentName appName = $AppName } @@ -83,7 +87,7 @@ try { throw "Deployment '$deploymentName' did not succeed: $($deployment.ProvisioningState)" } & (Join-Path $PSScriptRoot 'Test-ContainerApp.ps1') -SubscriptionId $SubscriptionId ` - -ResourceGroupName $ResourceGroupName -EnvironmentName $EnvironmentName -AppName $AppName + -ResourceGroupName $ResourceGroupName -EnvironmentName $EnvironmentName -AppName $AppName -Location $Location } catch { Write-Warning "Validation, deployment or verification failed. Resources are retained in '$ResourceGroupName'. Inspect deployment '$deploymentName' and any partial resources; this is NOT a successful confidential deployment." diff --git a/azure-container-apps-sample/container-apps-acc/DeploymentHelpers.ps1 b/azure-container-apps-sample/container-apps-acc/DeploymentHelpers.ps1 index 4d90b2b..f982d2e 100644 --- a/azure-container-apps-sample/container-apps-acc/DeploymentHelpers.ps1 +++ b/azure-container-apps-sample/container-apps-acc/DeploymentHelpers.ps1 @@ -13,7 +13,8 @@ function Assert-ConfidentialContainerApp { [CmdletBinding()] param( [Parameter(Mandatory)][object]$App, - [Parameter(Mandatory)][object]$Environment + [Parameter(Mandatory)][object]$Environment, + [ValidateSet('uaenorth', 'westeurope')][string]$Location = 'uaenorth' ) if ($App.properties.provisioningState -ne 'Succeeded' -or @@ -30,9 +31,9 @@ function Assert-ConfidentialContainerApp { $profiles[0].minimumCount -ne 1 -or $profiles[0].maximumCount -ne 1) { throw 'Expected explicit binding to the confidential DC4 profile with exactly one node; refusing ordinary-compute fallback.' } - if ($App.location -notin @('uaenorth', 'UAE North') -or - $Environment.location -notin @('uaenorth', 'UAE North')) { - throw 'Expected the app and environment in UAE North.' + if (($App.location -replace '\s', '') -ne $Location -or + ($Environment.location -replace '\s', '') -ne $Location) { + throw "Expected the app and environment in $Location." } $ingress = $App.properties.configuration.ingress if ($ingress.external -ne $true -or $ingress.allowInsecure -ne $false -or diff --git a/azure-container-apps-sample/container-apps-acc/README.md b/azure-container-apps-sample/container-apps-acc/README.md index 3056fcb..eb36919 100644 --- a/azure-container-apps-sample/container-apps-acc/README.md +++ b/azure-container-apps-sample/container-apps-acc/README.md @@ -3,7 +3,8 @@ [All Container Apps samples](../README.md) | [Repository overview](../../README.md) Deploy Microsoft's hello-world container to **Azure Container Apps** in **UAE -North**, using a dedicated **DC4 confidential workload profile**. Then verify the +North** by default, using a dedicated **DC4 confidential workload profile**. An +explicit **experimental West Europe** option is also available. Then verify the app's Azure-reported placement and open its public HTTPS page. This example follows [Confidential compute in Azure Container Apps](https://learn.microsoft.com/azure/container-apps/confidential-compute) @@ -15,6 +16,16 @@ DC4 binding and ready revision were verified, and the public endpoint returned HTTP 200 with the hello-world page. Availability, quota and organizational policies still need checking in your own subscription. +**West Europe experiment:** 10 November 2026. The same template deployed +successfully and passed live DC4 placement, revision-health and HTTPS checks. +See [observed results](#west-europe-observed-results-10-november-2026). + +> **Experimental regional option:** Public documentation currently lists only +> UAE North and warns that advertised DC-series profiles do not establish +> confidential-compute support. Selecting `-Location westeurope` is a regional +> experiment, not a claim of supported availability. There is no fallback to +> another region or ordinary compute. + For an application-code example, continue with [Functions on Azure Container Apps](../functions-on-azure-container-apps/README.md). It uses the native Functions integration with Node.js 24, a fresh runtime-image @@ -56,7 +67,7 @@ Secure Key Release, application authentication, or protection of external storag ## Resources and cost ```text -New dedicated resource group (UAE North) +New dedicated resource group (UAE North by default; West Europe explicitly) Virtual network (10.42.0.0/16) Delegated infrastructure subnet (10.42.0.0/23), with NSG Container Apps environment @@ -91,7 +102,8 @@ New dedicated resource group (UAE North) - An Azure public-cloud subscription authorized for confidential Container Apps. The linked documentation currently lists **UAE North**. Availability and capacity - can change; this sample intentionally permits only `uaenorth`. + can change; this sample permits `uaenorth` (default) and the explicitly + experimental `westeurope` option. - Permission to create a resource group, VNet, NSG, managed environment and container app (for example Contributor at subscription scope). **Owner is not required**; this example does not activate PIM roles or create role assignments. Activate @@ -157,6 +169,64 @@ and Network API `2024-05-01`. Default names are `cae-acc-hello` and `ca-acc-hello`. Optional `-EnvironmentName` and `-AppName` parameters customize those names; use the same names for verification. +### Explicit West Europe experiment + +Use the same sign-in prerequisites and a new dedicated group. Pass the region +to **both** deployment and verification; omitting it selects UAE North. + +```powershell +$resourceGroup = "rg-container-apps-acc-weu-$([guid]::NewGuid().ToString('N').Substring(0, 8))" +.\Deploy-ContainerApp.ps1 -SubscriptionId $subscriptionId ` + -ResourceGroupName $resourceGroup -Location westeurope -ValidateOnly +$result = .\Deploy-ContainerApp.ps1 -SubscriptionId $subscriptionId ` + -ResourceGroupName $resourceGroup -Location westeurope +$result | Format-List +.\Test-ContainerApp.ps1 -SubscriptionId $subscriptionId ` + -ResourceGroupName $resourceGroup -Location westeurope | Format-List +``` + +The script checks profiles and quota in the selected region and emits an +experimental-region warning for West Europe. Passing preflight does not +guarantee that the service will accept a confidential environment there. + +### West Europe observed results (10 November 2026) + +Verified at **18:26:29 UTC**, using Container Apps API `2025-07-01`: + +| Check | Observed result | +| --- | --- | +| Regional discovery | West Europe advertised DC4; regional environment usage was 0 of 50 before deployment | +| Preflight | 27 offline tests passed; ARM validation and what-if succeeded, with exactly four resource creates | +| Deployment | `acc-hello-weu-20261110` succeeded in `rg-container-apps-acc-weu-20261110` | +| Environment/app | `cae-acc-hello` and `ca-acc-hello` both reported `Succeeded` in West Europe | +| Explicit placement | App selected `confidential`; matching profile type `DC4`, minimum 1 / maximum 1 node | +| Revision | `ca-acc-hello--950ijs1`: active, `Healthy`, `RunningAtMaxScale`, one replica | +| Public endpoint | HTTPS returned HTTP 200 and the `Azure Container Apps` page signature | +| Identity/RBAC | App identity `None`; public Microsoft image, no application role assignment required | + +The template was deployed through the authenticated Azure CLI, with +`location=westeurope environmentName=cae-acc-hello appName=ca-acc-hello`; +the PowerShell Az context was not signed in. Live resources were read through +`az rest`, then checked using this sample's shared placement and endpoint +helpers. The offline suite separately verifies the PowerShell deployment +workflow's regional parameter routing. + +The first immediate post-deployment check encountered environment state +`Updating`, despite ARM reporting deployment success. Bounded readiness polling +then observed `Succeeded`; no redeployment or configuration workaround was +needed. `RunningAtMaxScale` is expected with the sample's maximum of one replica. +Azure also added its default Consumption profile, but the app remained +explicitly bound to DC4. + +No regional or ordinary-compute fallback, policy bypass, or AKS restart was +performed. Resources were initially retained, then removed on request on +10 November 2026. At 19:22 UTC, both the sample resource group and its +service-managed infrastructure group were verified absent. The test endpoint +is no longer a live demo. This successful +subscription-level experiment does **not** resolve the discrepancy with the +public UAE North-only support documentation, certify general West Europe +availability, or provide independent cryptographic hardware attestation. + Expect environment creation and DC4 provisioning to take several minutes. Success produces `AppUrl` and `PortalUrl`, both fully qualified HTTPS links, plus resource IDs and the verified profile. The page is Microsoft's Container Apps @@ -185,7 +255,8 @@ Verification requires: - Provisioning and latest-revision readiness settle within a bounded wait of 60 checks, 10 seconds apart. A temporarily `Updating` environment after ARM deployment is retried; failed or unexpected states terminate immediately. -- Both resources report `Succeeded` in UAE North. +- Both resources report `Succeeded` in the explicitly expected region (UAE North + by default; pass `-Location westeurope` for the experiment). - The app references the expected environment and its `confidential` DC4 profile, with exactly one dedicated node. - The latest revision is ready and ingress is public, HTTPS-only, on target port 80. @@ -224,6 +295,9 @@ Use this deployment's subscription, resource group and environment name there. The Functions app explicitly selects the same `confidential` DC4 profile and can share its existing node with the quickstart app. +The sibling Functions workflow remains **UAE North-specific**; do not reuse a +West Europe experimental environment with it unchanged. + The Functions guide adds a registry, user-assigned identity, scoped role assignments, host storage and private Blob networking. Unlike this quickstart, it requires permission to create role assignments. Its additional subnet @@ -238,7 +312,7 @@ respective guides; a successful hello-world response alone is insufficient. | --- | --- | | Provider is not registered | Register the named provider in the selected subscription and wait for `Registered`. | | Authorization/PIM error | Confirm tenant/subscription and activate an appropriate role; retry after propagation. | -| No DC4 profile, zero/exhausted quota, or insufficient capacity | Check UAE North support and request service access/quota/capacity through Azure support. Do not substitute ordinary compute. | +| No DC4 profile, zero/exhausted quota, or insufficient capacity | Check support in the selected region and request service access/quota/capacity through Azure support. West Europe remains an experimental option. Do not substitute ordinary compute. | | ARM validation or deployment fails | Read the terminating error and the resource group's **Deployments** details. Inspect partial resources; they are retained. | | Organizational networking policy rejects deployment | The template includes VNet injection and a subnet NSG. Review any additional private-ingress, outbound connectivity, address-space or tagging requirements with your administrator; do not bypass policy. | | Deployment succeeded but verification failed | Inspect the revision and profile, then rerun the verification script. Do not treat this as a verified confidential deployment. | diff --git a/azure-container-apps-sample/container-apps-acc/Test-ContainerApp.ps1 b/azure-container-apps-sample/container-apps-acc/Test-ContainerApp.ps1 index 0105d13..344f99b 100644 --- a/azure-container-apps-sample/container-apps-acc/Test-ContainerApp.ps1 +++ b/azure-container-apps-sample/container-apps-acc/Test-ContainerApp.ps1 @@ -3,6 +3,7 @@ [CmdletBinding()] param( [Parameter(Mandatory)][guid]$SubscriptionId, + [ValidateSet('uaenorth', 'westeurope')][string]$Location = 'uaenorth', [Parameter(Mandatory)][ValidatePattern('^[a-zA-Z0-9._()-]{1,90}$')][string]$ResourceGroupName, [ValidatePattern('^[a-z][a-z0-9-]{0,30}[a-z0-9]$')][string]$EnvironmentName = 'cae-acc-hello', [ValidatePattern('^[a-z][a-z0-9-]{0,30}[a-z0-9]$')][string]$AppName = 'ca-acc-hello' @@ -38,7 +39,7 @@ for ($attempt = 1; $attempt -le 60; $attempt++) { Write-Host "Waiting for provisioning and revision readiness ($attempt/60)." Start-Sleep -Seconds 10 } -$url = Assert-ConfidentialContainerApp -App $app -Environment $environment +$url = Assert-ConfidentialContainerApp -App $app -Environment $environment -Location $Location Test-HelloWorldEndpoint -Uri $url [pscustomobject]@{ diff --git a/azure-container-apps-sample/container-apps-acc/main.json b/azure-container-apps-sample/container-apps-acc/main.json index 0bb98e7..77b1bcf 100644 --- a/azure-container-apps-sample/container-apps-acc/main.json +++ b/azure-container-apps-sample/container-apps-acc/main.json @@ -5,8 +5,8 @@ "location": { "type": "string", "defaultValue": "uaenorth", - "allowedValues": ["uaenorth"], - "metadata": { "description": "Region documented for confidential Container Apps. No regional fallback." } + "allowedValues": ["uaenorth", "westeurope"], + "metadata": { "description": "Explicit deployment region. West Europe requires live DC4 availability checks; no regional fallback." } }, "environmentName": { "type": "string", diff --git a/azure-container-apps-sample/container-apps-acc/tests/Test-Sample.ps1 b/azure-container-apps-sample/container-apps-acc/tests/Test-Sample.ps1 index d531ea3..f8157d5 100644 --- a/azure-container-apps-sample/container-apps-acc/tests/Test-Sample.ps1 +++ b/azure-container-apps-sample/container-apps-acc/tests/Test-Sample.ps1 @@ -26,7 +26,7 @@ function New-Fixture { @{ Environment = @{ id = '/subscriptions/test/resourceGroups/test/providers/Microsoft.App/managedEnvironments/test' - location = 'uaenorth' + location = $state.fixtureLocation ?? 'uaenorth' properties = @{ provisioningState = 'Succeeded' workloadProfiles = @(@{ name = 'confidential'; workloadProfileType = 'DC4'; minimumCount = 1; maximumCount = 1 }) @@ -34,7 +34,7 @@ function New-Fixture { } App = @{ id = '/subscriptions/test/resourceGroups/test/providers/Microsoft.App/containerApps/test' - location = 'uaenorth' + location = $state.fixtureLocation ?? 'uaenorth' properties = @{ provisioningState = 'Succeeded' environmentId = '/subscriptions/test/resourceGroups/test/providers/Microsoft.App/managedEnvironments/test' @@ -54,12 +54,14 @@ function Get-AzResourceGroup { if ($state.existing) { @{ ResourceGroupName = 'rg-offline'; Tags = @{ sample = 'container-apps-acc' } } } } function Get-AzResource { if ($state.populated) { @{ Name = 'unrelated' } } } -function New-AzResourceGroup { $state.groupCreates++ } +function New-AzResourceGroup { param($Location) $state.groupCreates++; $state.groupLocation = $Location } function Test-AzResourceGroupDeployment { if ($state.invalid) { @{ Code = 'InvalidTemplate'; Message = 'test rejection' } } elseif ($state.validationEmptyString) { '' } } function New-AzResourceGroupDeployment { + param($TemplateParameterObject) + $state.templateLocation = $TemplateParameterObject.location $state.deployments++ if ($state.deployFails) { throw 'Mock capacity rejection' } @{ ProvisioningState = 'Succeeded' } @@ -69,6 +71,7 @@ function Invoke-AzRestMethod { if ($state.restFails) { return @{ StatusCode = 403; Content = 'Forbidden' } } $data = switch -Wildcard ($Path) { '*availableManagedEnvironmentsWorkloadProfileTypes*' { + $state.profilePath = $Path if ($state.noProfile) { @{ value = @() } } else { @{ value = @(@{ name = 'DC4' }) } } } @@ -94,6 +97,10 @@ function Invoke-WebRequest { } function Start-Sleep {} function Reset-Mocks { + $state.fixtureLocation = 'uaenorth' + $state.groupLocation = $null + $state.templateLocation = $null + $state.profilePath = $null $state.existing = $false $state.populated = $false $state.groupCreates = 0 @@ -128,7 +135,8 @@ Case 'ARM template binds DC4, HTTPS and bounded scaling' { $envResource = $template.resources | Where-Object type -eq 'Microsoft.App/managedEnvironments' $appResource = $template.resources | Where-Object type -eq 'Microsoft.App/containerApps' Assert ($template.resources.Count -eq 4) 'Unexpected supporting resources.' - Assert ($template.parameters.location.allowedValues.Count -eq 1 -and $template.parameters.location.allowedValues[0] -eq 'uaenorth') 'Region fallback allowed.' + Assert ($template.parameters.location.defaultValue -eq 'uaenorth') 'Default region changed.' + Assert (($template.parameters.location.allowedValues -join ',') -eq 'uaenorth,westeurope') 'Unexpected region allowed.' Assert ($envResource.properties.workloadProfiles.Count -eq 1) 'Unexpected additional profiles.' Assert ($envResource.properties.appLogsConfiguration.destination -ceq '') 'Expected disabled centralized logging via empty ARM destination.' $profile = $envResource.properties.workloadProfiles[0] @@ -148,6 +156,15 @@ Case 'Accept valid Azure-reported confidential placement' { $f = New-Fixture Assert ((Assert-ConfidentialContainerApp -App $f.App -Environment $f.Environment) -eq 'https://test.example.com') 'Wrong URL.' } +Case 'Accept explicitly selected West Europe and reject region mismatch' { + $f = New-Fixture + $f.App.location = 'West Europe' + $f.Environment.location = 'westeurope' + Assert ((Assert-ConfidentialContainerApp -App $f.App -Environment $f.Environment -Location westeurope) -eq 'https://test.example.com') 'West Europe rejected.' + Expect-Failure { Assert-ConfidentialContainerApp -App $f.App -Environment $f.Environment } 'Expected the app and environment' + $f.Environment.location = 'uaenorth' + Expect-Failure { Assert-ConfidentialContainerApp -App $f.App -Environment $f.Environment -Location westeurope } 'Expected the app and environment' +} Case 'Accept UAE North display name returned by the live API' { $f = New-Fixture $f.App.location = 'UAE North' @@ -177,7 +194,7 @@ foreach ($change in @( @{ Name = 'unready revision'; Edit = { param($f) $f.App.properties.latestReadyRevisionName = 'old' }; Pattern = 'not ready' }, @{ Name = 'insecure ingress'; Edit = { param($f) $f.App.properties.configuration.ingress.allowInsecure = $true }; Pattern = 'HTTPS-only' }, @{ Name = 'failed provisioning'; Edit = { param($f) $f.Environment.properties.provisioningState = 'Failed' }; Pattern = 'Succeeded' }, - @{ Name = 'wrong region'; Edit = { param($f) $f.App.location = 'eastus' }; Pattern = 'UAE North' }, + @{ Name = 'wrong region'; Edit = { param($f) $f.App.location = 'eastus' }; Pattern = 'Expected the app and environment in uaenorth' }, @{ Name = 'malformed FQDN'; Edit = { param($f) $f.App.properties.configuration.ingress.fqdn = 'example.com/other' }; Pattern = 'FQDN' } )) { Case "Reject $($change.Name)" { @@ -248,4 +265,11 @@ Case 'HTTP verification failure propagates from deployment' { Reset-Mocks; $state.content = 'wrong app' Expect-Failure { & $deployScript @deployArgs } 'signature' } +Case 'Explicit West Europe flows through discovery, deployment and verification' { + Reset-Mocks; $state.fixtureLocation = 'westeurope' + $result = & $deployScript @deployArgs -Location westeurope + Assert ($state.profilePath -match '/locations/westeurope/') 'Discovery used the wrong region.' + Assert ($state.groupLocation -eq 'westeurope' -and $state.templateLocation -eq 'westeurope') 'Deployment used the wrong region.' + Assert ($result.Location -eq 'westeurope' -and $result.WorkloadProfileType -eq 'DC4') 'Verification used the wrong region.' +} Write-Host "$script:passed offline tests passed." From 6addc7eb20d1449eb600b29093ab2feb8a713fc5 Mon Sep 17 00:00:00 2001 From: Simon Gallagher Date: Tue, 10 Nov 2026 20:42:44 +0000 Subject: [PATCH 2/2] Describe confidential Container Apps region selection as GA capability Remove regional experimental labels and warning while preserving availability, quota, capacity and confidential-placement checks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- azure-container-apps-sample/README.md | 8 ++-- .../Deploy-ContainerApp.ps1 | 4 -- .../container-apps-acc/README.md | 40 +++++++++---------- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/azure-container-apps-sample/README.md b/azure-container-apps-sample/README.md index de8d600..51e94c0 100644 --- a/azure-container-apps-sample/README.md +++ b/azure-container-apps-sample/README.md @@ -3,14 +3,14 @@ [Repository overview](../README.md) Two implemented examples demonstrate explicit binding to a **confidential DC4 -workload profile in UAE North**. The HTML quickstart also offers an explicitly -experimental West Europe option; the Functions workflow remains UAE +workload profile in UAE North**. The HTML quickstart also offers explicit +region selection for West Europe; the Functions workflow remains UAE North-specific. Choose a guide based on the endpoint and prerequisites you need: | Sample | Status | | --- | --- | -| [Confidential hello world](container-apps-acc/README.md) | ARM template and PowerShell deployment using DC4 in UAE North by default, with an explicit experimental West Europe option | +| [Confidential hello world](container-apps-acc/README.md) | ARM template and PowerShell deployment using DC4 in UAE North by default, with explicit region selection for West Europe | | [Functions on Azure Container Apps](functions-on-azure-container-apps/README.md) | Tested native Functions hello world on confidential DC4 in UAE North: Node.js 24, fresh ACR builds, managed identity, private Blob host storage, deployment and verification | Start with **Confidential hello world**. It uses Microsoft's public quickstart @@ -61,7 +61,7 @@ $subscriptionId = '' $resourceGroup = '' $environmentName = 'cae-acc-hello' $appName = 'ca-acc-hello' -$location = 'uaenorth' # Use 'westeurope' only for the HTML quickstart experiment. +$location = 'uaenorth' # Use 'westeurope' only for the HTML quickstart. Connect-AzAccount -Subscription $subscriptionId . .\azure-container-apps-sample\container-apps-acc\DeploymentHelpers.ps1 diff --git a/azure-container-apps-sample/container-apps-acc/Deploy-ContainerApp.ps1 b/azure-container-apps-sample/container-apps-acc/Deploy-ContainerApp.ps1 index f9c1643..d41b3ab 100644 --- a/azure-container-apps-sample/container-apps-acc/Deploy-ContainerApp.ps1 +++ b/azure-container-apps-sample/container-apps-acc/Deploy-ContainerApp.ps1 @@ -25,10 +25,6 @@ if ($context.Environment.Name -ne 'AzureCloud') { Write-Host "Subscription: $SubscriptionId; tenant: $($context.Tenant.Id); region: $Location" Write-Host "Resource group: $ResourceGroupName; environment: $EnvironmentName; app: $AppName" Write-Warning 'One dedicated DC4 node will incur ongoing charges. Nothing is automatically deleted, including on failure.' -if ($Location -eq 'westeurope') { - Write-Warning 'EXPERIMENTAL regional deployment: public documentation currently lists only UAE North. DC4 advertisement is not a guarantee of West Europe support or capacity.' -} - foreach ($namespace in @('Microsoft.App', 'Microsoft.Network')) { $provider = Get-ArmJson -Path "/subscriptions/$SubscriptionId/providers/${namespace}?api-version=2021-04-01" if ($provider.registrationState -ne 'Registered') { diff --git a/azure-container-apps-sample/container-apps-acc/README.md b/azure-container-apps-sample/container-apps-acc/README.md index eb36919..53e9cd5 100644 --- a/azure-container-apps-sample/container-apps-acc/README.md +++ b/azure-container-apps-sample/container-apps-acc/README.md @@ -4,7 +4,7 @@ Deploy Microsoft's hello-world container to **Azure Container Apps** in **UAE North** by default, using a dedicated **DC4 confidential workload profile**. An -explicit **experimental West Europe** option is also available. Then verify the +explicit **West Europe** region selection is also available. Then verify the app's Azure-reported placement and open its public HTTPS page. This example follows [Confidential compute in Azure Container Apps](https://learn.microsoft.com/azure/container-apps/confidential-compute) @@ -16,15 +16,15 @@ DC4 binding and ready revision were verified, and the public endpoint returned HTTP 200 with the hello-world page. Availability, quota and organizational policies still need checking in your own subscription. -**West Europe experiment:** 10 November 2026. The same template deployed +**Tested in West Europe:** 10 November 2026. The same template deployed successfully and passed live DC4 placement, revision-health and HTTPS checks. See [observed results](#west-europe-observed-results-10-november-2026). -> **Experimental regional option:** Public documentation currently lists only -> UAE North and warns that advertised DC-series profiles do not establish -> confidential-compute support. Selecting `-Location westeurope` is a regional -> experiment, not a claim of supported availability. There is no fallback to -> another region or ordinary compute. +> **Region selection:** Confidential compute in Azure Container Apps is a GA +> product. This sample adds region selection with `-Location westeurope`, while +> retaining UAE North as the default. Check regional availability, quota and +> capacity for your subscription. There is no fallback to another region or +> ordinary compute. For an application-code example, continue with [Functions on Azure Container Apps](../functions-on-azure-container-apps/README.md). @@ -101,9 +101,8 @@ New dedicated resource group (UAE North by default; West Europe explicitly) ## Prerequisites - An Azure public-cloud subscription authorized for confidential Container Apps. - The linked documentation currently lists **UAE North**. Availability and capacity - can change; this sample permits `uaenorth` (default) and the explicitly - experimental `westeurope` option. + Availability and capacity can change; this sample permits `uaenorth` (default) + and explicit selection of `westeurope`. - Permission to create a resource group, VNet, NSG, managed environment and container app (for example Contributor at subscription scope). **Owner is not required**; this example does not activate PIM roles or create role assignments. Activate @@ -169,7 +168,7 @@ and Network API `2024-05-01`. Default names are `cae-acc-hello` and `ca-acc-hello`. Optional `-EnvironmentName` and `-AppName` parameters customize those names; use the same names for verification. -### Explicit West Europe experiment +### Select West Europe Use the same sign-in prerequisites and a new dedicated group. Pass the region to **both** deployment and verification; omitting it selects UAE North. @@ -185,9 +184,8 @@ $result | Format-List -ResourceGroupName $resourceGroup -Location westeurope | Format-List ``` -The script checks profiles and quota in the selected region and emits an -experimental-region warning for West Europe. Passing preflight does not -guarantee that the service will accept a confidential environment there. +The script checks profiles and quota in the selected region. Passing preflight +does not guarantee capacity at provisioning time. ### West Europe observed results (10 November 2026) @@ -222,10 +220,10 @@ No regional or ordinary-compute fallback, policy bypass, or AKS restart was performed. Resources were initially retained, then removed on request on 10 November 2026. At 19:22 UTC, both the sample resource group and its service-managed infrastructure group were verified absent. The test endpoint -is no longer a live demo. This successful -subscription-level experiment does **not** resolve the discrepancy with the -public UAE North-only support documentation, certify general West Europe -availability, or provide independent cryptographic hardware attestation. +is no longer a live demo. These results record one successful deployment; +availability and capacity must still be checked for your subscription. +Configuration verification does not provide independent cryptographic hardware +attestation. Expect environment creation and DC4 provisioning to take several minutes. Success produces `AppUrl` and `PortalUrl`, both fully qualified HTTPS links, plus @@ -256,7 +254,7 @@ Verification requires: 60 checks, 10 seconds apart. A temporarily `Updating` environment after ARM deployment is retried; failed or unexpected states terminate immediately. - Both resources report `Succeeded` in the explicitly expected region (UAE North - by default; pass `-Location westeurope` for the experiment). + by default; pass `-Location westeurope` to select West Europe). - The app references the expected environment and its `confidential` DC4 profile, with exactly one dedicated node. - The latest revision is ready and ingress is public, HTTPS-only, on target port 80. @@ -296,7 +294,7 @@ The Functions app explicitly selects the same `confidential` DC4 profile and can share its existing node with the quickstart app. The sibling Functions workflow remains **UAE North-specific**; do not reuse a -West Europe experimental environment with it unchanged. +West Europe environment with it unchanged. The Functions guide adds a registry, user-assigned identity, scoped role assignments, host storage and private Blob networking. Unlike this quickstart, @@ -312,7 +310,7 @@ respective guides; a successful hello-world response alone is insufficient. | --- | --- | | Provider is not registered | Register the named provider in the selected subscription and wait for `Registered`. | | Authorization/PIM error | Confirm tenant/subscription and activate an appropriate role; retry after propagation. | -| No DC4 profile, zero/exhausted quota, or insufficient capacity | Check support in the selected region and request service access/quota/capacity through Azure support. West Europe remains an experimental option. Do not substitute ordinary compute. | +| No DC4 profile, zero/exhausted quota, or insufficient capacity | Check availability in the selected region and request service access/quota/capacity through Azure support. Do not substitute ordinary compute. | | ARM validation or deployment fails | Read the terminating error and the resource group's **Deployments** details. Inspect partial resources; they are retained. | | Organizational networking policy rejects deployment | The template includes VNet injection and a subnet NSG. Review any additional private-ingress, outbound connectivity, address-space or tagging requirements with your administrator; do not bypass policy. | | Deployment succeeded but verification failed | Inspect the revision and profile, then rerun the verification script. Do not treat this as a verified confidential deployment. |