Microsoft Foundry Series (Part 2) โ€” Bicep Deep Dive: Private Endpoints, RBAC, Deployment Types and Region Selection

In Part 1 we provisioned a Foundry resource, deployed GPT-4.1 mini, and made our first API call from Python. The Bicep template was intentionally minimal โ€” public network, no RBAC, a single deployment type.

Post hero

In production you need more. This article covers:

  1. Deployment types โ€” Global Standard vs Data Zone vs Regional and when to pick which
  2. Region selection โ€” how to choose where your Foundry resource lives
  3. Networking โ€” locking down access with private endpoints
  4. RBAC โ€” granting least-privilege access with Foundry roles
  5. Reusable Bicep modules โ€” structuring your IaC for multi-environment deployments

All code samples from this series are available in this repository (coming soon).

Deployment types explained #

When you deploy a model in Foundry, you pick a deployment type that controls three things: where data is processed, how you pay, and performance characteristics.

Deployment typeSKU name in BicepData processingBillingBest for
Global StandardGlobalStandardAny Azure regionPay-per-tokenGeneral workloads, highest quota
Global ProvisionedGlobalProvisionedManagedAny Azure regionReserved PTUPredictable high-throughput
Global BatchGlobalBatchAny Azure region50% discountLarge async jobs (24-hr target)
Data Zone StandardDataZoneStandardWithin data zone (EU/US)Pay-per-tokenEU/US data residency compliance
Data Zone ProvisionedDataZoneProvisionedManagedWithin data zone (EU/US)Reserved PTUData zone + predictable throughput
Standard (Regional)StandardSingle regionPay-per-tokenStrict regional compliance, low volume
Regional ProvisionedProvisionedManagedSingle regionReserved PTURegional compliance + guaranteed throughput

Full details: Deployment types for Microsoft Foundry Models

How to choose #

 1                          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 2                          โ”‚ Data residency       โ”‚
 3                          โ”‚ requirements?        โ”‚
 4                          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜
 5                                 โ”‚          โ”‚
 6                            No limits    EU/US only    Single region
 7                                 โ”‚          โ”‚               โ”‚
 8                                 โ–ผ          โ–ผ               โ–ผ
 9                           Global *    DataZone *      Standard /
10                                                    Regional Provisioned
11                                 โ”‚          โ”‚
12                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
13                    โ”‚               โ”‚  โ”‚             โ”‚
14              Variable        Steady   Variable   Steady
15              traffic        traffic   traffic    traffic
16                    โ”‚               โ”‚  โ”‚             โ”‚
17                    โ–ผ               โ–ผ  โ–ผ             โ–ผ
18              Global          Global   DataZone   DataZone
19              Standard     Provisioned Standard  Provisioned

For our product description generator, Global Standard is the simplest starting point โ€” highest quota, widest model availability, pay-per-token. If you need EU data residency (e.g. GDPR), switch to DataZone Standard deployed in a European region.

Bicep: switching deployment types #

In Part 1 we used Standard. Here’s how to switch to GlobalStandard or DataZoneStandard:

 1@description('Deployment type SKU')
 2@allowed([
 3  'Standard'
 4  'GlobalStandard'
 5  'DataZoneStandard'
 6])
 7param deploymentSku string = 'GlobalStandard'
 8
 9resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = {
10  parent: foundry
11  name: modelName
12  sku: {
13    name: deploymentSku
14    capacity: modelCapacity
15  }
16  properties: {
17    model: {
18      name: modelName
19      format: 'OpenAI'
20      version: modelVersion
21    }
22  }
23}

Choosing a region #

Two factors matter:

  1. Latency to your users โ€” use Azure Speed Test to measure round-trip time from your location to candidate regions.
  2. Model availability โ€” not every model is available in every region. Check the region availability matrix before committing.

For Global Standard deployments, the region of your Foundry resource matters less (requests can be processed anywhere), but it still determines where data is stored at rest. For Standard (Regional) deployments, the region determines both storage and processing โ€” model availability is more limited.

Here’s a quick snapshot of GPT model availability across European regions for Standard (Regional) deployments:

ModelFrance CentralSweden CentralUK SouthNorth EuropeWest Europe
gpt-4.1โ€”โœ…โ€”โ€”โ€”
gpt-4.1-miniโ€”โœ…โœ…โ€”โ€”
gpt-4oโœ…โœ…โœ…โ€”โ€”
gpt-5.1โ€”โœ…โ€”โ€”โ€”

Sweden Central has the broadest model selection in Europe for regional deployments. If you use Global Standard or Data Zone Standard, all major models are available regardless of resource region.

Bicep: parameterise the region #

1@description('Azure region for the Foundry resource')
2@allowed([
3  'swedencentral'
4  'francecentral'
5  'uksouth'
6  'eastus'
7  'eastus2'
8])
9param location string = 'swedencentral'

Locking down networking with private endpoints #

By default, Foundry resources are accessible from the public internet. In production, you should restrict access to your virtual network.

Architecture #

 1โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 2โ”‚  Your VNet (10.0.0.0/16)                 โ”‚
 3โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                  โ”‚
 4โ”‚  โ”‚ Subnet: default    โ”‚                  โ”‚
 5โ”‚  โ”‚ 10.0.1.0/24        โ”‚                  โ”‚
 6โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”‚                  โ”‚
 7โ”‚  โ”‚  โ”‚ Your App โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ–บ Private       โ”‚
 8โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ”‚    Endpoint      โ”‚
 9โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    (10.0.2.x)    โ”‚
10โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚          โ”‚
11โ”‚  โ”‚ Subnet: pe-subnet  โ”‚       โ”‚          โ”‚
12โ”‚  โ”‚ 10.0.2.0/24        โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”˜          โ”‚
13โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                  โ”‚
14โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
15           โ”‚ Private Link
16           โ–ผ
17โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
18โ”‚  Microsoft Foundry                       โ”‚
19โ”‚  publicNetworkAccess: Disabled           โ”‚
20โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Bicep: VNet + Private Endpoint #

 1@description('Enable private networking')
 2param enablePrivateEndpoint bool = false
 3
 4// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 5// Virtual Network
 6// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 7resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' = if (enablePrivateEndpoint) {
 8  name: '${baseName}-vnet'
 9  location: location
10  properties: {
11    addressSpace: {
12      addressPrefixes: ['10.0.0.0/16']
13    }
14    subnets: [
15      {
16        name: 'default'
17        properties: {
18          addressPrefix: '10.0.1.0/24'
19        }
20      }
21      {
22        name: 'pe-subnet'
23        properties: {
24          addressPrefix: '10.0.2.0/24'
25        }
26      }
27    ]
28  }
29}
30
31// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
32// Private Endpoint
33// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
34resource privateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = if (enablePrivateEndpoint) {
35  name: '${baseName}-pe'
36  location: location
37  properties: {
38    subnet: {
39      id: vnet.properties.subnets[1].id
40    }
41    privateLinkServiceConnections: [
42      {
43        name: '${baseName}-plsc'
44        properties: {
45          privateLinkServiceId: foundry.id
46          groupIds: ['account']
47        }
48      }
49    ]
50  }
51}
52
53// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
54// Private DNS Zone
55// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
56resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = if (enablePrivateEndpoint) {
57  name: 'privatelink.cognitiveservices.azure.com'
58  location: 'global'
59}
60
61resource dnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = if (enablePrivateEndpoint) {
62  parent: privateDnsZone
63  name: '${baseName}-dns-link'
64  location: 'global'
65  properties: {
66    virtualNetwork: {
67      id: vnet.id
68    }
69    registrationEnabled: false
70  }
71}
72
73resource dnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = if (enablePrivateEndpoint) {
74  parent: privateEndpoint
75  name: 'default'
76  properties: {
77    privateDnsZoneConfigs: [
78      {
79        name: 'config'
80        properties: {
81          privateDnsZoneId: privateDnsZone.id
82        }
83      }
84    ]
85  }
86}

Then update the Foundry resource to disable public access when private endpoints are enabled:

 1resource foundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = {
 2  name: '${baseName}-ai'
 3  location: location
 4  kind: 'AIServices'
 5  sku: {
 6    name: 'S0'
 7  }
 8  identity: {
 9    type: 'SystemAssigned'
10  }
11  properties: {
12    customSubDomainName: '${baseName}-ai'
13    allowProjectManagement: true
14    publicNetworkAccess: enablePrivateEndpoint ? 'Disabled' : 'Enabled'
15  }
16}

Deploy with private networking:

1az deployment group create \
2  --resource-group rg-foundry-demo \
3  --template-file main.bicep \
4  --parameters enablePrivateEndpoint=true

RBAC: least-privilege access #

Foundry introduces dedicated roles that map to common personas:

RoleRole ID (GUID)WhoCan do
Foundry Account Ownere47c6f54-e4a2-4754-9501-8e0985b135e1Platform engineersFull control over the Foundry resource and projects
Foundry Ownerc883944f-8b7b-4483-af10-35834be79c4aProject leadsManage a specific project โ€” models, agents, deployments
Foundry User53ca6127-db72-4b80-b1b0-d745d6d5456dDevelopersUse deployed models and agents, run experiments
Foundry Project Managereadc314b-1a2d-4efa-be10-5d325db5065eTeam managersManage project membership and settings

Bicep: assign roles #

 1@description('Principal ID of the developer who needs access')
 2param developerPrincipalId string = ''
 3
 4// Foundry User role
 5var foundryUserRoleId = '53ca6127-db72-4b80-b1b0-d745d6d5456d'
 6
 7resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(developerPrincipalId)) {
 8  name: guid(project.id, developerPrincipalId, foundryUserRoleId)
 9  scope: project
10  properties: {
11    roleDefinitionId: subscriptionResourceId(
12      'Microsoft.Authorization/roleDefinitions',
13      foundryUserRoleId
14    )
15    principalId: developerPrincipalId
16    principalType: 'User'
17  }
18}

Deploy with RBAC:

1# Get your own principal ID
2MY_ID=$(az ad signed-in-user show --query id -o tsv)
3
4az deployment group create \
5  --resource-group rg-foundry-demo \
6  --template-file main.bicep \
7  --parameters developerPrincipalId=$MY_ID

Structuring Bicep for reuse #

As the template grows, split it into modules. Here’s a recommended structure:

1infra/
2โ”œโ”€โ”€ main.bicep              # Orchestrator
3โ”œโ”€โ”€ main.bicepparam         # Parameter file
4โ”œโ”€โ”€ modules/
5โ”‚   โ”œโ”€โ”€ foundry.bicep       # Foundry resource + project
6โ”‚   โ”œโ”€โ”€ model.bicep         # Model deployment
7โ”‚   โ”œโ”€โ”€ networking.bicep    # VNet + Private Endpoint + DNS
8โ”‚   โ””โ”€โ”€ rbac.bicep          # Role assignments

main.bicep (orchestrator) #

 1targetScope = 'resourceGroup'
 2
 3param baseName string = 'foundry-demo'
 4param location string = 'swedencentral'
 5param modelName string = 'gpt-4.1-mini'
 6param modelVersion string = '2025-04-14'
 7param deploymentSku string = 'GlobalStandard'
 8param modelCapacity int = 10
 9param enablePrivateEndpoint bool = false
10param developerPrincipalId string = ''
11
12module foundryModule 'modules/foundry.bicep' = {
13  name: 'foundry'
14  params: {
15    baseName: baseName
16    location: location
17    enablePrivateEndpoint: enablePrivateEndpoint
18  }
19}
20
21module modelModule 'modules/model.bicep' = {
22  name: 'model'
23  params: {
24    foundryName: foundryModule.outputs.foundryName
25    modelName: modelName
26    modelVersion: modelVersion
27    deploymentSku: deploymentSku
28    modelCapacity: modelCapacity
29  }
30}
31
32module networkingModule 'modules/networking.bicep' = if (enablePrivateEndpoint) {
33  name: 'networking'
34  params: {
35    baseName: baseName
36    location: location
37    foundryId: foundryModule.outputs.foundryId
38  }
39}
40
41module rbacModule 'modules/rbac.bicep' = if (!empty(developerPrincipalId)) {
42  name: 'rbac'
43  params: {
44    projectId: foundryModule.outputs.projectId
45    developerPrincipalId: developerPrincipalId
46  }
47}
48
49output projectEndpoint string = foundryModule.outputs.projectEndpoint

Parameter file: main.bicepparam #

1using 'main.bicep'
2
3param baseName = 'foundry-demo'
4param location = 'swedencentral'
5param modelName = 'gpt-4.1-mini'
6param deploymentSku = 'GlobalStandard'
7param enablePrivateEndpoint = false

Deploy with the parameter file:

1az deployment group create \
2  --resource-group rg-foundry-demo \
3  --template-file infra/main.bicep \
4  --parameters infra/main.bicepparam

Restricting deployment types with Azure Policy #

If your organisation wants to enforce that only DataZoneStandard deployments are allowed (e.g. for GDPR), you can apply an Azure Policy:

 1{
 2  "mode": "All",
 3  "policyRule": {
 4    "if": {
 5      "allOf": [
 6        {
 7          "field": "type",
 8          "equals": "Microsoft.CognitiveServices/accounts/deployments"
 9        },
10        {
11          "field": "Microsoft.CognitiveServices/accounts/deployments/sku.name",
12          "notIn": ["DataZoneStandard", "DataZoneProvisionedManaged"]
13        }
14      ]
15    },
16    "then": {
17      "effect": "deny"
18    }
19  }
20}

This prevents anyone from creating Global or Regional deployments โ€” only Data Zone SKUs are allowed.

Clean up #

1az group delete --name rg-foundry-demo --yes --no-wait

What’s next? #

In Part 3 we will explore the Foundry model catalog โ€” comparing GPT-4.1, GPT-5, and open-weight models โ€” benchmarks, pricing, and guidance on which model to pick for your use case.

Full series outline #

#Topic
1Getting started โ€” Provision with Bicep, deploy GPT, generate descriptions
2Bicep deep dive โ€” networking, RBAC, deployment types, region selection (this post)
3Foundry model catalog โ€” comparing GPT-4.1, GPT-5, open-weight models and when to use what
4Foundry services overview โ€” agents, Responses API, tools, memory and real-world use cases
5Prompt engineering and structured JSON output for product descriptions
6Building the Python API โ€” FastAPI backend with Foundry SDK
7Adding a database โ€” product catalog with PostgreSQL and RAG via Azure AI Search
8Content safety, guardrails and Responsible AI
9Building the Vue.js frontend โ€” a full-stack product description generator
10CI/CD with GitLab, cost optimization and monitoring

Stay tuned!

comments powered by Disqus