Blog Post

Azure Infrastructure Blog
10 MIN READ

Automating Microsoft Foundry Deployment with IaC: Leveraging Bicep and GitHub Workflows

nasreensarah's avatar
nasreensarah
Icon for Microsoft rankMicrosoft
May 08, 2025

Deploying AI solutions at scale requires more than just innovation; it necessitates automation. In this updated blog, we explore how to optimize and standardize Microsoft Foundry deployments using Infrastructure-as-Code (IaC). By leveraging the declarative capabilities of Bicep alongside the automation features of GitHub Workflows, you can establish reproducible, secure, and fully automated deployment pipelines for your AI infrastructure. Whether you are developing proof-of-concepts or overseeing enterprise-scale environments, this method guarantees agility, compliance, and operational consistency.

What's Changed Since the Original Blog

Before diving into the updated architecture, here's a summary of the key changes:

Microsoft Foundry (formerly Azure AI Foundry) is a unified Azure platform-as-a-service offering for enterprise AI operations, model development, and application deployment. It combines production-grade infrastructure with intuitive tools, enabling developers to focus on building applications rather than managing infrastructure. The platform unifies agents, models, and tools under a single management grouping with built-in enterprise-readiness capabilities including tracing, monitoring, evaluations, and customizable enterprise setup configurations.

With the new Foundry resource model, the architecture has been significantly simplified. Previously, Azure AI Foundry relied on two primary components—a hub and a project—built on the `Microsoft.MachineLearningServices/workspaces` resource type. The hub served as the central development environment where users could configure infrastructure, create additional hubs, and launch new projects. Each project resided within a hub and could have its own set of permissions and allocated resources. This model required provisioning and wiring together several separate Azure resources: an AI Services account, a Key Vault, and a Storage Account.

The current model replaces this with a Foundry resource (`Microsoft.CognitiveServices/accounts` with `allowProjectManagement: true`) that serves as both the AI services provider and the management hub in a single resource. Projects (`Microsoft.CognitiveServices/accounts/projects`) are now native child resources directly under the Foundry resource, rather than separate workspace resources linked via a hub ID. Each project can still have its own set of permissions, managed identity, and allocated resources.

A key simplification is in how supporting infrastructure is handled. Previously, Azure AI Services existed as a separate resource providing Microsoft-maintained base models, and had to be manually connected to the hub. A Storage Account was required for user data, uploaded files, stored credentials, and generated artifacts like logs. For credential management, users had two options: an Azure Key Vault resource (either new or existing) to store secrets and sensitive information, or a Microsoft-managed credential store (which was in preview). With the new Foundry resource model, Key Vault and Storage are Microsoft-managed by default—the Microsoft-managed credential store is now the standard, and the secret data lifecycle automatically follows the hub, projects, connections, and compute. Organizations that require customer-managed keys (CMK), Bring Your Own Storage (BYOS), or advanced networking configurations can still provision and attach their own Key Vault and Storage Account resources.

This blog focuses on deploying the core Foundry components—the Foundry resource, a project, and model deployments—using Infrastructure-as-Code with Bicep, automated through a GitHub workflow. Compared to the original approach that required five separate Bicep modules for hub, project, AI Services, Key Vault, and Storage, the updated deployment requires just two resources, dramatically reducing template complexity and cross-resource wiring.

The Simplified Architecture

The most significant change is the simplified resource model. Previously, deploying Azure AI Foundry required provisioning and wiring together five separate resources:

  1. AI Foundry Hub (`Microsoft.MachineLearningServices/workspaces`, kind: `Hub`)
  2. AI Foundry Project (`Microsoft.MachineLearningServices/workspaces`, kind: `Project`)
  3. Azure AI Services (`Microsoft.CognitiveServices/accounts`)
  4. Azure Key Vault (for credential storage)
  5. Azure Storage Account (for data and artifacts)

With the new model, the architecture is dramatically simpler:

  1. Foundry Resource (`Microsoft.CognitiveServices/accounts`, kind: `AIServices`) — a single resource that serves as both the AI services provider and the management hub, with `allowProjectManagement: true`
  2. Project (`Microsoft.CognitiveServices/accounts/projects`) — a child resource directly under the Foundry resource
  3. Model Deployments (`Microsoft.CognitiveServices/accounts/deployments`) — optional model deployments as child resources

Key Vault and Storage are now Microsoft-managed by default, eliminating the need to provision and configure them separately. The credential store follows the lifecycle of the hub, projects, and connections automatically.

Note: If you have existing hub-based projects, they remain accessible in the Foundry (classic) portal. New investments should use the new Foundry project model. 

Repository Structure and Setup

With the simplified resource model, the project structure is leaner:

aiInfrastructure/

├── main.bicep                          # Orchestrates the deployment

├── development.main.bicepparam         # Dev environment parameters

├── staging.main.bicepparam             # Staging environment parameters

├── production.main.bicepparam          # Production environment parameters

└── modules/

    ├── foundryResource.bicep           # Foundry resource (replaces hub + AI Services)

    └── foundryProject.bicep            # Foundry project (simplified)

Compared to the original structure, we no longer need separate modules for Key Vault, Storage Account, or AI Services connections. The Foundry resource handles all of this internally.

Environment-Specific Parameters

To enable consistent and environment-specific deployments, separate `.bicepparam` files define values such as resource name prefixes, model deployment configurations, and tags. This structure promotes reusability and clarity.

using './main.bicep'

param aiFoundryName = 'foundry-dev01'
param aiProjectName = 'foundry-dev01-proj'
param location = 'eastus2'
param disableLocalAuth = false
param openAiDeployments = [
  {
    model: {
      name: 'gpt-4.1'
      format: 'OpenAI'
      version: '2025-04-14'
    }
    sku: {
      name: 'GlobalStandard'
      capacity: 10
    }
  }
]
param tags = {
  environment: 'development'
  iac: 'bicep'
}

Key Changes in Parameters

- No more `userObjectId` for Key Vault RBAC — Key Vault is Microsoft-managed

- No more `keyVaultEnablePurgeProtection`— not applicable

- `aiFoundryName` replaces `prefix`/`suffix` naming — a single `customSubDomainName` defines the developer API endpoint

- Model versions updated — e.g., `gpt-4.1` with version `2025-04-14` (1M token context) replaces the older `gpt-4o` `2024-05-13`

Modular Bicep Architecture

The Foundry Resource Module

// modules/foundryResource.bicep

@description('Name of the Foundry resource')
param name string

@description('Azure region for deployment')
param location string

@description('Tags to apply to all resources')
param tags object = {}

@description('Whether to disable local (key-based) authentication')
param disableLocalAuth bool = false

@description('Model deployments to create')
param deployments array = []

resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-06-01' = {
  name: name
  location: location
  tags: tags
  identity: {
    type: 'SystemAssigned'
  }
  sku: {
    name: 'S0'
  }
  kind: 'AIServices'
  properties: {
    allowProjectManagement: true
    customSubDomainName: name
    disableLocalAuth: disableLocalAuth
  }
}

@batchSize(1)
resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2025-06-01' = [
  for deployment in deployments: {
    parent: aiFoundry
    name: deployment.model.name
    sku: {
      capacity: deployment.sku.capacity ?? 1
      name: deployment.sku.name ?? 'GlobalStandard'
    }
    properties: {
      model: {
        format: deployment.model.?format ?? 'OpenAI'
        name: deployment.model.name
        version: deployment.model.version
      }
    }
  }
]

@description('The resource ID of the Foundry resource')
output id string = aiFoundry.id

@description('The name of the Foundry resource')
output name string = aiFoundry.name

@description('The endpoint of the Foundry resource')
output endpoint string = aiFoundry.properties.endpoint

@description('The principal ID of the system-assigned managed identity')
output principalId string = aiFoundry.identity.principalId

Key Differences from the Original Hub + AI Services Modules

The Project Module

The project module is also simplified. Projects are now child resources of the CognitiveServices account rather than a separate `MachineLearningServices/workspaces` resource.

@description('Name of the project')
param name string

@description('Azure region for deployment')
param location string

@description('Tags to apply')
param tags object = {}

@description('Name of the parent Foundry resource')
param foundryResourceName string

resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-06-01' existing = {
  name: foundryResourceName
}

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

@description('The resource ID of the project')
output id string = project.id

@description('The name of the project')
output name string = project.name

@description('The principal ID of the project managed identity')
output principalId string = project.identity.principalId

Key Differences from the Original Project Module

What About Key Vault and Storage?

In the original blog, dedicated Bicep modules were needed for:

- Key Vault — to store secrets and credentials used by the hub

- Storage Account — to store uploaded data, logs, and generated artifacts

These are no longer required for basic deployments. The Foundry resource uses a Microsoft-managed credential store and storage by default. Secret data lifecycle follows the hub, projects, connections, and compute automatically.

If your organization requires customer-managed keys, BYOS (Bring Your Own Storage), or advanced networking, you can still provision and attach your own Key Vault and Storage Account. 

The Main Bicep File

The `main.bicep` file orchestrates the deployment by referencing the modular templates:

@description('Name of the AI Foundry resource')
param aiFoundryName string

@description('Name of the AI project')
param aiProjectName string = '${aiFoundryName}-proj'

@description('Azure region for deployment')
param location string = 'eastus2'

@description('Whether to disable local auth')
param disableLocalAuth bool = false

@description('Model deployments')
param openAiDeployments array = []

@description('Tags to apply to all resources')
param tags object = {}

module foundryResource 'modules/foundryResource.bicep' = {
  name: 'foundry-resource'
  params: {
    name: aiFoundryName
    location: location
    tags: tags
    disableLocalAuth: disableLocalAuth
    deployments: openAiDeployments
  }
}

module foundryProject 'modules/foundryProject.bicep' = {
  name: 'foundry-project'
  params: {
    name: aiProjectName
    location: location
    tags: tags
    foundryResourceName: foundryResource.outputs.name
  }
}

output foundryResourceId string = foundryResource.outputs.id
output foundryResourceName string = foundryResource.outputs.name
output foundryEndpoint string = foundryResource.outputs.endpoint
output projectId string = foundryProject.outputs.id
output projectName string = foundryProject.outputs.name

This is notably simpler than the original, which required orchestrating five modules with cross-references for Key Vault IDs, Storage Account IDs, and AI Services connection strings.

Workflow Breakdown: Automating Deployment with GitHub Actions

The GitHub Actions workflow has been updated with several improvements:

Manual Trigger with Environment Selection
GitHub Actions allows for flexible workflow execution using manual triggers via the workflow_dispatch event. This feature empowers users to choose the target deployment environment—such as development, staging, or production—directly from the GitHub Actions UI at runtime.

on:
  workflow_dispatch:
    inputs:
      environment:
        description: "Select the environment to deploy"
        required: true
        default: development
        type: choice
        options:
          - development
          - staging
          - production

Secure Azure Authentication
The workflow uses OIDC (OpenID Connect) via azure/login@v2(updated from `v1`): to securely authenticate to Azure using environment-specific secrets (AZURE_CLIENT_ID, etc.).

The GitHub environment (development in this case) is configured with three encrypted secrets as shown below:

  • AZURE_CLIENT_ID: Client ID of the workload identity app.
  • AZURE_TENANT_ID: Your Azure AD tenant ID.
  • AZURE_SUBSCRIPTION_ID: The Azure subscription used for the deployment.

These secrets are used at runtime to authenticate to Azure in a secure, environment-scoped context, enabling the workflow to perform operations like deploying Bicep templates.

GitHub OIDC Credential Setup Summary

  • Federated credential scenario: Enables GitHub Actions to securely access Azure resources using OIDC (OpenID Connect).
  • Issuer: Pre-defined as https://token.actions.githubusercontent.com (GitHub's identity provider).
  • Organization & Repository: Ties the credential to your GitHub repo (reponame/sample).
  • Entity type: Set as Environment to scope access only to a specific GitHub environment (e.g., development).
  • Subject identifier: Auto-generated to match the exact GitHub environment, ensuring scoped token issuance.
  • Name: Name of the federated credential (Limit of 120 characters)
  • Audience: Set to api://AzureADTokenExchange, which is required for Azure token exchange.
  • Purpose: Allows GitHub workflows to authenticate into Azure without storing client secrets, improving security and automation compliance.

Simplified: Bicep CLI Setup

In the original blog, we manually downloaded the Bicep CLI binary and created a symlink. This step is no longer necessary — modern versions of Azure CLI (2.67+) ship with full `.bicepparam` support and automatically manage the Bicep binary.

If you still want to ensure the latest Bicep version, you can use a single command:

      # Ensure latest Bicep CLI (optional — Azure CLI 2.67+ includes full .bicepparam support)
      - name: Upgrade Bicep CLI
        run: az bicep upgrade

This replaces the original multi-step process of downloading the binary, setting permissions, and creating symlinks.

Resource Group Creation and Deployment

The deployment steps remain the same — the simplification is in the Bicep templates themselves. Instead of hardcoding locations, the workflow retrieves the deployment region dynamically via repository variable (AZURE_LOCATION), keeping the pipeline flexible and clean. Each environment has its own .bicepparam file which is passed directly to az deployment group create. This enables clean separation of development, staging, and production configurations.

      - name: Create resource group if it doesn't exist
        run: |
          az group create \
            --name AI-${{ github.event.inputs.environment }} \
            --location ${{ env.AZURE_LOCATION }}

      - name: Deploy Bicep Template
        run: |
          az deployment group create \
            --resource-group AI-${{ github.event.inputs.environment }} \
            --template-file "${{ env.CODE_PATH }}main.bicep" \
            --parameters "${{ env.CODE_PATH }}${{ github.event.inputs.environment }}.main.bicepparam" \
            --name "${{ env.DESCRIPTION }}-${{ github.event.inputs.environment }}"

      - name: Show deployment outputs
        run: |
          echo "Deployment outputs:"
          az deployment group show \
            --resource-group AI-${{ github.event.inputs.environment }} \
            --name "${{ env.DESCRIPTION }}-${{ github.event.inputs.environment }}" \
            --query "properties.outputs"

Post-deployment, the workflow extracts and logs the outputs from the Bicep modules. This gives you immediate visibility into what was provisioned — ideal for chaining deployments or debugging.

Complete Updated Workflow

name: Deploy Microsoft Foundry Infrastructure

on:
  workflow_dispatch:
    inputs:
      environment:
        description: "Select the environment to deploy"
        required: true
        default: development
        type: choice
        options:
          - development
          - staging
          - production

permissions:
  id-token: write
  contents: read

env:
  CODE_PATH: 'aiInfrastructure/'
  DESCRIPTION: 'ai-foundry-infra'
  AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}

jobs:
  deploy:
    name: Deploy Bicep Template to Azure
    runs-on: ubuntu-latest
    environment: ${{ github.event.inputs.environment }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Azure Login
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Upgrade Bicep CLI
        run: az bicep upgrade

      - name: Create resource group if it doesn't exist
        run: |
          az group create \
            --name AI-${{ github.event.inputs.environment }} \
            --location ${{ env.AZURE_LOCATION }}

      - name: Deploy Bicep Template
        run: |
          az deployment group create \
            --resource-group AI-${{ github.event.inputs.environment }} \
            --template-file "${{ env.CODE_PATH }}main.bicep" \
            --parameters "${{ env.CODE_PATH }}${{ github.event.inputs.environment }}.main.bicepparam" \
            --name "${{ env.DESCRIPTION }}-${{ github.event.inputs.environment }}"

      - name: Show deployment outputs
        run: |
          echo "Deployment outputs:"
          az deployment group show \
            --resource-group AI-${{ github.event.inputs.environment }} \
            --name "${{ env.DESCRIPTION }}-${{ github.event.inputs.environment }}" \
            --query "properties.outputs"

Workflow Changes Summary

Additional Considerations for the New Model

Azure Verified Modules (AVM)

For production deployments, consider using the official Azure Verified Module for AI Foundry (https://github.com/Azure/bicep-registry-modules/tree/main/avm/ptn/ai-ml/ai-foundry) from the Bicep public registry. This provides enterprise-ready patterns including:

- Network isolation with private endpoints

- Customer-managed keys (CMK) for encryption

- Standard Agent Services setup

- WAF-aligned security configurations

Usage is as simple as:

module aiFoundry 'br/public:avm/ptn/ai-ml/ai-foundry:<version>' = {
  name: 'ai-foundry'
  params: {
    baseName: 'myai'
    aiModelDeployments: [
      {
        model: {
          format: 'OpenAI'
          name: 'gpt-4.1'
          version: '2025-04-14'
        }
        name: 'gpt-4.1'
        sku: {
          capacity: 1
          name: 'GlobalStandard'
        }
      }
    ]
  }
}

Upgrading Existing Azure OpenAI Resources

If you have existing Azure OpenAI resources, you can upgrade them to Foundry resources while preserving your endpoint, API keys, and existing state. The upgrade converts the Azure OpenAI resource type to a Foundry resource type. 

New Capabilities Available via IaC

The new Foundry resource model enables deploying additional capabilities as child resources:

- CapabilityHosts (`Microsoft.CognitiveServices/accounts/capabilityHosts`) — configure agent infrastructure with storage, search, and thread connections

- Applications (`Microsoft.CognitiveServices/accounts/projects/applications`) — deploy agent applications with authorization policies

- RAI Safety Providers — configure content safety and responsible AI filters

Conclusion

The evolution from Azure AI Foundry to Microsoft Foundry has brought significant simplification to Infrastructure-as-Code deployments. What previously required five Bicep modules—hub, project, AI Services, Key Vault, and Storage Account—can now be achieved with just two resources: a Foundry resource and a project.

By adopting the updated `Microsoft.CognitiveServices/accounts` resource model with `allowProjectManagement: true`, teams benefit from:

- Fewer resources to manage — Microsoft handles credential storage and data management by default

- Simpler Bicep templates — no cross-resource wiring for connections, Key Vault IDs, or Storage Account IDs

- Modern API versions — `2025-06-01` and later bring GA stability and new capabilities

- Access to the latest features — Agents v2, Tool Catalog, multi-agent workflows, and the Responses API

- Streamlined GitHub workflows — with `azure/login@v2` and native Bicep CLI support in Azure CLI

This framework continues to support multi-environment deployments, OIDC-based secure authentication, and modular Bicep architectures. As your use of Microsoft Foundry grows, these patterns can be extended to support agent deployments, advanced networking with private endpoints, customer-managed keys, and governance at scale.

Updated Apr 17, 2026
Version 2.0