- name
- foundry-vnet-deploy
- description
- Deploy Azure AI Foundry with **Agent Setup inside a private VNet** — the `15-private-network-standard-agent-setup` Bicep reference architecture. Supports new/existing VNets, resource reuse (CosmosDB / Storage / AI Search / private DNS zones), hosted-agent RBAC, App Insights, and spoke-side peering + APIM-DNS-zone link to a Citadel hub VNet. Read the full skill body for the guided interview, retry logic, and subnet sizing — do not deploy from this summary. USE FOR: foundry private vnet, network-secured foundry, agent injection, capability host, citadel hub peering, vnet-isolated citadel spoke, apim private dns zone link, central dns at scale, hub-spoke private dns, InvalidPrivateDnsZoneIds. DO NOT USE FOR: azd-based deploys (use threadlight-deploy or foundry-hosted-agents), public-network Foundry, APIM cross-resource (use foundry-cross-resource), tenant isolation (use azure-tenant-isolation), Citadel app-layer onboarding (use citadel-spoke-onboarding for APIM products + Foundry connection).
- metadata
- {"version":"1.3.2"}
# Foundry VNet Deploy — Agent Setup inside a Private VNet
Before hosted artifact preparation, select the
[versioned hosted profile](../foundry-hosted-agents/references/hosted-contract.json)
and complete its capability/permission feasibility gate. An existing VNet,
capability host or ARM success does not prove serving configuration, runtime
pull or result-reader access. Preserve the Basic/Standard topology and use the
[shared recovery contract](../foundry-hosted-agents/references/operation-recovery.md)
instead of changing network/authentication after an uncertain result.
## 1. Goal
Guide the user step by step to deploy **Azure AI Foundry with Agent in a private VNet** using the Bicep files from the `15-private-network-standard-agent-setup` reference project. The skill collects all required parameters, generates the `.bicepparam` file, and runs the deployment.
> **Optional scenario hint.** When the user invokes the skill, they may pass a one-line scenario hint such as `"new VNet in swedencentral"` or `"existing VNet with existing DNS zones"`. Use it to pre-fill defaults during the interview wherever it applies.
> **Day-2 lifecycle companion.** The full workflow is for greenfield setup. For an existing private project missing only its Basic host, reuse only the canonical project-host module after read-only verification and explicit authorization; never replay the full deployment to repair one missing prerequisite. Follow the [hosted deployment preflight](../foundry-hosted-agents/references/deployment-preflight.md). For other **Day-2 capability host lifecycle** operations (idempotent re-create, delete, soft-delete + purge, recovery), see [`foundry-caphost-lifecycle`](../foundry-caphost-lifecycle/SKILL.md). Those destructive operations are not default troubleshooting.
## 2. Architecture being deployed
The deployment creates the following secure network architecture:
```
┌─────────────────────────────────────────────────────────┐
│ Resource Group │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Virtual Network (VNet) │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────────────┐ │ │
│ │ │ Agent Subnet │ │ Private Endpoint Subnet │ │ │
│ │ │ (delegated to │ │ │ │ │
│ │ │ Container Apps) │ │ ● AI Services PE │ │ │
│ │ │ │ │ ● AI Search PE │ │ │
│ │ │ Network │ │ ● Storage PE (blob) │ │ │
│ │ │ Injection for │ │ ● CosmosDB PE (SQL) │ │ │
│ │ │ AI Agents │ │ │ │ │
│ │ └──────────────────┘ └──────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌────────────┐ ┌───────────────────┐ │
│ │ AI Services │ │ AI Search │ │ Storage Account │ │
│ │ (Foundry) │ │ (standard) │ │ (StorageV2, ZRS) │ │
│ │ ● SKU S0 │ │ ● disabled │ │ ● public access │ │
│ │ ● public │ │ public │ │ disabled │ │
│ │ disabled │ │ access │ │ ● shared key │ │
│ │ ● network │ │ │ │ disabled │ │
│ │ injection │ │ │ │ │ │
│ └──────────────┘ └────────────┘ └───────────────────┘ │
│ │
│ ┌──────────────┐ ┌────────────────────────────────────┐ │
│ │ CosmosDB │ │ Private DNS Zones (6 zones) │ │
│ │ (SQL API) │ │ ● privatelink.services.ai.azure.. │ │
│ │ ● public │ │ ● privatelink.openai.azure.com │ │
│ │ disabled │ │ ● privatelink.cognitiveservices.. │ │
│ │ ● local auth │ │ ● privatelink.search.windows.net │ │
│ │ disabled │ │ ● privatelink.blob.core.windows.. │ │
│ │ │ │ ● privatelink.documents.azure.com │ │
│ └──────────────┘ └────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ AI Foundry Account Capability Host │ │
│ │ ● capabilityHostKind: Agents │ │
│ │ ● customerSubnet → Agent Subnet (VNet injection) │ │
│ │ ● Replaces manual createCapHost.sh │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ AI Foundry Project │ │
│ │ ● System-Assigned Managed Identity │ │
│ │ ● Connections: CosmosDB, Storage, AI Search │ │
│ │ ● Project Capability Host (Agents) with: │ │
│ │ - vectorStoreConnections → AI Search │ │
│ │ - storageConnections → Blob Storage │ │
│ │ - threadStorageConnections → CosmosDB │ │
│ │ ● Depends on Account Capability Host │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Role Assignments (Project SMI): │
│ ● Storage Blob Data Contributor (account level) │
│ ● Storage Blob Data Owner (container level, scoped) │
│ ● Cosmos DB Operator (account level) │
│ ● Cosmos DB Built-in Data Contributor (enterprise_memory)│
│ ● AI Search Index Data Contributor │
│ ● AI Search Service Contributor │
│ └───────────────────────────────────────────────────────┘
└──────────────────────────────────────────────────────────┘
```
## 3. Workflow
Follow these steps IN ORDER. Use the `ask_user` tool for every question.
### Step 0: Choose the template (Decision Guide)
This skill vendors **two** Foundry network-isolation templates. Pick one before
the interview — they share Steps 1-8b/12/13 but differ on BYO resources and DNS.
| You need… | Template | `TEMPLATE_DIR` |
|---|---|---|
| **BYO** Azure AI Search + Storage + Cosmos wired as project connections (vector store, thread storage, file storage) | **standard-agent** (template 15) | `templates/standard-agent` |
| **Platform-managed** storage — no BYO Search/Storage/Cosmos, smallest network surface, optional private ACR | **basic-vnet** (template 11) | `templates/basic-vnet` |
Ask the user:
1. **Do your agents need BYO Search / Storage / Cosmos connected to the project?**
- **Yes / not sure / need vector search + custom thread storage** → `standard-agent`.
- **No — platform-managed storage is fine** → `basic-vnet`.
Set the working directory for every later deploy/verify command:
```bash
# standard:
TEMPLATE_DIR="templates/standard-agent"
# basic:
TEMPLATE_DIR="templates/basic-vnet"
```
**What differs by fork** (both keep the four awesome-gbb integrations —
hosted-agent developer RBAC, App Insights project-MI roles, Citadel spoke
peering, APIM DNS link):
| Aspect | standard-agent (15) | basic-vnet (11) |
|---|---|---|
| BYO resources | AI Search + Storage + Cosmos (create or reuse) | none (platform-managed) |
| Project connections | CosmosDb, CognitiveSearch, AzureStorageAccount (+ appinsights) | appinsights only |
| Private endpoints | AI Services, Search, Storage(blob), Cosmos(SQL) (4) | AI Services + Monitor PLS (2) + ACR (optional → 3) |
| Private DNS zones | 6 (services.ai, openai, cognitiveservices, search, blob, documents) | 3 app (services.ai, openai, cognitiveservices) + `azurecr.io` (if ACR) + 4 monitor zones |
| Optional private ACR | no | yes (`enableContainerRegistry`, default **true**; `developerIpCidr`) |
| Account capability host | yes (`accountCapHost`) | project caphost only (`projectCapHost` = `caphostproj`) |
| Role assignments | 6 (project-MI over BYO) + optional developer/telemetry | telemetry (LAW Reader + Azure AI User) + optional developer + ACR AcrPull |
| Deploy time | 45-90 min | 30-60 min (no BYO wiring) |
**Basic is platform-managed backing storage, not absence of a project host.**
Read back the project host's `capabilityHostKind: Agents` and
`provisioningState: Succeeded` before registering a hosted version, including
when using raw SDK or brownfield `azd deploy`. If the inventory is empty, only
the explicitly authorized
[`add-project-capability-host.bicep`](templates/basic-vnet/modules-network-secured/add-project-capability-host.bicep)
module is needed against the existing account/project. Do not add the Standard
account host to Basic. Existing BYO connections mean stop and resolve mode,
not overwrite them with Basic defaults.
> Steps 7 (BYO existing resources) and 8 (6-zone DNS map) apply to
> **standard-agent only**. On **basic-vnet**, skip Step 7 entirely; in Step 8
> the DNS map uses the basic zone set (`existingDnsZones` = 3 app zones +
> optional `azurecr.io`; `existingMonitorDnsZones` = 4 monitor zones), and you
> additionally ask about the optional private ACR (`enableContainerRegistry`,
> `developerIpCidr`).
### Step 1: Verify prerequisites
Before starting, verify:
1. Apply `azure-tenant-isolation` before the first Azure command in each shell:
both isolated CLI config directories and the approved tenant/subscription
must be explicit. Never log in or switch the global default as a diagnostic.
2. Run `az account show` in that isolated context; confirm the intended target
with the user. If authentication is missing, use the tenant-isolation workflow.
3. Establish the network model and execution location before selecting parameters.
The two vendored templates cover **BYO VNet**, not **Managed VNet**. If Managed
VNet is requested, stop for a separately approved design rather than treating
its outbound rules as interchangeable with these templates.
4. Ask who owns the VNet, private DNS zones, NSGs/routes and reverse peering,
including their resource groups/subscriptions. Reusing resources does not
authorize editing them. Collect existing IaC and identify additive changes;
keep the vendored templates and their Citadel interfaces intact.
5. Record where each caller runs (agent runtime, SDK client, browser/Playground)
and how it reaches private DNS and TCP 443. A management-plane check from
outside the VNet cannot establish those runtime paths. A Foundry private
endpoint covers ingress, not agent egress. Use the
[runbook's caller/path intake](../foundry-network-runbook/SKILL.md#31-identify-the-caller-and-network-model)
to separate hosted code from platform tool/data-proxy calls.
### Step 2: Subscription and Resource Group
Ask the user:
1. **Subscription**: Do they want to use the current subscription or change it? If they want to change, ask for the subscription ID.
2. **Resource Group**: Does the resource group already exist or does it need to be created?
- If it needs to be created, ask for the name and location.
- If it already exists, ask for the name.
### Step 3: Location
Ask for the deployment region. The allowed regions are:
- westus, eastus, eastus2, japaneast, francecentral, spaincentral, uaenorth
- southcentralus, italynorth, germanywestcentral, brazilsouth, southafricanorth
- australiaeast, swedencentral, canadaeast, westeurope, westus3, uksouth, southindia
- koreacentral, polandcentral, switzerlandnorth, norwayeast (Class B and C only)
Present the options as a list and offer `swedencentral` as the recommended one.
### Step 4: AI service and project name
Ask:
1. **aiServices**: Prefix for the AI Services resource (default: `foundry`). A unique suffix will be appended automatically.
2. **firstProjectName**: Project name (default: `project`).
3. **displayName**: Visible project name (default: same as the project name).
4. **projectDescription**: Project description (default: `A project for the AI Foundry account with network secured deployed Agent`).
5. **accountCapHost**: Name of the account-level capability host (default: `caphostacct`). This resource sets `customerSubnet` for the agent runtime network injection.
### Step 5: Model configuration
Ask:
1. **modelName**: Name of the model to deploy (default: `gpt-4.1`). Common options: gpt-4.1, gpt-4o, gpt-4o-mini, gpt-4.1-mini, gpt-4.1-nano.
2. **modelFormat**: Model provider (default: `OpenAI`).
3. **modelVersion**: Model version. Depends on the chosen model.
4. **modelSkuName**: Deployment SKU (default: `GlobalStandard`). Options: GlobalStandard, Standard, ProvisionedManaged.
5. **modelCapacity**: TPM (tokens per minute) in thousands (default: `30` = 30K TPM).
### Step 6: Network configuration (VNet)
Ask the user:
**Are you going to use an existing VNet or create a new one?**
> **Subnet sizing (agent injection).** The agent subnet is delegated to
> `Microsoft.App/environments` and consumes ~1 IP per ~10 running agent pods.
> The platform caps concurrent agent sessions at **50 per subscription per
> region** — that ceiling does **not** scale with a bigger subnet. A larger
> subnet buys **project density** (~250 projects at low traffic, as few as ~25
> at full scale) and **upgrade/scale headroom**, not more sessions.
>
> | Agent subnet CIDR | Usable IPs | Concurrent sessions | Use when |
> |---|---|---|---|
> | **/24** | 251 | 50 (platform cap) + upgrade buffer | **Production default** (Microsoft-recommended) |
> | /25 | 123 | 50 (platform cap) | Buffer between /26 and /24 |
> | /26 | 59 | ~50 — **minimum to reach the cap** | Smallest that supports the full 50 |
> | /27 | 27 | ~17 | Dev/test only — **minimum, risky** |
>
> Rules: **RFC-1918 only** (10/8, 172.16/12, 192.168/16) — the CGNAT
> `100.64.0.0/10` range is **not supported** (routing failures). Avoid
> `172.17.0.0/16` (reserved by Docker bridge). Target **< 80 % utilization**.
> Prompt-agent revisions do **not** consume subnet IPs; hosted-agent revisions
> do (100 active / 1000 total per agent name; ~200 hosted agents per Foundry
> instance). The PE subnet only needs one IP per private endpoint (a /27 is
> plenty). Full IP math and exhaustion symptoms:
> [`references/agent-networking.md`](references/agent-networking.md).
### Option A: New VNet
Ask:
1. **vnetName**: VNet name (default: `agent-vnet`).
2. **vnetAddressPrefix**: Address space (default: `192.168.0.0/16`). Also supports `10.x.x.x/16` or `172.16.x.x/16`.
3. **agentSubnetPrefix**: CIDR of the agent subnet (default: automatically calculated as the first /24 of the address space).
4. **peSubnetPrefix**: CIDR of the private endpoint subnet (default: automatically calculated as the second /24).
5. **agentSubnetName**: Name of the agent subnet (default: `agent-subnet`).
6. **peSubnetName**: Name of the PE subnet (default: `pe-subnet`).
### Option B: Existing VNet
Ask:
1. **existingVnetResourceId**: Full resource ID of the VNet. Format: `/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/virtualNetworks/{name}`.
2. **agentSubnetName**: Name of the agent subnet (will be created if it does not exist). IMPORTANT: This subnet needs delegation to `Microsoft.App/environments`.
3. **peSubnetName**: Name of the private endpoint subnet (will be created if it does not exist).
4. **agentSubnetPrefix**: CIDR of the agent subnet (mandatory if the subnet does not exist). WARNING: Must not overlap with existing subnets.
5. **peSubnetPrefix**: CIDR of the PE subnet (mandatory if the subnet does not exist).
### Step 7: Existing resources (optional)
Ask whether the user has existing resources they want to reuse:
1. **Do you have an existing AI Search?** If yes → ask for `aiSearchResourceId` (full ARM format).
2. **Do you have an existing Storage Account?** If yes → ask for `azureStorageAccountResourceId`.
3. **Do you have an existing CosmosDB?** If yes → ask for `azureCosmosDBAccountResourceId`.
If none are provided, all of them will be created automatically. The new resources are created with:
- **AI Search**: Standard SKU, public access disabled, local auth enabled with AAD.
- **Storage**: StorageV2, ZRS (or GRS in southindia/westus), public access disabled, shared key disabled.
- **CosmosDB**: Global Document DB, Session consistency, public access disabled, local auth disabled.
### Step 8: Private DNS zones (optional)
Ask:
**Do you have existing private DNS zones that you want to reuse?**
If NO → all zones will be created automatically (6 zones). Continue to step 9.
If YES → ask:
1. **dnsZonesSubscriptionId**: Subscription where the DNS zones are located (leave empty if it is the same as the deployment).
2. For each zone, ask whether it exists and which resource group it is in:
- `privatelink.services.ai.azure.com`
- `privatelink.openai.azure.com`
- `privatelink.cognitiveservices.azure.com`
- `privatelink.search.windows.net`
- `privatelink.blob.core.windows.net`
- `privatelink.documents.azure.com`
The format is an object where each key is the zone name and the value is the resource group (empty = create a new one).
> **Central DNS at scale (hub-and-spoke).** When the spoke is deployed into an enterprise hub-and-spoke topology, the 6 zones above are typically owned by the **platform team in a separate subscription** (often the connectivity hub). The `dnsZonesSubscriptionId` parameter combined with the per-zone resource-group map in `existingDnsZones` is exactly the pattern described in Microsoft's Cloud Adoption Framework — see [Private Link and DNS integration at scale](https://learn.microsoft.com/azure/cloud-adoption-framework/ready/azure-best-practices/private-link-and-dns-integration-at-scale#private-link-and-dns-integration-in-hub-and-spoke-network-architectures) for the canonical reference architecture. The deployment principal needs `Private DNS Zone Contributor` on each zone in the hub subscription to create the VNet links; without this you will hit `InvalidPrivateDnsZoneIds` at deploy time.
>
> - For DNS troubleshooting, including `InvalidPrivateDnsZoneIds`, use
> [`foundry-network-runbook`](../foundry-network-runbook/SKILL.md#5-pre-flight-at-scale-cross-subscription-dns).
> With corporate/custom DNS, record the resolver path as well as zone ownership.
> On-premises conditional forwarders use the recommended public service
> namespaces toward an Azure DNS Private Resolver inbound endpoint or existing
> Azure-hosted forwarder, not directly toward `168.63.129.16`. A VNet link or
> VPN connection alone does not configure client DNS.
### Step 8b: Hosted agent developers (optional but recommended)
Users / service principals that are going to **create hosted agents** in this Foundry need:
- `Managed Identity Operator` on the Foundry account
- `Network Contributor` on the agent injection subnet
Without these permissions, hosted agent creation will fail with 403 errors when provisioning the MIs or NICs. The template can assign them automatically.
Ask:
1. **Which users / groups / SPs are going to create hosted agents in this Foundry?**
- Ask for a list of **AAD objectIds** (you can get them with `az ad signed-in-user show --query id -o tsv` or `az ad user show --id <upn> --query id -o tsv`).
- If the user does not want to use it now, leave the list empty and it can be added manually later.
2. **Principal type** (User / Group / ServicePrincipal). Default `User`. All IDs must be of the same type (run the module twice if you need to mix types).
### Step 8c: Application Insights + Log Analytics (recommended)
The official [hosted agent permissions](https://learn.microsoft.com/azure/foundry/agents/concepts/hosted-agent-permissions) doc lists App Insights + Log Analytics Workspace as **required resources**:
- They enable agent traces, logs and metrics
- Required for the **evaluations** feature
- The template creates them + creates an `appinsights` connection in the project + assigns to the project MI:
- `Log Analytics Reader` on the workspace
Auf GitHub ansehen