Background
Sections
IntroductionFoundations1. Resource Hierarchy2. Resource Manager3. Identity and RBAC4. Regions and Availability5. Naming and TaggingVirtual Machines1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetVirtual Network1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetBlob Storage1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure SQL Database1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Kubernetes Service1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Container Registry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetMicrosoft Entra ID1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure RBAC1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Functions1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAPI Management1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure App Configuration1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Machine Learning1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Monitor1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure AI Foundry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and Cheatsheet

5. Deployment

18 min read

Getting Started proved the service exists. This page makes it repeatable, reviewable, and reversible — a parameterised Terraform module, remote state, an Ansible playbook for day-two work, the Bicep equivalent, an OIDC pipeline, three environments, and an honest answer to "how do we roll this back at 2 a.m.".

Foundry has one wrinkle no other topic in this article has: you are deploying two things on two cadences. The infrastructure (account, project, deployments, connections, role assignments) changes rarely and belongs in Terraform. The AI artefacts — prompts, agent definitions, tool schemas, model versions, evaluation datasets — change weekly and are the thing most likely to break production. Treat them as a separate, faster pipeline with its own gate: an evaluation run. Infrastructure CI/CD that does not gate on evaluation quality is deploying a coin flip on a schedule.

The infrastructure pipeline and the prompt-and-model pipeline, promoted through dev, staging, and prod

Tool order for this topic

Rank Tool What it does here Where it struggles
1. Primary Terraform (azurerm + azapi) Account, model deployments, connections, role assignments, networking, diagnostics — the full worked example below Foundry projects, capability hosts, and new agent features land in the API before azurerm covers them; that is what azapi is for
2. Secondary Ansible (azure.azcollection) Day-two operations: adding or resizing a model deployment, rotating a connection, bulk-applying a content filter across accounts, orchestrating a region cutover No drift reconciliation; it will happily create something Terraform then wants to destroy
3. Third Bicep / ARM Azure-native, first-class support for preview Foundry properties on day one, what-if preview, deployment stacks Azure-only; and the JSON under Bicep is unpleasant when you are generating many deployments

All three apply to this service. The one honest caveat: agents themselves are not infrastructure. An agent is a data-plane object created through the project endpoint, not an ARM resource. Terraform can create the account, the project, the model deployment, and the connections an agent needs — the agent definition itself belongs in your application repository and its own deploy step, versioned as code. Trying to force agents into Terraform produces a state file that fights your application team.

Terraform — the module shape

Three files, parameterised on environment, with the provider features {} block present because Cognitive Services has soft-delete behaviour that it governs.

variables.tf

variable "environment" {
  type        = string
  description = "dev | staging | prod"
}

variable "name_prefix" {
  type        = string
  description = "Short, lowercase, no hyphens — becomes part of a globally unique subdomain"
}

variable "location" {
  type        = string
  default     = "eastus2"
  description = "Must be a region offering the models below, for the deployment types below"
}

variable "model_deployments" {
  description = "Job-named deployments. Keys are the names your application calls."
  type = map(object({
    model_name      = string
    model_version   = string
    deployment_type = string # GlobalStandard | Standard | DataZoneStandard | ProvisionedManaged
    capacity        = number # thousands of TPM for standard types; PTUs for provisioned
    upgrade_policy  = string # OnceNewDefaultVersionAvailable | OnceCurrentVersionExpired | NoAutoUpgrade
  }))
  default = {
    "chat-default" = {
      model_name      = "gpt-4.1"
      model_version   = "2025-04-14"
      deployment_type = "GlobalStandard"
      capacity        = 50
      upgrade_policy  = "OnceCurrentVersionExpired"
    }
    "embed-default" = {
      model_name      = "text-embedding-3-large"
      model_version   = "1"
      deployment_type = "Standard"
      capacity        = 30
      upgrade_policy  = "OnceCurrentVersionExpired"
    }
  }
}

variable "app_principal_ids" {
  type        = list(string)
  description = "Object IDs of the managed identities that must be able to CALL the models"
  default     = []
}

main.tf

terraform {
  required_providers {
    azurerm = { source = "hashicorp/azurerm", version = "~> 4.0" }
    azapi   = { source = "Azure/azapi", version = "~> 2.0" }
  }
}

provider "azurerm" {
  features {
    cognitive_account {
      # Purge the soft-deleted account on destroy, so the globally unique name is
      # released and the next apply can recreate it. Set this deliberately:
      # in prod you may WANT the soft-delete safety net instead.
      purge_soft_delete_on_destroy = true
    }
  }
}

provider "azapi" {}

locals {
  is_prod = var.environment == "prod"
  tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
    Workload    = "ai-foundry"
  }
}

resource "azurerm_resource_group" "this" {
  name     = "rg-${var.name_prefix}-aif-${var.environment}"
  location = var.location
  tags     = local.tags
}

# ---------------------------------------------------------------------------
# The Foundry account. kind = AIServices + project management = a Foundry
# resource rather than a bare AI Services or Azure OpenAI resource.
# ---------------------------------------------------------------------------
resource "azurerm_cognitive_account" "this" {
  name                = "aif${var.name_prefix}${var.environment}"
  resource_group_name = azurerm_resource_group.this.name
  location            = azurerm_resource_group.this.location

  kind                       = "AIServices"
  sku_name                   = "S0"
  custom_subdomain_name      = "aif${var.name_prefix}${var.environment}"
  project_management_enabled = true

  # The single highest-value security setting on this resource: no API keys.
  local_auth_enabled            = false
  public_network_access_enabled = !local.is_prod
  outbound_network_access_restricted = local.is_prod

  identity {
    type = "SystemAssigned"
  }

  network_acls {
    default_action = local.is_prod ? "Deny" : "Allow"
  }

  tags = local.tags

  lifecycle {
    # Changing kind or subdomain replaces the resource — and replacement means a
    # new endpoint, lost deployments, and a soft-deleted name in the way.
    prevent_destroy = false # set true in prod once you are past the churn
  }
}

# ---------------------------------------------------------------------------
# Model deployments — the things your application actually calls, by name.
# ---------------------------------------------------------------------------
resource "azurerm_cognitive_deployment" "this" {
  for_each = var.model_deployments

  name                 = each.key
  cognitive_account_id = azurerm_cognitive_account.this.id

  model {
    format  = "OpenAI"
    name    = each.value.model_name
    version = each.value.model_version
  }

  sku {
    name     = each.value.deployment_type
    capacity = each.value.capacity
  }

  version_upgrade_option = each.value.upgrade_policy
}

# ---------------------------------------------------------------------------
# A Foundry project. azurerm coverage of projects has lagged the API, so azapi
# is the dependable path. Verify whether a first-class resource now exists.
# ---------------------------------------------------------------------------
resource "azapi_resource" "project" {
  type      = "Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview"
  name      = "proj-${var.name_prefix}-${var.environment}"
  parent_id = azurerm_cognitive_account.this.id
  location  = azurerm_resource_group.this.location

  identity {
    type = "SystemAssigned"
  }

  body = {
    properties = {
      displayName = "${var.name_prefix} ${var.environment}"
      description = "Managed by Terraform"
    }
  }

  tags = local.tags
}

# ---------------------------------------------------------------------------
# Data-plane RBAC. Without this the account exists and nothing can call it.
# This is the step people leave out of IaC because the portal did it for them.
# ---------------------------------------------------------------------------
resource "azurerm_role_assignment" "app_inference" {
  for_each = toset(var.app_principal_ids)

  scope                = azurerm_cognitive_account.this.id
  role_definition_name = "Cognitive Services OpenAI User"
  principal_id         = each.value
}

# ---------------------------------------------------------------------------
# Observability is off until you turn it on.
# ---------------------------------------------------------------------------
resource "azurerm_log_analytics_workspace" "this" {
  name                = "law-${var.name_prefix}-aif-${var.environment}"
  location            = azurerm_resource_group.this.location
  resource_group_name = azurerm_resource_group.this.name
  sku                 = "PerGB2018"
  retention_in_days   = local.is_prod ? 90 : 30
  tags                = local.tags
}

resource "azurerm_monitor_diagnostic_setting" "this" {
  name                       = "diag-to-law"
  target_resource_id         = azurerm_cognitive_account.this.id
  log_analytics_workspace_id = azurerm_log_analytics_workspace.this.id

  enabled_log { category = "Audit" }
  enabled_log { category = "RequestResponse" }
  enabled_log { category = "Trace" }

  metric { category = "AllMetrics" }
}

⚠️ Argument names on azurerm_cognitive_account (notably project_management_enabled, outbound_network_access_restricted, and the cognitive_account features block) and the azapi API version above should be verified against the current provider and Azure documentation before use — this resource has gained arguments quickly.

outputs.tf

output "account_id"       { value = azurerm_cognitive_account.this.id }
output "endpoint"         { value = azurerm_cognitive_account.this.endpoint }
output "project_endpoint" {
  value = "${azurerm_cognitive_account.this.endpoint}api/projects/${azapi_resource.project.name}"
}
output "deployment_names" { value = keys(var.model_deployments) }
output "identity_principal_id" {
  value = azurerm_cognitive_account.this.identity[0].principal_id
}

The loop

terraform init -backend-config=env/prod.backend.hcl
terraform plan  -var-file=env/prod.tfvars -out=tfplan
terraform apply tfplan

Read the plan for two things specifically on this service: any # forces replacement on the account (kind, subdomain, or location changed — that is a new endpoint and a soft-delete collision), and any change to a deployment's sku.name (changing deployment type is not a resize; it is a different capacity pool and may not be available).

Remote state and locking

Local state is a footgun the moment a second person or a pipeline exists. Use the Azure Storage backend — and note the Azure-specific nicety: locking uses native blob leases, so there is no separate lock table to create and no second thing to pay for, unlike DynamoDB in AWS.

# env/prod.backend.hcl
resource_group_name  = "rg-tfstate"
storage_account_name = "sttfstateprod"
container_name       = "tfstate"
key                  = "ai-foundry/prod.terraform.tfstate"
use_azuread_auth     = true # authenticate to the backend with your identity, not a storage key

Two honest options for separating environments, pick one and be consistent:

  • Directory (or repo) per environment, each with its own backend key and .tfvars. Verbose, but the blast radius is obvious from the file path and you can give prod different permissions. This is the one to choose for Foundry, because environments should live in different subscriptions to get different quota pools.
  • Terraform workspaces, one state per workspace behind one configuration. Less duplication, but everything shares a backend and a set of credentials, and it is easy to apply to the wrong one.

Ansible — day-two operations

Ansible's place here is not initial provisioning; it is the imperative jobs that Terraform makes awkward — adding a deployment during an incident, resizing capacity across several accounts, or driving a region cutover. Authenticate with a managed identity on the runner or an OIDC-federated service principal, never a stored secret.

# playbook: ensure a Foundry account and a chat deployment exist, idempotently
- name: Foundry day-two operations
  hosts: localhost
  connection: local
  gather_facts: false

  vars:
    resource_group: "rg-{{ name_prefix }}-aif-{{ env }}"
    account_name: "aif{{ name_prefix }}{{ env }}"
    location: eastus2

  tasks:
    - name: Ensure the Foundry account exists
      azure.azcollection.azure_rm_cognitiveservicesaccount:
        resource_group: "{{ resource_group }}"
        name: "{{ account_name }}"
        location: "{{ location }}"
        kind: AIServices
        sku_name: S0
        custom_subdomain_name: "{{ account_name }}"
        state: present
      register: account

    - name: Ensure the chat deployment exists at the right capacity
      # No dedicated module for model deployments at the time of writing, so drive
      # the ARM API directly — still idempotent, because PUT is declarative.
      azure.azcollection.azure_rm_resource:
        api_version: "2024-10-01"
        resource_group: "{{ resource_group }}"
        provider: CognitiveServices
        resource_type: accounts
        resource_name: "{{ account_name }}"
        subresource:
          - type: deployments
            name: chat-default
        body:
          sku:
            name: GlobalStandard
            capacity: "{{ chat_capacity | default(50) }}"
          properties:
            model:
              format: OpenAI
              name: gpt-4.1
              version: "2025-04-14"
            versionUpgradeOption: OnceCurrentVersionExpired
        state: present

    - name: Show the endpoint
      ansible.builtin.debug:
        msg: "{{ account.state.properties.endpoint }}"

Idempotency check: run it twice. The second run should report ok with no changed — because both tasks are declarative PUTs. If the second run reports changed, something in the body differs from what the API returns (a common cause is omitting a property the service defaults), and that difference will fight your Terraform state too.

⚠️ Verify the current azure.azcollection module names and the Cognitive Services API version above before use.

Bicep / ARM

Azure-native, and genuinely the better choice for two situations on this service: preview Foundry properties that neither azurerm nor the Ansible collection has caught up with, and teams already governing deployments with deployment stacks or Azure DevOps.

Bicep equivalent — account, project, and one model deployment
@description('Short lowercase prefix; becomes part of a globally unique subdomain')
param namePrefix string

@allowed(['dev', 'staging', 'prod'])
param environment string

param location string = resourceGroup().location

var accountName = 'aif${namePrefix}${environment}'

resource account 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = {
  name: accountName
  location: location
  kind: 'AIServices'
  sku: {
    name: 'S0'
  }
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    customSubDomainName: accountName
    allowProjectManagement: true
    disableLocalAuth: true
    publicNetworkAccess: environment == 'prod' ? 'Disabled' : 'Enabled'
    networkAcls: {
      defaultAction: environment == 'prod' ? 'Deny' : 'Allow'
    }
  }
}

resource project 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = {
  parent: account
  name: 'proj-${namePrefix}-${environment}'
  location: location
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    displayName: '${namePrefix} ${environment}'
  }
}

resource chat 'Microsoft.CognitiveServices/accounts/deployments@2025-04-01-preview' = {
  parent: account
  name: 'chat-default'
  sku: {
    name: 'GlobalStandard'
    capacity: 50
  }
  properties: {
    model: {
      format: 'OpenAI'
      name: 'gpt-4.1'
      version: '2025-04-14'
    }
    versionUpgradeOption: 'OnceCurrentVersionExpired'
  }
}

output endpoint string = account.properties.endpoint
output projectEndpoint string = '${account.properties.endpoint}api/projects/${project.name}'

Preview the change before you make it:

az deployment group what-if \
  -g rg-demo-aif-prod \
  -f main.bicep \
  -p namePrefix=demo environment=prod

⚠️ Deployment modes. az deployment group create defaults to incremental — resources in the group that are not in the template are left alone. Complete mode deletes them. On a resource group containing a Foundry account plus the Cosmos DB, AI Search, and Storage that your agents depend on, a complete-mode deployment of a template that only describes the account will delete the agent state stores. Run what-if with the mode you intend to use, every time.

⚠️ Verify the API version and the allowProjectManagement / disableLocalAuth property names against current Azure docs; this resource type is iterating quickly and several properties arrived in preview API versions first.

CI/CD — OIDC, never a secret

Workload identity federation means the pipeline exchanges a short-lived GitHub or Azure DevOps token for an Entra ID token. No client secret, nothing to rotate, nothing to leak.

name: ai-foundry-infra

on:
  pull_request:
    paths: ['infra/ai-foundry/**']
  push:
    branches: [main]
    paths: ['infra/ai-foundry/**']

permissions:
  id-token: write   # required for OIDC
  contents: read

jobs:
  plan:
    runs-on: ubuntu-latest
    environment: dev
    defaults:
      run:
        working-directory: infra/ai-foundry
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ vars.AZURE_CLIENT_ID }}
          tenant-id: ${{ vars.AZURE_TENANT_ID }}
          subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init -backend-config=env/dev.backend.hcl
      - run: terraform plan -var-file=env/dev.tfvars -out=tfplan
      - run: terraform show -no-color tfplan > plan.txt
      - uses: actions/upload-artifact@v4
        with: { name: plan, path: infra/ai-foundry/plan.txt }

  apply-prod:
    if: github.ref == 'refs/heads/main'
    needs: plan
    runs-on: ubuntu-latest
    environment: prod          # ← the manual approval gate lives on this environment
    defaults:
      run:
        working-directory: infra/ai-foundry
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ vars.AZURE_CLIENT_ID_PROD }}
          tenant-id: ${{ vars.AZURE_TENANT_ID }}
          subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID_PROD }}
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init -backend-config=env/prod.backend.hcl
      - run: terraform apply -auto-approve -var-file=env/prod.tfvars

Set the federated credential on the app registration to the specific repository and environment, so a fork or a different branch cannot assume it. Give the dev identity Cognitive Services Contributor on the dev resource group and nothing more; prod gets a separate identity, a separate subscription, and an approval gate.

The second pipeline: prompts, agents, and models

The artefacts that change weekly need their own workflow, and its gate is quality, not approval:

  1. Prompt, agent definition, and tool schemas are files in the application repository.
  2. On pull request, run the Foundry evaluation suite against a dev deployment with a fixed dataset — groundedness, relevance, and safety evaluators at minimum.
  3. Compare against the baseline scores stored from the last release. Fail the build on regression, not just on error.
  4. On merge, publish the agent definition to staging; run the same suite against real-ish traffic.
  5. Promote to prod by pointing the prod deployment name at the new artefact.

A model version change is a code change and must go through this pipeline, because it can change output distribution as much as a prompt rewrite can. That is the argument for versionUpgradeOption = NoAutoUpgrade on high-stakes deployments — you take responsibility for the retirement calendar in exchange for no surprise behaviour changes.

Environments

dev staging prod
Boundary Separate subscription Separate subscription Separate subscription
Why a subscription, not a resource group Because TPM quota is per subscription per region — a shared subscription means dev experiments starve prod, and prod capacity requests are hard to reason about
Network Public endpoint, network_acls allow Private endpoint, mirroring prod Private endpoint only, publicNetworkAccess: Disabled
Auth Local auth disabled anyway (habit) Disabled Disabled
Deployment types Small GlobalStandard Same as prod, smaller capacity Whatever residency requires; provisioned if latency matters
Content filter Default Prod configuration Approved configuration, change-controlled
Enforced by Microsoft.CognitiveServices Azure Policy at the management group: deny accounts with local auth enabled, deny public network access in the prod management group, require diagnostic settings, restrict allowed regions

Azure Policy is what makes this a boundary rather than a convention. The three policies worth having on day one: deny disableLocalAuth = false, deny publicNetworkAccess = Enabled in prod, and DeployIfNotExists a diagnostic setting to the central Log Analytics workspace.

Rollback and blast radius

What "undo" means depends entirely on what you changed, and on this service the layers roll back at very different speeds:

Change Rollback Speed Risk
Prompt or agent definition Redeploy the previous artefact Seconds Low — this is why they belong in a separate pipeline
Model version on a deployment Update the deployment back to the previous version Minutes Low, if the old version is still offered. If it retired, there is no rollback — only forward
Deployment capacity Change it back Minutes Reducing capacity is easy; increasing it may fail on quota
Deployment type (e.g. Global → DataZone) Delete and recreate Minutes The deployment name is briefly gone — callers get 404s. Create the new one under a temporary name and switch
Connection credentials Re-apply previous Seconds Low
Account network configuration Re-apply previous commit Minutes Medium — you can lock yourself out; keep a break-glass path
Account kind, subdomain, or location There is no rollback. This is a replacement Hours High. New endpoint, all deployments gone, all data-plane role assignments gone, and the old name soft-deleted and blocking

The three Azure-specific traps, named:

  • Soft delete. A deleted Cognitive Services account keeps its globally unique name until purged. A destroy-and-recreate cycle fails on the second step with a name conflict that reads like someone else took your name. az cognitiveservices account purge is the fix; the purge_soft_delete_on_destroy feature flag automates it — and you should think hard before enabling that in prod, because the soft-delete window is also your accidental-deletion safety net.
  • Resource locks. A CanNotDelete lock on the resource group makes terraform apply fail on any replacement with an error that reads like a permissions problem. Put CanNotDelete on prod Foundry accounts deliberately, and document that pipelines must remove it consciously rather than being granted permission to ignore it.
  • Complete-mode deployments. Covered above, and worth repeating because an agent's Cosmos DB and Search index are usually in the same resource group as the account and are usually not in the same template.

Blast radius, stated once: the account is the boundary for endpoint, network, keys, and quota consumption. Everything in it shares those. A project is not a blast-radius boundary — it is an access and organisation boundary. If a workload needs isolation that survives someone else's mistake, it needs its own account, in its own resource group, ideally in its own subscription.

Drift

Foundry drifts more than most services, because the portal is designed for people to change things in — that is the whole point of the Foundry portal. Expect it and detect it:

  • Scheduled terraform plan in CI, nightly, failing the job on any non-empty plan. This is the single most effective control.
  • az deployment group what-if for the Bicep path.
  • Azure Policy compliance state for the things you care about most: local auth, public network access, diagnostic settings.
  • Activity log / Change Analysis to answer "who changed the capacity on chat-default at 03:12". Alert on Microsoft.CognitiveServices/accounts/deployments/write in prod.

The commonest real drift on this service, in order: someone raised a deployment's capacity in the portal during an incident; someone changed a content filter configuration; someone added a connection with a key. The first two are visible to terraform plan; the third often is not, because connections created in the Foundry portal may not be in your state at all — which is a good reason to manage connections in IaC from the start.

To recover a hand-edited resource: terraform plan to see the diff, decide whether the human or the code was right, then either terraform apply (code wins) or update the code and re-apply (human wins). Use terraform import for objects created outside state entirely, and add them to the module so it does not happen twice.

Teardown

terraform destroy -var-file=env/dev.tfvars

What destroy will not remove:

  • The soft-deleted account, unless purge_soft_delete_on_destroy is set. The name stays reserved.
  • Anything behind a CanNotDelete resource lock — the destroy fails partway, leaving a half-torn environment and a dirty state file.
  • Role assignments created outside the state file — including the ones the portal auto-created when a human first opened the project.
  • Agent data in a standard setup: your Cosmos DB, AI Search index, and storage containers are separate resources with their own lifecycle, and if they are in a different resource group or module, destroy does not touch them. They keep billing.
  • Purchased PTU reservations, which are a billing commitment, not a resource. Deleting the deployment stops the deployment; the reservation continues until its term ends or you exchange it.
  • Diagnostic settings and Log Analytics data created outside this module, and the ingested logs themselves, which bill for their retention period.

Next: Integrations →

← Back to the Azure AI Foundry overview · ← Previous: Getting Started