10.4 Deploy Foundry Infrastructure with Bicep and Azure CLI

Key Takeaways

  • New Foundry IaC uses Bicep resource types Microsoft.CognitiveServices/accounts and Microsoft.CognitiveServices/accounts/projects (kind AIServices, SKU S0, allowProjectManagement, customSubDomainName). Classic hubs use Microsoft.MachineLearningServices/workspaces with kind hub or project.
  • Azure CLI for the new model is az cognitiveservices account create --kind AIServices --sku S0 --custom-domain --allow-project-management, then az cognitiveservices account project create. Classic hubs remain az ml workspace create --kind hub and --kind project --hub-id.
  • Start from microsoft-foundry/foundry-samples infrastructure-setup-bicep (00-basic, private-network agent setups) or Export template from the Azure portal. Parameterize names, location, publicNetworkAccess, disableLocalAuth, and identity. Do not commit hardcoded subscription IDs.
  • GitHub Actions should deploy Foundry with the same OIDC pattern as Azure Machine Learning: permissions.id-token: write, azure/login@v2 with AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_SUBSCRIPTION_ID, then az deployment group create. Do not store a client secret as AZURE_CREDENTIALS for new pipelines.
  • allowProjectManagement is immutable after create. Foundry account names must be globally unique (2–64 characters, lowercase, numbers, hyphens). Pass location explicitly; sample templates default to eastus2, which may not match the resource group.
Last updated: August 2026

Deploy Foundry Infrastructure with Bicep and Azure CLI

Quick Answer: Bicep deploys Microsoft.CognitiveServices/accounts plus .../accounts/projects for the new Foundry resource, or Microsoft.MachineLearningServices/workspaces kind: hub|project for classic hubs. Apply with az deployment group create --template-file main.bicep. CLI without Bicep: az cognitiveservices account create --kind AIServices --sku S0 --allow-project-management. Parameterize dev/test/prod. GitHub Actions uses the same OIDC pattern as Chapter 4, not a client secret.

Domain 3's fourth platform bullet is deploy infrastructure using Bicep templates and Azure CLI. You already did this for Azure Machine Learning in Chapter 4. Foundry is a different resource provider on the new path. Copying Microsoft.MachineLearningServices/workspaces YAML and hoping it becomes a Foundry project is the high-scoring wrong answer.

Which types go in the template

What you are landingBicep / ARM typeKind / SKUAzure CLI
New Foundry resourceMicrosoft.CognitiveServices/accountsAIServices / S0az cognitiveservices account create --kind AIServices --sku S0 --custom-domain --allow-project-management
New Foundry projectMicrosoft.CognitiveServices/accounts/projectschild of the accountaz cognitiveservices account project create
Classic Foundry / AML hubMicrosoft.MachineLearningServices/workspaceshubaz ml workspace create --kind hub
Hub-based projectMicrosoft.MachineLearningServices/workspacesproject + parent hub resource IDaz ml workspace create --kind project --hub-id
Connection (new)Cognitive Services account or project connectioncategory such as CognitiveSearchaz cognitiveservices account project connection create --file
Connection (hub)workspace connectionYAML type: azure_ai_servicesaz ml connection create --file

Microsoft's quickstart (updated 2026-07-28) deploys the 00-basic sample from https://github.com/microsoft-foundry/foundry-samples infrastructure/infrastructure-setup-bicep. That template creates the account and a project. Private-network Standard Agent setups live in the same repo (15-private-network-standard-agent-setup, 11-private-network-basic-vnet). Prefer cloning those over inventing Private Link + injection from memory.

You can also open an existing Foundry resource in the Azure portal → Automation → Export template → Bicep. Export may warn on types that do not fully serialize; fill gaps by hand and replace hardcoded subscription IDs, resource group names, and resource IDs with parameters.

Bicep shape for a new Foundry account

A production main.bicep should parameterize at least aiFoundryName, aiProjectName, location, publicNetworkAccess, and disableLocalAuth. Sketch of the account (property names match the Cognitive Services ARM contract):

param aiFoundryName string
param aiProjectName string
param location string = resourceGroup().location

resource foundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = {
  name: aiFoundryName
  location: location
  kind: 'AIServices'
  sku: { name: 'S0' }
  identity: { type: 'SystemAssigned' }
  properties: {
    customSubDomainName: aiFoundryName
    allowProjectManagement: true
    publicNetworkAccess: 'Disabled'
    disableLocalAuth: true
  }
}

resource project 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = {
  parent: foundry
  name: aiProjectName
  location: location
  identity: { type: 'SystemAssigned' }
  properties: {}
}

Then grant Foundry User (GUID 53ca6127-db72-4b80-b1b0-d745d6d5456d) to the project identity and to the developer group with Microsoft.Authorization/roleAssignments modules. Portal/UI create auto-assigns Foundry User; templates do not.

Related controls Microsoft lists next to the Bicep quickstart: private endpoints (section 10.3), customer-managed keys (Key Vault in the same region, soft-delete and purge protection, Key Vault Crypto User on the managed identity), RBAC, and custom Azure Policy (allowed regions, required tags, mandatory Private Link or CMK).

Classic hub Bicep stays on Microsoft.MachineLearningServices/workspaces with kind: 'hub' and a second workspace kind: 'project' whose properties include the hub resource ID. That is the Chapter 4 workspace family plus kind. Do not mix allowProjectManagement onto an AML workspace.

Azure CLI without a template (and with one)

Group + new-model account + project:

az group create --name rg-claims-foundry-prod --location eastus

az cognitiveservices account create \
  --name claims-foundry-prod \
  --resource-group rg-claims-foundry-prod \
  --kind AIServices --sku S0 --location eastus \
  --custom-domain claims-foundry-prod \
  --allow-project-management

az cognitiveservices account project create \
  --name claims-foundry-prod \
  --resource-group rg-claims-foundry-prod \
  --project-name claims-assistant --location eastus

Apply Bicep:

az deployment group create \
  --resource-group rg-claims-foundry-prod \
  --template-file main.bicep \
  --parameters aiFoundryName=claims-foundry-prod aiProjectName=claims-assistant location=eastus

Classic hub still uses the ml extension:

az extension add -n ml -y
az ml workspace create --kind hub -g rg-claims-foundry-prod -n hub-claims-prod
az ml workspace create --kind project -g rg-claims-foundry-prod -n proj-claims-chat --hub-id <hub-arm-id>

--allow-project-management is create-time only. If you omit it, delete and recreate the account (after draining projects) or you cannot host Foundry projects. AccountNameInvalid means the name is not globally unique or it violates 2–64 lowercase / digit / hyphen rules. Sample templates that default location to eastus2 will land resources in eastus2 even when the resource group is in eastus — pass location explicitly.

If a template includes a model deployment whose version is retired, ARM returns ServiceModelDeprecating. Comment the deployment out or pick a Generally Available model (az cognitiveservices model list --location <loc> --query "[?model.lifecycleStatus=='GenerallyAvailable']"). Azure CLI 2.74–2.75 can mask the real error behind The content for this response was already consumed; rerun with --debug.

Parameterize environments and keep secrets out of Git

Same discipline as Chapter 4:

  • dev — PNA may be Enabled, disableLocalAuth false for laptop keys, cheaper regions, no CMK.
  • test — PNA Disabled, private endpoint, Entra ID only, shared Search in the test subscription.
  • prod — PNA Disabled, injection subnet, CMK, disableLocalAuth true, GitHub Environment approvals.

Check dev.bicepparam / prod.bicepparam into Git. Do not check connection API keys. Prefer authType: AAD connections. If a key is unavoidable, inject it from Key Vault references or GitHub Environment secrets at deploy time.

Register providers the Standard Agent templates need: Microsoft.CognitiveServices, Microsoft.Storage, Microsoft.KeyVault, Microsoft.Search, Microsoft.App, and Microsoft.MachineLearningServices if hubs are in the same landing zone.

GitHub Actions OIDC — same pattern as Azure Machine Learning

Do not invent a Foundry-specific login. Federate GitHub's OIDC token to an Entra app or user-assigned managed identity, store only client / tenant / subscription IDs, and grant the identity least privilege (resource-group Contributor plus User Access Administrator if the template assigns Foundry User).

permissions:
  id-token: write
  contents: read
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: prod
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - run: |
          az deployment group create \
            --resource-group rg-claims-foundry-prod \
            --template-file infra/foundry.bicep \
            --parameters infra/prod.bicepparam

permissions.id-token: write is mandatory. Path-filter on infra/**. Do not use creds: ${{ secrets.AZURE_CREDENTIALS }} for new work; az ad sp create-for-rbac --json-auth is the deprecated secret-based path from Chapter 4.

After the account exists, a second job (or step) can az cognitiveservices account project connection create and assign the Foundry User GUID to a Microsoft Entra group. Keep model-version pins in parameters so a retired model does not fail Friday's apply.

Exam scenario

Platform engineering cloned 00-basic, deployed to a lab, and now needs prod in eastus with no keys and no public endpoint. They exported the lab resource to Bicep and want to commit it.

Replace every subscription ID and resource name with parameters. Set publicNetworkAccess: Disabled, disableLocalAuth: true, and allowProjectManagement: true on the account. Add private endpoint, DNS zone, and (if agents) the delegated subnet modules from 15-private-network-standard-agent-setup rather than hand-editing the export. Wire GitHub Actions OIDC to a user-assigned identity that has RBAC only on rg-claims-foundry-prod. Do not paste the lab API key into GitHub secrets as the production auth story.

Common trap

Using az ml workspace create --kind project (or workspace Bicep without kind: hub) and calling it a Foundry project. That creates a hub-based project and fails without a hub. A second trap is deploying the sample without overriding location, then wondering why Private Link is in eastus2. A third is exporting Bicep and shipping hardcoded IDs, or leaving allowProjectManagement off because the lab account was created before someone read the flag. A fourth is granting the GitHub identity subscription Owner "so role assignments work" — scope it to the resource group and a dedicated role-assignment module.

Test Your Knowledge

Which Bicep resource types deploy a new-portal Microsoft Foundry resource and a Foundry project?

A
B
C
D
Test Your Knowledge

An engineer creates a Cognitive Services account of kind AIServices and SKU S0 but omits --allow-project-management. They later need Foundry projects on that account. What does Microsoft document?

A
B
C
D
Test Your Knowledge

How should a GitHub Actions workflow authenticate to deploy Foundry Bicep in production?

A
B
C
D