What “isolated developer environment” should mean
A virtual desktop can centralize source code and tools, but centralization is not the same as isolation. A useful design defines the boundary in several layers so a compromised developer session cannot automatically reach every other workload.
| Isolation layer | Control | What it prevents |
|---|---|---|
| Identity | Microsoft Entra groups, Conditional Access, separate admin roles, least-privilege Azure RBAC | Unapproved users receiving a desktop, VPN profile, or administrative rights. |
| Compute | Personal AVD host pool or one developer per VM | One developer’s machine-level changes affecting another developer on the same host. |
| Network | Dedicated VNet or spoke, separate subnets, NSGs, UDRs, Azure Firewall where required | Unrestricted east-west movement and accidental access to production networks. |
| Data | Private endpoints, repository permissions, Key Vault, separate storage, controlled profile design | Source, secrets, and artifacts being exposed through broad file-share or service access. |
| Management | Dedicated resource groups, policy, logging, image pipeline, break-glass process | Untracked manual changes and excessive standing privilege. |
Decision rule: pooled multi-session is appropriate when developers use a standardized, non-admin toolset. Choose personal desktops when they need machine-level isolation, persistent local state, local administrator rights, WSL, container tooling, drivers, or incompatible SDK versions.
Reference architecture
The example uses a dedicated Azure Virtual Desktop spoke VNet. Session hosts have no public IP addresses. They make outbound connections to the Azure Virtual Desktop service through controlled egress. Developers connect through the Azure Virtual Desktop client. A Point-to-Site VPN Gateway provides a separate private path for administrators or for developers who also need direct access from their local devices to private endpoints. Microsoft’s Azure Virtual Desktop guidance requires session hosts to reach the documented service FQDNs and endpoints, even when the environment is otherwise tightly restricted.
| Component | Example | Purpose |
|---|---|---|
| Resource group | rg-dev-avd-isolated | AVD, networking, gateway, and supporting resources for the example. |
| Virtual network | vnet-dev-avd / 10.40.0.0/16 | Dedicated developer boundary or workload spoke. |
| AVD subnet | snet-avd-hosts / 10.40.10.0/24 | Personal session hosts; no public IPs. |
| Private endpoint subnet | snet-private-endpoints / 10.40.20.0/24 | Private endpoints for storage, Key Vault, package feeds, or other PaaS services. |
| Management subnet | snet-management / 10.40.30.0/24 | Optional management tools or private administration services. |
| GatewaySubnet | 10.40.254.0/27 | Reserved only for Azure VPN Gateway. |
| P2S client pool | 172.20.201.0/24 | Addresses assigned to connected VPN clients; this is not a VNet subnet. |
| Host pool | hp-dev-personal | Personal desktops with direct assignment. |
| Developer group | AVD-Developers | Receives desktop access and VM user-login rights. |
| VPN group | VPN-Dev-Admins | Explicitly authorized P2S VPN users or administrators. |
For a larger estate, place the VPN Gateway and Azure Firewall in a connectivity hub and peer the developer VNet as a spoke. For a small isolated environment, the single-VNet pattern below is easier to understand, but it should not become a reason to connect development and production with unrestricted peering.
Before deployment: make five decisions
- Personal or pooled: this guide uses a personal host pool because the isolation boundary is one VM per developer.
- Identity join: the example uses Microsoft Entra joined Windows 11 session hosts. Confirm application and policy compatibility before choosing it.
- Outbound control: NAT Gateway gives deterministic outbound IPs but does not filter destinations. Use Azure Firewall or another approved inspection layer when outbound allow-listing is required.
- VPN access model: use the Microsoft-registered Azure VPN Client audience for simple tenant authentication, or a custom audience plus Enterprise Application assignment when only selected users or groups should connect.
- Profile model: personal desktops can keep local state on the OS disk. Add FSLogix only when profile portability, rebuild behavior, or operational requirements justify it.
Also confirm subscription quota, regional VM availability, Azure Virtual Desktop licensing eligibility, naming standards, policy assignments, DNS ownership, the administrator break-glass method, and who approves developer access.
Step 1: create the resource group, VNet, and subnets
Run the following in Azure Cloud Shell with Bash or from a workstation with the current Azure CLI. The script intentionally creates the P2S address pool only as a variable. Do not create it as a subnet inside the VNet.
SUBSCRIPTION_ID="<subscription-id>"
LOCATION="centralus"
RG="rg-dev-avd-isolated"
VNET="vnet-dev-avd"
VNET_PREFIX="10.40.0.0/16"
AVD_SUBNET="snet-avd-hosts"
AVD_PREFIX="10.40.10.0/24"
PE_SUBNET="snet-private-endpoints"
PE_PREFIX="10.40.20.0/24"
MGMT_SUBNET="snet-management"
MGMT_PREFIX="10.40.30.0/24"
GATEWAY_PREFIX="10.40.254.0/27"
P2S_POOL="172.20.201.0/24"
az account set --subscription "$SUBSCRIPTION_ID"
az group create \
--name "$RG" \
--location "$LOCATION" \
--tags Environment=Development Workload=AVD Isolation=Dedicated
az network vnet create \
--resource-group "$RG" \
--name "$VNET" \
--location "$LOCATION" \
--address-prefixes "$VNET_PREFIX" \
--subnet-name "$AVD_SUBNET" \
--subnet-prefixes "$AVD_PREFIX"
az network vnet subnet create \
--resource-group "$RG" \
--vnet-name "$VNET" \
--name "$PE_SUBNET" \
--address-prefixes "$PE_PREFIX"
az network vnet subnet create \
--resource-group "$RG" \
--vnet-name "$VNET" \
--name "$MGMT_SUBNET" \
--address-prefixes "$MGMT_PREFIX"
az network vnet subnet create \
--resource-group "$RG" \
--vnet-name "$VNET" \
--name "GatewaySubnet" \
--address-prefixes "$GATEWAY_PREFIX"
Validation: confirm that the VNet, all four subnets, and the planned P2S pool do not overlap any peered VNet, on-premises network, home-office range that must communicate through the VPN, or future expansion range.
Step 2: apply subnet security and controlled egress
Azure automatically creates system routes between subnets in the same VNet. Separate subnet names alone therefore do not block traffic. Apply NSGs, and use a firewall or network virtual appliance when you need centralized east-west and outbound inspection.
Create an NSG for AVD session hosts
AVD_NSG="nsg-avd-hosts"
az network nsg create \
--resource-group "$RG" \
--name "$AVD_NSG" \
--location "$LOCATION"
az network vnet subnet update \
--resource-group "$RG" \
--vnet-name "$VNET" \
--name "$AVD_SUBNET" \
--network-security-group "$AVD_NSG"
Do not add a general inbound RDP rule for Azure Virtual Desktop users. AVD user sessions are brokered by the service and do not require public inbound TCP 3389. For private troubleshooting, prefer Azure Bastion or a narrowly scoped P2S rule to specific administrative targets.
Optional: permit RDP only from the P2S pool
az network nsg rule create \
--resource-group "$RG" \
--nsg-name "$AVD_NSG" \
--name "Allow-RDP-From-P2S-Admins" \
--priority 200 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes "$P2S_POOL" \
--source-port-ranges "*" \
--destination-address-prefixes "$AVD_PREFIX" \
--destination-port-ranges 3389
Use that rule only when RDP is an approved support path. A stronger implementation targets an Application Security Group or a small management set rather than the entire AVD subnet.
Add NAT Gateway for deterministic outbound access
NAT Gateway provides a stable outbound public IP for the session-host subnet. It does not inspect or filter destinations, and it does not replace a VPN Gateway.
NAT_PIP="pip-nat-dev-avd"
NAT_GW="nat-dev-avd"
az network public-ip create \
--resource-group "$RG" \
--name "$NAT_PIP" \
--location "$LOCATION" \
--sku Standard \
--allocation-method Static
az network nat gateway create \
--resource-group "$RG" \
--name "$NAT_GW" \
--location "$LOCATION" \
--public-ip-addresses "$NAT_PIP" \
--idle-timeout 10
az network vnet subnet update \
--resource-group "$RG" \
--vnet-name "$VNET" \
--name "$AVD_SUBNET" \
--nat-gateway "$NAT_GW"
When the security requirement is outbound allow-listing, route the AVD subnet through Azure Firewall or another approved control and allow the current Azure Virtual Desktop required FQDNs and endpoints. Validate the first host with the Azure Virtual Desktop Agent URL Tool before scaling out.
Step 3: create the Azure VPN Gateway
The example uses an active-standby, route-based VpnGw1AZ gateway with a Standard static public IP. Confirm regional availability, capacity, availability requirements, and cost before using the example SKU.
VPN_PIP="pip-vgw-dev-avd"
VPN_GW="vgw-dev-avd"
az network public-ip create \
--resource-group "$RG" \
--name "$VPN_PIP" \
--location "$LOCATION" \
--sku Standard \
--allocation-method Static \
--version IPv4 \
--zone 1 2 3
az network vnet-gateway create \
--resource-group "$RG" \
--name "$VPN_GW" \
--location "$LOCATION" \
--vnet "$VNET" \
--public-ip-addresses "$VPN_PIP" \
--gateway-type Vpn \
--vpn-type RouteBased \
--sku VpnGw1AZ \
--vpn-gateway-generation Generation2 \
--no-wait
az network vnet-gateway show \
--resource-group "$RG" \
--name "$VPN_GW" \
--query "{state:provisioningState,sku:sku.name,gatewayType:gatewayType,vpnType:vpnType}" \
--output table
Gateway deployment commonly takes tens of minutes. Continue only after the provisioning state is Succeeded. Never attach NAT Gateway to GatewaySubnet, and do not deploy VMs or private endpoints into it.
Step 4: configure Point-to-Site VPN with Microsoft Entra ID
For Azure Public, the Microsoft-registered Azure VPN Client audience is c632b3df-fb67-4d84-bdcf-b95ad541b5c8. Microsoft Entra authentication for P2S uses OpenVPN and the Azure VPN Client. The client pool must not overlap the VNet or any network that must communicate through the tunnel.
TENANT_ID="<tenant-id>"
AAD_TENANT="https://login.microsoftonline.com/${TENANT_ID}"
AAD_ISSUER="https://sts.windows.net/${TENANT_ID}/"
AAD_AUDIENCE="c632b3df-fb67-4d84-bdcf-b95ad541b5c8"
az network vnet-gateway update \
--resource-group "$RG" \
--name "$VPN_GW" \
--address-prefixes "$P2S_POOL" \
--client-protocol OpenVPN \
--vpn-auth-type AAD \
--aad-tenant "$AAD_TENANT" \
--aad-audience "$AAD_AUDIENCE" \
--aad-issuer "$AAD_ISSUER"
The Issuer value must include the trailing slash. With the Microsoft-registered audience, the older manual Azure VPN Client registration and consent step is not required. Microsoft documents that a P2S gateway supports one Audience value at a time, so changing it requires a fresh client profile.
Step 5: restrict VPN access to assigned users or groups
Tenant authentication answers “is this a valid identity?” It does not by itself create a customer-specific authorization boundary for one gateway. For restricted access, create a custom audience application, authorize the Microsoft-registered Azure VPN Client to request its scope, require assignment on the Enterprise Application, assign the approved identities, and replace the gateway Audience with the custom Application (client) ID.
- In Microsoft Entra ID, create a single-tenant app registration such as
app-p2s-vpn-dev. Leave Redirect URI empty and do not create a client secret. - On Expose an API, keep the generated Application ID URI and add an enabled scope named
p2s-vpnwith admin-only consent. - Choose Add a client application. Enter
c632b3df-fb67-4d84-bdcf-b95ad541b5c8and authorize thep2s-vpnscope. - Open the corresponding Enterprise Application, set Assignment required to Yes, and assign only the approved users or security group.
- Return to the VPN Gateway and replace the Audience value with the custom app’s Application (client) ID. Keep Tenant, Issuer, OpenVPN, and the client pool unchanged.
- After the gateway update, download and distribute a new VPN client profile. Test with one assigned identity and one unassigned identity.
Group assignment is preferable for lifecycle management when the tenant licensing and group-assignment requirements are satisfied. Keep the VPN authorization group separate from the AVD desktop group unless every desktop user genuinely needs direct network access from a local device.
Step 6: create the Azure Virtual Desktop control plane
The example uses Azure PowerShell to create a personal host pool, a desktop application group, and a workspace. A personal pool provides one assigned desktop per developer and is the clearer compute-isolation boundary for machine-level development tools.
Install-Module Az.Accounts -Scope CurrentUser -Force
Install-Module Az.DesktopVirtualization -Scope CurrentUser -Force
Connect-AzAccount
Set-AzContext -Subscription "<subscription-id>"
$ResourceGroup = "rg-dev-avd-isolated"
$Location = "centralus"
$HostPoolName = "hp-dev-personal"
$AppGroupName = "dag-dev-personal"
$WorkspaceName = "ws-dev-avd"
$HostPool = New-AzWvdHostPool `
-ResourceGroupName $ResourceGroup `
-Name $HostPoolName `
-Location $Location `
-HostPoolType "Personal" `
-LoadBalancerType "Persistent" `
-PreferredAppGroupType "Desktop" `
-PersonalDesktopAssignmentType "Direct" `
-StartVMOnConnect `
-FriendlyName "Isolated Developer Desktops" `
-Description "Personal Azure Virtual Desktop host pool for isolated developer environments"
$AppGroup = New-AzWvdApplicationGroup `
-ResourceGroupName $ResourceGroup `
-Name $AppGroupName `
-Location $Location `
-HostPoolArmPath $HostPool.Id `
-ApplicationGroupType "Desktop" `
-ShowInFeed `
-FriendlyName "Developer Desktop"
$Workspace = New-AzWvdWorkspace `
-ResourceGroupName $ResourceGroup `
-Name $WorkspaceName `
-Location $Location `
-ApplicationGroupReference $AppGroup.Id `
-FriendlyName "Developer Workspace" `
-Description "Workspace for isolated developer desktops"
Create or identify a Microsoft Entra security group named AVD-Developers, then grant it the Desktop Virtualization User role on the application group. For Microsoft Entra joined session hosts, also grant Virtual Machine User Login on the session-host resource group or on the intended VMs.
$DeveloperGroupObjectId = "<AVD-Developers-group-object-id>"
New-AzRoleAssignment `
-ObjectId $DeveloperGroupObjectId `
-RoleDefinitionName "Desktop Virtualization User" `
-Scope $AppGroup.Id
New-AzRoleAssignment `
-ObjectId $DeveloperGroupObjectId `
-RoleDefinitionName "Virtual Machine User Login" `
-ResourceGroupName $ResourceGroup
Keep Virtual Machine Administrator Login in a separate support group. Desktop entitlement should not automatically make every developer an Azure VM administrator.
Step 7: add personal session hosts
For Microsoft Entra joined AVD session hosts, the most reliable manual path is the Azure Virtual Desktop portal or an approved ARM/Bicep deployment because the service adds the required AADLoginForWindows extension and registration configuration.
- Open Azure Virtual Desktop → Host pools → hp-dev-personal → Session hosts → Add.
- Select the same subscription, resource group, region, and VNet. Choose
snet-avd-hosts. - Use Windows 11 Enterprise single-session for one assigned developer per VM, unless a different supported image is required. Enable Trusted Launch, Secure Boot, and vTPM where the selected image and VM size support them.
- Set Directory to Microsoft Entra ID. Do not assign a public IP address.
- Choose a VM size from measured workload needs. Development workloads may require more memory, disk throughput, or nested virtualization features than an office desktop.
- Add one session host per developer for the initial pilot. Confirm the host registers as Available before adding more.
- Assign each developer directly to a personal desktop. A user assigned only to the host pool but not to a personal session host can receive a “No resources available” error.
- Run the Azure Virtual Desktop Agent URL Tool on the first host and correct outbound connectivity before scaling the pool.
Use a golden image or Azure Compute Gallery for repeatable delivery. Do not treat an interactively modified developer VM as the production image pipeline. Build the image, install approved agents and base tools, patch it, validate it, generalize it, publish a version, and replace hosts through a controlled process.
Step 8: apply developer-specific isolation controls
Separate the desktop from production
- Do not peer the development VNet directly to production unless an approved traffic matrix requires it.
- When connectivity is required, route through Azure Firewall or an approved inspection point and permit only the destination services and ports that the development workflow needs.
- Use separate private DNS zones or carefully controlled links so development hosts do not automatically resolve every production private endpoint.
- Keep production credentials, deployment service connections, and data-access roles out of the developer desktop. Use workload identities, managed identities, and just-in-time elevation where possible.
Control local administrator rights
Some development tools require machine-level changes. Granting permanent local administrator rights increases the impact of a compromised session. Prefer managed installation, Endpoint Privilege Management, or a time-bound support process. When local admin is unavoidable, personal hosts reduce cross-user impact but do not replace endpoint protection, application control, logging, or least privilege.
Control source code and build artifacts
- Keep authoritative source in the approved repository, not only on the VM OS disk.
- Use separate repositories, branches, and service connections for development and production.
- Store secrets in Key Vault or the approved secret platform. Do not bake credentials into the image, scripts, VPN profile, or FSLogix container.
- Place private package feeds and artifact stores behind private endpoints when the platform supports it, and grant access through identity rather than shared keys.
Use personal desktops carefully with containers and WSL
A personal desktop is the better starting point when developers require WSL, container engines, kernel-level tooling, or conflicting SDKs because each developer receives a dedicated VM. Confirm the VM size, image, nested-virtualization capability, endpoint-security compatibility, and licensing for the exact toolchain. AVD isolation does not automatically make every local container workload supportable.
Step 9: decide whether to add FSLogix
Personal desktops can persist user state on their OS disks, so FSLogix is not automatically required. Use it when the profile must survive host replacement or follow the user to another host. For large source trees, package caches, container layers, and build outputs, avoid turning the profile container into a high-churn build disk.
When FSLogix is required, Azure Files with Microsoft Entra Kerberos can support hybrid or cloud-only identities when current prerequisites are met. Use a private endpoint and private DNS, grant both share-level Azure RBAC and file-system ACLs, and configure every session host consistently. The two permission layers are independent: successful Kerberos authentication does not guarantee file authorization.
$SharePath = "\\<storage-account>.file.core.windows.net\<profile-share>"
$KerberosPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters"
New-Item -Path $KerberosPath -Force | Out-Null
New-ItemProperty -Path $KerberosPath `
-Name CloudKerberosTicketRetrievalEnabled `
-PropertyType DWord -Value 1 -Force | Out-Null
$AzureADPath = "HKLM:\SOFTWARE\Policies\Microsoft\AzureADAccount"
New-Item -Path $AzureADPath -Force | Out-Null
New-ItemProperty -Path $AzureADPath `
-Name LoadCredKeyFromProfile `
-PropertyType DWord -Value 1 -Force | Out-Null
$ProfilePath = "HKLM:\SOFTWARE\FSLogix\Profiles"
New-Item -Path $ProfilePath -Force | Out-Null
New-ItemProperty -Path $ProfilePath -Name Enabled `
-PropertyType DWord -Value 1 -Force | Out-Null
New-ItemProperty -Path $ProfilePath -Name VHDLocations `
-PropertyType MultiString -Value $SharePath -Force | Out-Null
New-ItemProperty -Path $ProfilePath -Name VolumeType `
-PropertyType String -Value "VHDX" -Force | Out-Null
Validate DNS, TCP 445, the user’s Primary Refresh Token, a CIFS Kerberos ticket, the FSLogix service, profile attachment, and sign-in on a second test host before declaring the profile design ready.
Step 10: download and test the Azure VPN Client profile
After the final VPN Gateway Audience is configured, download a fresh client package. Import AzureVPN\azurevpnconfig.xml into the current Azure VPN Client. The Windows client can also be installed with Windows Package Manager.
winget install Microsoft.AzureVPNClient --source winget
- Connect with an assigned VPN user and confirm the client receives an address from
172.20.201.0/24. - Confirm an unassigned identity is denied when a custom audience and Assignment required are configured.
- Run
route printand confirm the VNet route is present. - Test only the approved destination and port, for example
Test-NetConnection 10.40.10.10 -Port 3389for a private support path. - Review the destination NSG, UDR, Azure Firewall policy, and Windows Firewall if the VPN connects but the service is unreachable.
- Review Microsoft Entra sign-in logs for the custom Enterprise Application.
Step 11: validate the complete environment
| Area | Test | Expected result |
|---|---|---|
| AVD entitlement | Assigned developer refreshes the workspace feed. | The Developer Desktop is visible. |
| Personal assignment | Developer launches the desktop. | Connection reaches only the assigned session host. |
| Negative identity test | Unassigned user attempts AVD access. | No desktop is presented. |
| Session-host network | Run the AVD Agent URL Tool. | All required service checks pass. |
| Public exposure | Review NICs, load balancers, and NSGs. | No session host has a public IP or internet-exposed RDP rule. |
| VPN positive test | Assigned VPN user connects. | Connected; P2S address and VNet routes are present. |
| VPN negative test | Unassigned user connects. | Microsoft Entra denies the request. |
| Segmentation | Test a prohibited development-to-production path. | Connection is blocked and, where configured, logged. |
| Approved private access | Test required repository, package feed, Key Vault, or build service. | Only approved services and ports succeed. |
| Rebuild | Replace one pilot host from the approved image. | Developer can reconnect and recover required state through the defined profile/repository process. |
Common failure patterns
| Symptom | Likely cause | Corrective action |
|---|---|---|
| Session host deploys but never becomes Available | Required AVD endpoint blocked, expired registration token, or agent/extension issue. | Validate the subnet with the AVD Agent URL Tool, review agent logs, and recreate a short-lived token if using manual registration. |
| Developer sees No resources available | Personal host pool access exists, but no personal desktop is assigned. | Assign the user to a specific session host and refresh the feed. |
| VPN token or invalid resource error | Gateway Audience and client profile do not match, or the Microsoft VPN Client is not authorized for the custom scope. | Correct the custom app, update the gateway, and import a fresh profile. |
| VPN connects but private VM is unreachable | NSG, Windows Firewall, UDR, Azure Firewall, wrong IP, or overlapping routes. | Test the private IP and port, inspect routes, then validate each control in order. |
| Session host has no outbound connectivity | NAT Gateway is absent, a UDR points to an unavailable firewall, or required FQDNs are blocked. | Restore the intended egress path and validate required AVD endpoints. |
| Subnet separation did not block traffic | Azure system routes still permit east-west connectivity. | Add explicit NSG/firewall controls and test negative paths. |
| One developer’s tools affect another | A pooled multi-session host was used for machine-level development tools. | Move the workload to personal desktops or dedicated VMs. |
| FSLogix authenticates but access is denied | Share RBAC or NTFS-style ACLs do not authorize the user. | Validate both permission layers, group membership, and token freshness. |
Operational ownership after deployment
Isolation degrades when nobody owns the image, firewall rules, group membership, or old developer desktops. Define owners and review intervals before production rollout.
- Platform team: host pools, images, session-host lifecycle, scaling, diagnostics, and AVD service health.
- Network team: address space, NSGs, route tables, firewall policy, private DNS, VPN Gateway, and client-profile distribution.
- Identity team: Conditional Access, AVD and VPN groups, Enterprise Application assignment, privileged roles, and access reviews.
- Security team: endpoint protection, application control, vulnerability management, logging, and incident response.
- Development leadership: approved toolchain, data classification, repository controls, production-access boundaries, and user acceptance.
For cost control, personal hosts can use Start VM on Connect and a deliberate shutdown schedule, but cost automation must not conflict with patching, builds, or long-running jobs. Test the developer experience and define who may exclude a host from automation.
Practical exit criteria
- Each pilot developer receives only the intended personal desktop.
- No session host exposes a public IP or broad internet-facing management port.
- Required AVD service endpoints pass from the session-host subnet.
- Assigned VPN users connect and unassigned users are denied.
- Development-to-production traffic is blocked by default and only approved flows are permitted.
- Source, secrets, profiles, and build artifacts follow documented storage and recovery paths.
- A replacement host can be created from the approved image without undocumented manual steps.
- Platform, network, identity, security, and development owners accept their operational responsibilities.
Final recommendation
Start with one personal Azure Virtual Desktop host per pilot developer, a dedicated development VNet or spoke, no public IPs, controlled outbound connectivity, and separate Microsoft Entra groups for desktop access, VPN access, and administration. Treat Point-to-Site VPN as a private network path—not as the AVD sign-in mechanism and not as internet egress for session hosts. Add Private Link, Azure Firewall, FSLogix, and broader automation only where the threat model and operating requirements justify the added complexity.
BI Cloud Tech can help review the design boundary across Azure Virtual Desktop architecture, Azure networking and connectivity, and Microsoft Entra identity and access. For an architecture review or implementation plan, contact BI Cloud Tech.
