The address space matches the Infrastructure Architecture document
(192.168.0.0/16, agent subnet 192.168.0.0/24, private-endpoint subnet
192.168.1.0/24). Everything else fits around those two fixed values.
| Subnet | CIDR | Delegation / purpose |
|---|---|---|
snet-agent |
192.168.0.0/24 |
Delegated to Microsoft.App/environments. Foundry hosted-agent network injection. Dedicated to this Foundry resource — cannot be shared. |
snet-pe |
192.168.1.0/24 |
Private endpoints for Foundry, Cosmos, AI Search, Storage, Key Vault, ACR. PE network policies disabled. |
snet-aca |
192.168.2.0/24 |
Delegated to Microsoft.App/environments. Internal ACA environment (UI, stubs, Toolbox, jobs). |
AzureBastionSubnet |
192.168.3.0/26 |
Azure Bastion (fixed name, /26 minimum). |
snet-appsvc |
192.168.4.0/24 |
Delegated to Microsoft.Web/serverFarms. Only used if the optional Web App is enabled. |
snet-mgmt |
192.168.5.0/24 |
Undelegated. Hosts the optional Windows jumpbox VM (Bastion target). Created always; VM only when deployJumpbox = true. |
Jumpbox access path: external user → Azure Portal (Entra sign-in) → Bastion → jumpbox VM in snet-mgmt (RDP over TLS 443). The VM has no public IP; a NIC-level NSG allows RDP (3389) only from the Bastion subnet. A VM can't live in the delegated subnets (snet-agent, snet-aca, snet-appsvc) or in AzureBastionSubnet, which is why snet-mgmt exists.
Adjust any prefix via parameters/variables. Keep snet-agent and snet-pe on the two
document-specified values unless you have a reason to move them.
- No public ingress. The ACA environment is
internal: true; every app uses internal-only ingress. Reach the UI and tools through Azure Bastion (VPN or ExpressRoute are drop-in alternatives). - Optional public edge for the UI (Front Door + WAF). When
deployFrontDoor = true, Azure Front Door (Premium) fronts only the ui app over Private Link to the ACA environment (groupId: managedEnvironments) — the container app still has no public IP. The path isInternet → Front Door → WAF → Private Link → internal ILB → ui. This is additive: the internal Bastion/ILB path is unchanged, and the WAF (DRS 2.1 + Bot Manager- per-IP rate limit) plus optional EasyAuth Entra sign-in gate the public surface.
Setup, the required Private Link approval, and back-out are in
FRONTDOOR-EASYAUTH.md.
- per-IP rate limit) plus optional EasyAuth Entra sign-in gate the public surface.
Setup, the required Private Link approval, and back-out are in
- Private data plane. Storage, Cosmos, AI Search, Foundry, Key Vault, and ACR all have public network access disabled and are reached over private endpoints.
- Egress (NAT gateway, optional). Because Azure retired default outbound access
(Sept 30, 2025), a private subnet has no internet egress unless you provide one. The
optional
deployNatGatewayflag (deploy_nat_gatewayin Terraform) adds a NAT gateway + Standard public IP and associates it withsnet-mgmt(jumpbox) andsnet-aca(container apps). This is outbound-only SNAT — no inbound exposure, so the "no public ingress" posture is preserved. It's a deliberate deviation from strict no public egress; leave the flag off to keep zero outbound. The data tier never needs it (private endpoints), and ACA image pulls use the platform path regardless — so the flag only matters for the jumpbox's own browsing and the app's outbound API calls (e.g. the external news/rate feed). - For a fully controlled egress (allow-list specific FQDNs), replace the NAT gateway with an Azure Firewall + a route table (UDR) — heavier, left out to keep the POC simple.
| Zone | Serves |
|---|---|
privatelink.blob.core.windows.net |
Storage (blob) |
privatelink.documents.azure.com |
Cosmos DB (Sql) |
privatelink.search.windows.net |
AI Search |
privatelink.vaultcore.azure.net |
Key Vault |
privatelink.azurecr.io |
Container Registry |
privatelink.openai.azure.com |
Foundry / AIServices |
privatelink.cognitiveservices.azure.com |
Foundry / AIServices |
privatelink.services.ai.azure.com |
Foundry / AIServices |
The Foundry account private endpoint registers across all three AI zones — all three are
required for full resolution. Validate DNS resolution from inside the VNet after
deploy; an approved private endpoint with an unlinked zone resolves to a public IP and the
call is rejected (a classic private-Foundry failure — see OPERATIONS.md).
- The ACA environment is internal (
internal_load_balancer_enabled = true) with a private static IP — no public ingress. - Each app uses
external: trueingress. On an internal environment this means "reachable from the VNet via the internal load balancer" — still private, not internet-facing. (external: falsewould make an app reachable only by other apps inside the environment, which is why VNet clients like the jumpbox get a 404.) Withexternal: truethe app FQDN is<app>.<envDefaultDomain>(no.internal.). - The template auto-creates a Private DNS Zone named the environment's
defaultDomain(e.g.greenbay-xxxx.<region>.azurecontainerapps.io), a*A record → the env's static IP, and a VNet link (modules/aca-private-dns.bicep/ theaca_*resources incompute.tf). Without it, app FQDNs don't resolve from inside the VNet. - Test from an in-VNet host (jumpbox) using the exact FQDN from
az containerapp show … --query properties.configuration.ingress.fqdn— don't hand-type it.
VNet and Foundry must be in the same region, and that region must support Foundry hosted
agents. Default swedencentral. The 192.168.x space is a private (RFC 1918) range; confirm it
doesn't overlap any peered network or reserved range before you deploy.