This guide focuses on configuration concepts for the AI Landing Zone.
Important: This repository deploys using Bicep parameter files, not
infra/main.parameters.json.
- Primary parameters file:
infra/main.bicepparam- AI Landing Zone submodule parameters file (if you deploy it directly):
submodules/ai-landing-zone/main.parameters.jsonFabric options in this repo are configured in
infra/main.bicepparamvia:
fabricCapacityPreset(create|byo|none) — driven by thefabricCapacityModeenv variablefabricWorkspacePreset(create|byo|none) — mirrorsfabricCapacityPresetby default- BYO inputs:
fabricCapacityResourceId(env),FABRIC_WORKSPACE_ID(env),FABRIC_WORKSPACE_NAME(env)
Deployment flow: This repo deploys the AI Landing Zone submodule from
submodules/ai-landing-zone/main.bicepduring the preprovision hook. The single source of truth for parameters isinfra/main.bicepparam.
| Mode | Description |
|---|---|
create |
Provisions a new Fabric capacity (Bicep) and workspace (postprovision script) |
byo |
Reuses an existing Fabric capacity and workspace — no new resources created |
none |
Disables all Fabric automation; OneLake indexing will be skipped |
Both capacity and workspace modes are controlled by the same fabricCapacityMode environment variable (they are tied together in infra/main.bicepparam).
The recommended way to configure Fabric mode is with azd env set — these values are read directly by infra/main.bicepparam at provision time:
# Choose one:
azd env set fabricCapacityMode create # create new capacity + workspace (default if not set)
azd env set fabricCapacityMode byo # reuse existing capacity + workspace
azd env set fabricCapacityMode none # disable all Fabric automationWhen fabricCapacityMode is byo, supply the identifiers of your existing resources:
# ARM resource ID of the existing Fabric capacity
azd env set fabricCapacityResourceId "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Fabric/capacities/<capacity-name>"
# GUID of the existing Fabric workspace (from the workspace URL)
azd env set FABRIC_WORKSPACE_ID "<workspace-guid>"
# Display name of the existing workspace (optional, used for naming/UX)
azd env set FABRIC_WORKSPACE_NAME "<workspace-display-name>"How to find the workspace GUID: Open the workspace in app.fabric.microsoft.com. The URL segment after
/groups/is the GUID (e.g.,https://app.fabric.microsoft.com/groups/e9c7ed61-0cdc-4356-a239-9d49cc755fe0/...).How to find the capacity resource ID: Azure Portal → Fabric capacity resource → Properties → Resource ID.
You can also set these directly in infra/main.bicepparam if you prefer source-controlled values:
// infra/main.bicepparam
var fabricCapacityPreset = 'byo'
param fabricCapacityResourceId = '/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Fabric/capacities/<name>'
param fabricWorkspaceId = '<workspace-guid>'
param fabricWorkspaceName = '<workspace-display-name>'Note: Values set via
azd env settake precedence over hardcoded bicepparam values becausereadEnvironmentVariable(...)is evaluated at deploy time.
When fabricCapacityMode is create, you must provide at least one admin principal:
// infra/main.bicepparam
param fabricCapacityAdmins = ['user@contoso.com']
param fabricCapacitySku = 'F2' // adjust SKU as neededPermission requirement: The identity running
azdmust have the Fabric Administrator role (or Power BI tenant admin) to call the workspace admin APIs used during postprovision.
By default the wrapper sets deployLogAnalytics = false, so the AI Landing Zone does not create a new Log Analytics workspace and Application Insights is not provisioned. If you already have a centralized Log Analytics workspace (for example one shared across the platform), you can wire the deployed Foundry application and the wrapper-managed PostgreSQL Flexible Server to it.
When you set existingLogAnalyticsWorkspaceResourceId:
- An Application Insights component is created in the deployment resource group and linked to your existing workspace via
WorkspaceResourceId— only whendeployAppInsights = trueanddeployLogAnalytics = false(the wrapper defaults). Its name follows the sameappInsightsNameconvention (appi-<resourceToken>). - PostgreSQL Flexible Server diagnostic settings (all logs + AllMetrics) are routed to your workspace (only when PostgreSQL is deployed by the wrapper).
- The connection string and instrumentation key are exposed as deployment outputs (when the Application Insights component is created) so post-provision automation (or your application configuration) can pick them up.
Note: This is wrapper-side wiring. The upstream AI Landing Zone submodule does not natively support a BYO Log Analytics workspace, so leave
deployLogAnalytics = falseanddeployAppInsights = true(the defaults) when using BYO so the LAZ does not create its own workspace + Application Insights pair.
azd env set EXISTING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.OperationalInsights/workspaces/<workspace-name>"Or set it directly in infra/main.bicepparam:
param existingLogAnalyticsWorkspaceResourceId = '/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.OperationalInsights/workspaces/<workspace-name>'| Output | Description |
|---|---|
existingLogAnalyticsWorkspaceResourceIdOut |
Echo of the supplied workspace resource ID |
byoApplicationInsightsResourceId |
Resource ID of the App Insights component created against the BYO workspace |
byoApplicationInsightsName |
Name of the App Insights component |
byoApplicationInsightsConnectionString |
Connection string for app instrumentation |
byoApplicationInsightsInstrumentationKey |
Instrumentation key for legacy SDKs |
Sensitive outputs: The connection string and instrumentation key are bootstrap credentials for sending telemetry to your Application Insights resource. They are emitted as deployment outputs so post-provision scripts and
azdenv can wire them into application configuration. Anyone with read access to the deployment history (subscription/RGMicrosoft.Resources/deployments/read) can retrieve these values — keep that access scoped appropriately.
The identity running the deployment needs permission to attach diagnostic settings to the workspace and to create the Application Insights component:
Microsoft.Insights/diagnosticSettings/writeon the BYO Log Analytics workspace (or its resource group). The built-in Log Analytics Contributor role on the workspace (or its RG) covers this — there is no need to grant subscription-wide rights.Microsoft.Insights/components/writeon the deployment resource group (covered by Contributor on the deployment RG, which the deployment identity already needs to provision the rest of the stack).- The PostgreSQL Flexible Server that emits diagnostics is wrapper-managed in the deployment RG, so no additional cross-resource permissions are required.
- Basic Parameters
- Deployment Toggles
- Network Configuration
- Microsoft Foundry Configuration
- Individual Service Configuration
- Common Customization Examples
Type: string
Default: ${AZURE_LOCATION=eastus2}
Description: Azure region where all resources will be deployed.
"location": {
"value": "${AZURE_LOCATION=westus2}"
}Set via azd:
azd env set AZURE_LOCATION westus2Available regions (check AI service availability):
eastus,eastus2,westus,westus2,centralusnortheurope,westeuropeaustraliaeast,southeastasia
Type: string
Default: ${AZURE_ENV_NAME}
Description: Base name used to generate resource names.
"baseName": {
"value": "${AZURE_ENV_NAME}"
}Set via azd:
azd env new my-ai-app # baseName becomes "my-ai-app"Results in resource names like:
rg-my-ai-appkv-my-ai-app-xyzacr-my-ai-app-xyz
Type: object
Default: Environment-specific tags
Description: Tags applied to all resources.
"tags": {
"value": {
"azd-env-name": "${AZURE_ENV_NAME}",
"environment": "production",
"project": "ai-application",
"cost-center": "engineering",
"owner": "ai-team"
}
}Each toggle controls whether a service is created. Set to true to deploy, false to skip.
"deployToggles": {
"value": {
"logAnalytics": true, // Log Analytics Workspace
"appInsights": true, // Application Insights
"virtualNetwork": true // Virtual Network
}
}"cosmosDb": true, // Azure Cosmos DB
"keyVault": true, // Azure Key Vault
"searchService": true, // Azure AI Search
"storageAccount": true // Storage AccountWhen to disable:
- Using existing Cosmos DB: set
cosmosDb: false+ provideresourceIds.cosmosDbResourceId - Using existing Key Vault: set
keyVault: false+ provideresourceIds.keyVaultResourceId
"containerEnv": true, // Container Apps Environment
"containerRegistry": true, // Azure Container Registry
"containerApps": false // Individual Container AppsNote: containerApps: false means no apps are deployed, but the environment is ready.
"appConfig": false, // Azure App Configuration
"apiManagement": false, // API Management
"applicationGateway": false, // Application Gateway
"applicationGatewayPublicIp": false,
"firewall": false, // Azure Firewall
"buildVm": false, // Linux build VM
"jumpVm": false, // Windows jump box
"bastionHost": false, // Azure Bastion
"groundingWithBingSearch": false, // Bing Search Service
"wafPolicy": false // Web Application FirewallWhen to enable:
apiManagement: true- For API gateway and rate limitingapplicationGateway: true- For load balancing and SSL terminationfirewall: true- For outbound traffic filteringbastionHost: true- For secure VM accessbuildVm: true- For CI/CD build agentsjumpVm: true- For Windows-based management
If you are using an existing Log Analytics workspace, set the resource ID in infra/main.bicepparam:
param logAnalyticsWorkspaceResourceId = '/subscriptions/<subId>/resourceGroups/<rg>/providers/Microsoft.OperationalInsights/workspaces/<name>'
"agentNsg": true, // NSG for agent/workload subnet
"peNsg": true, // NSG for private endpoints subnet
"acaEnvironmentNsg": true, // NSG for container apps subnet
"applicationGatewayNsg": false, // NSG for App Gateway subnet
"apiManagementNsg": false, // NSG for APIM subnet
"jumpboxNsg": false, // NSG for jumpbox subnet
"devopsBuildAgentsNsg": false, // NSG for build agents subnet
"bastionNsg": false // NSG for Bastion subnetRule: Enable NSG for any subnet you're using.
Required when: deployToggles.virtualNetwork: true
"vNetDefinition": {
"value": {
"name": "vnet-ai-landing-zone",
"addressPrefixes": [
"10.0.0.0/16"
],
"subnets": [
{
"name": "snet-agents",
"addressPrefix": "10.0.1.0/24",
"role": "agents"
},
{
"name": "snet-private-endpoints",
"addressPrefix": "10.0.2.0/24",
"role": "private-endpoints"
},
{
"name": "snet-container-apps",
"addressPrefix": "10.0.3.0/23",
"role": "container-apps-environment"
}
]
}
}| Role | Required | Purpose | Minimum Size |
|---|---|---|---|
agents |
✅ Yes | Workload VMs, compute | /26 (64 IPs) |
private-endpoints |
✅ Yes | Private endpoint NICs | /26 (64 IPs) |
container-apps-environment |
If containerEnv: true |
Container Apps | /23 (512 IPs) |
application-gateway |
If applicationGateway: true |
App Gateway | /27 (32 IPs) |
api-management |
If apiManagement: true |
APIM | /27 (32 IPs) |
jumpbox |
If jumpVm: true |
Jump VM | /28 (16 IPs) |
bastion |
If bastionHost: true |
Azure Bastion | /26 (64 IPs) |
devops-build-agents |
If buildVm: true |
Build VMs | /28 (16 IPs) |
"addressPrefixes": ["10.0.0.0/16"],
"subnets": [
{
"name": "snet-agents",
"addressPrefix": "10.0.1.0/26",
"role": "agents"
},
{
"name": "snet-private-endpoints",
"addressPrefix": "10.0.2.0/26",
"role": "private-endpoints"
}
]"addressPrefixes": ["10.0.0.0/16"],
"subnets": [
{
"name": "snet-agents",
"addressPrefix": "10.0.1.0/24",
"role": "agents"
},
{
"name": "snet-private-endpoints",
"addressPrefix": "10.0.2.0/24",
"role": "private-endpoints"
},
{
"name": "snet-container-apps",
"addressPrefix": "10.0.3.0/23",
"role": "container-apps-environment"
},
{
"name": "snet-app-gateway",
"addressPrefix": "10.0.5.0/27",
"role": "application-gateway"
},
{
"name": "snet-apim",
"addressPrefix": "10.0.6.0/27",
"role": "api-management"
},
{
"name": "snet-bastion",
"addressPrefix": "10.0.7.0/26",
"role": "bastion"
},
{
"name": "snet-jumpbox",
"addressPrefix": "10.0.8.0/28",
"role": "jumpbox"
},
{
"name": "snet-build-agents",
"addressPrefix": "10.0.9.0/28",
"role": "devops-build-agents"
}
]Controls Microsoft Foundry account/project and model deployments.
"aiFoundryDefinition": {
"value": {
"includeAssociatedResources": true,
"aiFoundryConfiguration": {
"disableLocalAuth": false
},
"aiModelDeployments": [...]
}
}Type: boolean
Default: true
Description: Create dedicated AI Search, Cosmos DB, Key Vault, and Storage for Microsoft Foundry.
Set to false if you want to use shared resources.
Type: boolean
Default: false
Description: Require Entra ID authentication (no API keys).
Set to true for maximum security in production.
Array of OpenAI models to deploy.
{
"name": "gpt-4o",
"model": {
"format": "OpenAI",
"name": "gpt-4o",
"version": "2024-08-06"
},
"sku": {
"name": "Standard",
"capacity": 10
}
}// GPT-4o (latest)
{
"name": "gpt-4o",
"model": {"format": "OpenAI", "name": "gpt-4o", "version": "2024-08-06"},
"sku": {"name": "Standard", "capacity": 10}
}
// GPT-4o mini (cost-effective)
{
"name": "gpt-4o-mini",
"model": {"format": "OpenAI", "name": "gpt-4o-mini", "version": "2024-07-18"},
"sku": {"name": "Standard", "capacity": 10}
}
// GPT-4 Turbo
{
"name": "gpt-4-turbo",
"model": {"format": "OpenAI", "name": "gpt-4", "version": "turbo-2024-04-09"},
"sku": {"name": "Standard", "capacity": 10}
}
// GPT-3.5 Turbo
{
"name": "gpt-35-turbo",
"model": {"format": "OpenAI", "name": "gpt-35-turbo", "version": "0125"},
"sku": {"name": "Standard", "capacity": 10}
}// text-embedding-3-small (recommended)
{
"name": "text-embedding-3-small",
"model": {"format": "OpenAI", "name": "text-embedding-3-small", "version": "1"},
"sku": {"name": "Standard", "capacity": 10}
}
// text-embedding-3-large (higher dimensions)
{
"name": "text-embedding-3-large",
"model": {"format": "OpenAI", "name": "text-embedding-3-large", "version": "1"},
"sku": {"name": "Standard", "capacity": 10}
}
// text-embedding-ada-002
{
"name": "text-embedding-ada-002",
"model": {"format": "OpenAI", "name": "text-embedding-ada-002", "version": "2"},
"sku": {"name": "Standard", "capacity": 10}
}// DALL-E 3
{
"name": "dall-e-3",
"model": {"format": "OpenAI", "name": "dall-e-3", "version": "3.0"},
"sku": {"name": "Standard", "capacity": 1}
}| Capacity | TPM (K) | Use Case |
|---|---|---|
| 1 | 1,000 | Development/testing |
| 10 | 10,000 | Small production |
| 50 | 50,000 | Medium production |
| 100 | 100,000 | Large production |
| 240 | 240,000 | Enterprise (max for Standard) |
Check quota:
az cognitiveservices account list-usage \
--name <account-name> \
--resource-group <rg-name>Use these in infra/main.bicepparam when deploying via this repo. postgreSqlNetworkIsolation defaults to networkIsolation.
param deployPostgreSql = true
param postgreSqlNetworkIsolation = networkIsolation
param postgreSqlAllowAzureServices = false
param postgreSqlMirrorConnectionMode = 'fabricUser'
param postgreSqlAuthConfig = {
activeDirectoryAuth: 'Enabled'
passwordAuth: 'Enabled'
}
When postgreSqlNetworkIsolation is false, PostgreSQL uses public access and does not create private endpoints or private DNS resources.
postgreSqlAllowAzureServices controls whether deployment also creates the PostgreSQL firewall rule that allows Azure services to connect (0.0.0.0 to 0.0.0.0). This is the declarative equivalent of the Azure portal Allow public access from any Azure service within Azure to this server setting.
Recommended combinations:
- Public/manual Fabric path:
postgreSqlNetworkIsolation = falseandpostgreSqlAllowAzureServices = true - Private/gateway path:
postgreSqlNetworkIsolation = trueandpostgreSqlAllowAzureServices = false
postgreSqlAuthConfig should remain set to both authentication modes enabled if you plan to configure Fabric mirroring after deployment. This ensures the server is created with password authentication available for the fabric_user connection instead of relying on a later hook to change the auth mode.
postgreSqlMirrorConnectionMode controls which credential the manual Fabric PostgreSQL connection should use after deployment:
fabricUseruses the dedicated least-privilege mirroring user andpostgres-fabric-user-password. This is the production-oriented default.adminuses the PostgreSQL admin login andpostgres-admin-password. This is intended for demo automation scenarios where you want to avoid creating a separate mirroring user.
"storageAccountDefinition": {
"value": {
"name": "stmyaiapp",
"sku": "Standard_LRS",
"allowBlobPublicAccess": false
}
}"keyVaultDefinition": {
"value": {
"name": "kv-myaiapp",
"enableRbacAuthorization": true,
"enablePurgeProtection": true,
"softDeleteRetentionInDays": 90
}
}"cosmosDbDefinition": {
"value": {
"name": "cosmos-myaiapp",
"sqlDatabases": [
{
"name": "chatdb",
"containers": [
{
"name": "conversations",
"partitionKeyPath": "/userId"
}
]
}
]
}
}"aiSearchDefinition": {
"value": {
"name": "search-myaiapp",
"sku": "standard",
"semanticSearch": "free"
}
}{
"location": {"value": "eastus2"},
"baseName": {"value": "dev-ai"},
"deployToggles": {
"value": {
"logAnalytics": true,
"appInsights": true,
"containerEnv": true,
"containerRegistry": true,
"cosmosDb": true,
"keyVault": true,
"storageAccount": true,
"searchService": true,
"virtualNetwork": true,
"agentNsg": true,
"peNsg": true,
"acaEnvironmentNsg": true,
// All others false
}
},
"aiFoundryDefinition": {
"value": {
"includeAssociatedResources": true,
"aiModelDeployments": [
{
"name": "gpt-4o-mini",
"model": {
"format": "OpenAI",
"name": "gpt-4o-mini",
"version": "2024-07-18"
},
"sku": {"name": "Standard", "capacity": 1}
}
]
}
}
}{
"location": {"value": "eastus2"},
"baseName": {"value": "prod-ai"},
"deployToggles": {
"value": {
"logAnalytics": true,
"appInsights": true,
"containerEnv": true,
"containerRegistry": true,
"cosmosDb": true,
"keyVault": true,
"storageAccount": true,
"searchService": true,
"virtualNetwork": true,
"apiManagement": true,
"applicationGateway": true,
"firewall": true,
"bastionHost": true,
"agentNsg": true,
"peNsg": true,
"acaEnvironmentNsg": true,
"apiManagementNsg": true,
"applicationGatewayNsg": true,
"bastionNsg": true
}
},
"aiFoundryDefinition": {
"value": {
"includeAssociatedResources": true,
"aiFoundryConfiguration": {
"disableLocalAuth": true
},
"aiModelDeployments": [
{
"name": "gpt-4o",
"model": {
"format": "OpenAI",
"name": "gpt-4o",
"version": "2024-08-06"
},
"sku": {"name": "Standard", "capacity": 100}
},
{
"name": "text-embedding-3-large",
"model": {
"format": "OpenAI",
"name": "text-embedding-3-large",
"version": "1"
},
"sku": {"name": "Standard", "capacity": 50}
}
]
}
}
}{
"deployToggles": {
"value": {
"logAnalytics": false, // Using existing
"keyVault": false, // Using existing
"virtualNetwork": false, // Using existing
// ... other services true
}
},
"resourceIds": {
"value": {
"logAnalyticsWorkspaceResourceId": "/subscriptions/.../Microsoft.OperationalInsights/workspaces/my-workspace",
"keyVaultResourceId": "/subscriptions/.../Microsoft.KeyVault/vaults/my-keyvault",
"virtualNetworkResourceId": "/subscriptions/.../Microsoft.Network/virtualNetworks/my-vnet"
}
}
}{
"aiFoundryDefinition": {
"value": {
"includeAssociatedResources": true,
"aiModelDeployments": [
{
"name": "gpt-4o",
"model": {"format": "OpenAI", "name": "gpt-4o", "version": "2024-08-06"},
"sku": {"name": "Standard", "capacity": 50}
},
{
"name": "gpt-4o-mini",
"model": {"format": "OpenAI", "name": "gpt-4o-mini", "version": "2024-07-18"},
"sku": {"name": "Standard", "capacity": 10}
},
{
"name": "text-embedding-3-small",
"model": {"format": "OpenAI", "name": "text-embedding-3-small", "version": "1"},
"sku": {"name": "Standard", "capacity": 20}
},
{
"name": "dall-e-3",
"model": {"format": "OpenAI", "name": "dall-e-3", "version": "3.0"},
"sku": {"name": "Standard", "capacity": 1}
}
]
}
}
}# Validate JSON syntax
cat infra/main.parameters.json | jq .
# Validate Bicep compilation
cd infra
az bicep build --file main.bicepazd provision --what-ifaz deployment group what-if \
--resource-group <rg-name> \
--template-file infra/main.bicep \
--parameters infra/main.parameters.json- Parameter errors: Check JSON syntax with
jq - Deployment errors: Run with
--debugflag - Quota errors: Check regional quotas with
az vm list-usage - Network errors: Verify CIDR ranges don't overlap
📖 Deployment Guide: docs/deploymentguide.md