Multitenant by default, isolated by exception
When you deploy a normal Azure Web App, it lands on shared infrastructure: your app and thousands of strangers' apps run on the same pool of front ends and workers, walled off from each other by the platform. That is the right default — it is cheap, it scales, and the isolation is real. But some workloads carry a requirement that "walled off by the platform" does not satisfy: a regulator who wants the compute physically dedicated, a security posture where the app must have no public endpoint at all, or a network design where the app must sit inside a private VNet and be reachable only from there.
That is what an App Service Environment (ASE) is — the same App Service you already know, but deployed as a single-tenant stamp injected into a subnet of your own virtual network. Same apps, same deployment slots, same scaling — different tenancy and different network position. This lab is about understanding that difference precisely enough to know when to reach for it, and reading the exact Bicep that builds one.
You don't rent a floor. You buy the building.
Why this lab you read instead of run
Every other CAMPUX lab is free because the resources cost pennies and you tear them down in minutes. An ASE is the exception. App Service Environment v3 has no stamp fee — an improvement over v2 — but you still pay the Isolated v2 instance rate, and an empty ASE is billed as if it held one I1v2 instance the entire time it exists.1 That is on the order of hundreds of dollars a month, and the environment takes an hour or more to provision and to delete. Reading the template teaches the architecture for $0; deploying it does not teach you anything the reading didn't, and it can leave a real bill behind. So we read.
Everything below is still hands-on — you clone nothing you have to pay for, you read a real template, and you compile it locally to prove it is valid. You just stop one step short of az deployment group create, on purpose.
A place to read and compile
Open Azure Cloud Shell (Bash) — az and the Bicep CLI are already installed and signed in, and reading and compiling a template costs nothing. Create the file below with a here-doc (or paste it into the Cloud Shell editor with code ase.bicep):
cat > ase.bicep <<'EOF'
// (paste the template from the next section here)
EOF
ase.bicep file open in a shell where az bicep version prints a version number. Nothing has been deployed and nothing can be billed by reading a file.Four resources make an ASE
An isolated App Service is really four resources stacked in order: a network with a subnet handed over to the platform, the environment itself, an Isolated v2 plan placed inside it, and an app on that plan. Read it top to bottom — the comments are the lesson.
// ase.bicep — App Service Environment v3, read-only reference template. // Illustrative: read it and compile it; this lab does not deploy it. @description('Azure region for every resource.') param location string = resourceGroup().location @description('Globally unique name stem for the environment.') param aseName string = 'campux-ase' @description('Isolated v2 plan size. I1v2 is the smallest; I2v2 / I3v2 scale up.') @allowed([ 'I1v2', 'I2v2', 'I3v2' ]) param planSku string = 'I1v2' // 1 · The network. The ASE is injected into ONE subnet, which must be // delegated to the App Service platform — that delegation is what lets // Azure inject the environment's infrastructure. Nothing else may share // this subnet; the environment owns the whole /24. resource vnet 'Microsoft.Network/virtualNetworks@2023-11-01' = { name: '${aseName}-vnet' location: location properties: { addressSpace: { addressPrefixes: [ '10.0.0.0/16' ] } subnets: [ { name: 'ase-subnet' properties: { addressPrefix: '10.0.1.0/24' delegations: [ { name: 'Microsoft.Web.hostingEnvironments' properties: { serviceName: 'Microsoft.Web/hostingEnvironments' } } ] } } ] } } // 2 · The environment. kind 'ASEV3' selects v3 (no stamp fee). // internalLoadBalancingMode 3 = fully internal (ILB): apps get PRIVATE // IPs only, reachable inside the VNet, never from the public internet. // Mode 0 would publish an external VIP instead. Private is the whole // point of most ASE deployments. resource ase 'Microsoft.Web/hostingEnvironments@2024-04-01' = { name: aseName location: location kind: 'ASEV3' properties: { internalLoadBalancingMode: 3 virtualNetwork: { id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnet.name, 'ase-subnet') } zoneRedundant: false } } // 3 · An Isolated v2 App Service plan, placed INSIDE the environment via // hostingEnvironmentProfile. This is the only place Isolated v2 SKUs // run. You pay the Isolated v2 instance rate — and an empty ASE still // bills as one I1v2, which is why this template stays on the page. resource plan 'Microsoft.Web/serverfarms@2024-04-01' = { name: '${aseName}-plan' location: location sku: { name: planSku tier: 'IsolatedV2' } properties: { hostingEnvironmentProfile: { id: ase.id } } } // 4 · A web app on that plan. Because the plan lives in an internal ASE, // this app has no public endpoint — it answers only inside the VNet. resource site 'Microsoft.Web/sites@2024-04-01' = { name: '${aseName}-app' location: location properties: { serverFarmId: plan.id hostingEnvironmentProfile: { id: ase.id } httpsOnly: true } } output aseId string = ase.id output appPrivateHostName string = site.properties.defaultHostName
ASEV3 environment (the isolated stamp), the IsolatedV2 plan pinned into it by hostingEnvironmentProfile, and the app that inherits the environment's privacy. If you can explain internalLoadBalancingMode: 3 to someone, you have the core idea.Compile it — the free proof it's real
You cannot cheaply deploy this, but you can compile it. az bicep build type-checks the whole template against Azure's resource schemas and emits the ARM JSON it would submit — entirely offline, no subscription touched, $0. It is the same check that runs before every real deploy, and it is how you prove a template is valid without spending a cent.
az bicep build --file ase.bicep --stdout | head -40
# or lint it for style + correctness warnings:
az bicep lint --file ase.bicep
bicep build completes and prints ARM JSON with a resources array containing the virtual network, the hostingEnvironments stamp, the serverfarms plan, and the sites app. No error means the schema, the API versions, and the hostingEnvironmentProfile wiring are all valid — the template is real, you just chose not to run it.Where the money is, and where it isn't
The single most useful thing to carry out of this lab is an accurate mental model of ASE pricing, because it is where people get surprised.
| Concept | What it means for the bill |
|---|---|
| No stamp fee (v3) | ASE v2 charged a flat hourly "stamp fee" on top of your plans. v3 removed it — you pay only for the Isolated v2 instances you run.1 |
| Isolated v2 SKU rate | Your plan instances (I1v2 / I2v2 / I3v2) bill at Isolated v2 rates — higher than the shared tiers, because the hardware is dedicated to you. |
| Empty ≠ free | An ASE with no apps is still billed as if it ran one I1v2 instance. There is no "scale to zero" for an environment. Creating one starts a meter that only az group delete stops. |
| Provisioning time | An ASE takes on the order of an hour to create and to delete — this is infrastructure, not a quick web app. |
Put together: an ASE is a standing monthly commitment, not a pennies-and-teardown experiment. That single fact is why regular App Service is the default answer and an ASE is the deliberate exception.
When it's the right answer — and when it isn't
The interview-grade version of this knowledge is not "I can deploy an ASE." It is knowing when you would, and — more often — when you wouldn't.
| Reach for an ASE when… | Stay on regular App Service when… |
|---|---|
| The app must have no public endpoint at all, private inside a VNet (internal ILB mode).2 | Private inbound is enough via private endpoints on a normal Web App — far cheaper, same "no public door". |
| A regulator or contract requires single-tenant, dedicated compute. | Platform-level multitenant isolation meets your compliance bar (it usually does). |
| You need very high scale of Isolated instances behind one network boundary. | Standard/Premium plans with VNet integration cover your networking needs. |
Most "we need the app to be private" requirements are satisfied by a normal App Service with VNet integration (for outbound) and a private endpoint (for inbound) — no ASE, no standing Isolated bill. Knowing that an ASE is rarely the right first reach is itself the senior instinct this lab is teaching.
What you can now honestly claim
You can explain the difference between multitenant App Service and a single-tenant App Service Environment, read the four resources that compose an ASE v3 in Bicep — the delegated subnet, the ASEV3 environment, the IsolatedV2 plan pinned by hostingEnvironmentProfile, and the app that inherits its privacy — and you compiled that template to valid ARM for free. Most usefully, you can say when an ASE is warranted and when a private endpoint on a regular Web App is the cheaper, correct answer. That last judgement is what separates someone who has heard of ASEs from someone who can be trusted to spend the budget on one.
- App Service Environment v3 removed the flat stamp fee that v1/v2 carried; you pay the Isolated v2 instance rate instead, and a totally empty ASE v3 is billed as one
I1v2App Service plan instance. Source: Microsoft's "Estimate your cost savings by migrating to App Service Environment v3" (azure.github.io/AppService). Rates move — price a real deployment against the current App Service pricing page before you commit. internalLoadBalancingMode: 3gives an internal (ILB) ASE where apps resolve to private VNet addresses and have no public endpoint; you provide DNS for the environment's domain suffix inside the VNet. Mode0publishes an external VIP instead. The property andkind: 'ASEV3'are from theMicrosoft.Web/hostingEnvironmentstemplate reference on Microsoft Learn.- API versions are pinned deliberately (
2024-04-01forMicrosoft.Web,2023-11-01for the VNet). If a futureaz bicep buildwarns about a retired version, bump to a current one — the resource shapes here are stable across recent versions.