Public network access: disabled.
Ship it like infrastructure
The whole Warranty Desk stack as Bicep, deployed from GitHub Actions with no secret in the repo, into a Foundry environment where nothing has a public endpoint and every model call goes through a gateway that counts tokens.
- build time
- 8 to 10 hours
- cost
- Roughly $0.60 to $1.60 an hour while it exists, so about $25 if you leave it up for a working day3
- services
- Bicep, GitHub Actions, azd, Virtual Network, Private Link, Private DNS, Azure Firewall, API Management, Key Vault
- assumes
- Project 01, and that you have deployed anything with Bicep before. If not, the CAMPUX bootcamp covers it; this page does not.
- exams
- Overlaps AI-200, AZ-104, AZ-305
- vocabulary
- Standard setup, BYO resources, private endpoint, VNet injection, delegated subnet, federated credential, AI gateway
The problem
Projects 01 to 04 were built by hand, by you, in a subscription you own. Campux Retail's platform team will not run that. Their rules are short.
Everything is code and it deploys from a pipeline, not a laptop. Nothing the agent touches has a public endpoint: not the project, not the search index, not the order data. The pipeline holds no secret that could leak. Every model call goes through one gateway so the platform team can cap a team's tokens and see who spent what. And a second environment must be one command away, identical to the first. The platform lead's test is blunt: "if you can reach it from a coffee shop, it is not done."
- definition
- Standard setup. A Foundry project that brings its own Storage, Azure AI Search and Cosmos DB, so every byte the Agent Service stores lands in resources you own and can put behind private endpoints. The basic setup manages those for you and is the wrong choice here.
The architecture
One virtual network, private endpoints for everything, and a pipeline that holds no secret.
- Sign in. GitHub Actions authenticates to Azure with a federated credential; there is no client secret to leak.
- Provision. azd runs the Bicep from Microsoft's private-network standard-setup template, plus your additions.
- Gateway. API Management fronts the model deployment with a per-team token limit, token metrics and content safety.
- Inject. The agent client runs in a subnet you own, delegated to Microsoft.App/environments, /27 or larger.
- Search privately. Azure AI Search is reached over a private endpoint; the indexer runs in the private execution environment.
- Store privately. Agent data lands in your own Storage and Cosmos DB over private endpoints you created.
- Egress. Outbound traffic passes an Azure Firewall allowlist: Entra, Application Insights ingestion, nothing else.
| Job | AWS version | This build | Same idea? |
|---|---|---|---|
| Infrastructure as code | CloudFormation, CDK | Bicep, driven by azd | Yes |
| Pipeline identity | GitHub OIDC to an IAM role | GitHub OIDC to an Entra federated credential | Yes |
| Private access | VPC endpoints | Private endpoints plus private DNS zones | Yes |
| Agent in your network | Lambda in a VPC | Agent client VNet injection, delegated subnet1 | Yes |
| Egress control | NAT plus security groups | Azure Firewall with an FQDN allowlist | Yes |
| AI gateway | API Gateway plus custom code | API Management AI gateway policies2 | Different. Token limits and metrics are built in |
The build
In this order. Decide the network before you create anything; outbound isolation cannot be added to an existing Foundry resource, only redeployed.
-
Start from Microsoft's template
Clone the Foundry samples repository and read the Bicep in
You can list every resource the template creates from memory, and you noticed it does not create private endpoints for Search, Storage or Cosmos.infrastructure-setup-bicep/15-private-network-standard-agent-setupend to end before running it. Note what it creates and what it does not. -
The network
A virtual network with three subnets: one for private endpoints, one delegated to
Microsoft.App/environmentsat /27 or larger for the agent client, and one for a firewall. Private DNS zones for everyprivatelinkdomain you will need, linked to the network.nslookupfor the Foundry endpoint from a VM in the network returns a private IP; from your laptop it returns a public one you cannot use. -
Foundry, standard setup, private
Deploy the Foundry resource and project with public network access Disabled, a private endpoint, VNet injection into the delegated subnet, and your own Storage, AI Search and Cosmos DB as the standard setup. Then create the private endpoints for those three yourself.
Four private endpoint connections show Approved. The Foundry portal from your laptop says you cannot reach the project. Good. -
The agent, in the network
Deploy the Project 01 agent into this project. Its OpenAPI tool and file search now travel through your subnet and private endpoints; check the tool support table before you rely on any other tool.
From a VM in the network, the agent answers a warranty question. The Function App's public endpoint is disabled and it still works. -
Egress
Route the agent subnet through Azure Firewall with an allowlist of the FQDNs Foundry needs: Entra sign-in, the Application Insights ingestion endpoints, and nothing else.
The agent still works. A curl to an unrelated site from the same subnet is blocked and the firewall log shows why. -
The AI gateway
Put API Management in front of the model deployment with managed identity, and add three policies:
Set a 200 tokens-per-minute limit for a test key and get a 429 on the third question. Ours did exactly that: 163 tokens consumed on the first call, 37 remaining; 142 on the second, 0 remaining; thenllm-token-limitper team key,llm-emit-token-metricwith a team dimension, andllm-content-safety. Point the agent at the gateway, not the model.429 Token limit is exceeded. Try again in 55 seconds.Two things to check while you are there: thex-consumed-tokensandx-remaining-tokensheaders come back on every call, and the gateway reaches the model with a managed identity, so there is no key anywhere in the policy.4 -
The pipeline
GitHub Actions with a federated credential to an Entra app: no client secret anywhere.
azd provisionthenazd deployon push to main, with a manual approval environment gate for production.grep -ri secret .github/finds nothing. A push deploys; a second environment is one variable change away. -
The second environment
Create
Two identical stacks, and the evaluation scores match within noise.stagingfrom the same templates with different parameters. Run the Project 03 evaluation against it. -
Tear it down
azd down --purge
Then check for the things Bicep does not own: the private DNS zone links, the firewall's public IP, and the API Management instance, which can take a while to delete.
The resource group is empty. Today's cost is under $25, most of it the firewall and gateway hours.
Where it breaks
Cause each one on purpose. Private networking fails silently and blames DNS.
az apim create --sku-name Developer # 35 minutes to Activating -> Succeeded then an API pointing at https://<account>.openai.azure.com/openai call 1: HTTP 404 Resource not found the backend was right. the API had no operation to match the path. PUT .../apis/aoai/operations/chat # POST /deployments/{deployment}/chat/completions operation PUT: 201 policy: llm-token-limit tokens-per-minute=200, managed identity to the backend 6 identical calls through the gateway call 1: HTTP 200 consumed=163 remaining=37 call 2: HTTP 200 consumed=142 remaining=0 call 3: HTTP 429 Token limit is exceeded. Try again in 55 seconds. call 4: HTTP 429 Token limit is exceeded. Try again in 55 seconds. the cap held on the third call, and no key appears anywhere in the policy.
the create call is accepted. that is not the same as it working. PUT .../accounts/campux-inj-agent28 # subnet 10.10.2.0/28, delegated HTTP 201 ACCEPTED PUT .../accounts/campux-inj-agent27 # subnet 10.10.3.0/27, delegated HTTP 201 ACCEPTED poll provisioningState poll 1: /28=Creating /27=Creating poll 3: /28=Creating /27=Creating poll 4: /28=Failed /27=Creating final: /28=Failed /27=Succeeded the resource itself carries no error. it is in the activity log: "code":"ResourceProviderExtensionError","message":"ExtendedErrorInfo:: Kind: AmlRp, Code: BadRequest, Message: Capability host creation failed with agent messages" nothing about subnet size. you would look at the agent config for an hour first.
private endpoints are not created for you. three by hand, then the dns zone group writes the A records. az network private-endpoint list -g rg-campux-net -o table Name State Ip Fqdn pe-cosmos Succeeded 10.20.1.6 campux-cosmos.documents.azure.com pe-search Succeeded 10.20.1.5 campux-search.search.windows.net pe-storage Succeeded 10.20.1.4 campuxnet.blob.core.windows.net az network private-dns record-set a list -z privatelink.documents.azure.com -g rg-campux-net campux-cosmos 10.20.1.6 campux-cosmos-eastus 10.20.1.7 <- cosmos writes two, account and region public access off. now call storage from outside, with a good token. curl -H "Authorization: Bearer $TOKEN" "https://$SA.blob.core.windows.net/?comp=list" HTTP 403 <Error><Code>AuthorizationFailure</Code><Message>This request is not authorized to perform this operation. nothing about the network. that is a role-assignment error message. az storage container list --account-name $SA --auth-mode login ERROR: The request may be blocked by network rules of storage account. the cli says it. the api does not. now egress, for real: a container in a subnet routed 0.0.0.0/0 -> 10.20.0.4 az container logs -g rg-campux-net -n fwtest ALLOWED-login.microsoftonline.com HTTP 200 BLOCKED-example.com HTTP 000 curl-exit-35 BLOCKED-pypi.org HTTP 000 curl-exit-35 exit 35 is "SSL connect error". the allowlist works, and it will cost you an hour the first time it stops a package install.
The trade-offs
- Standard setup, or basic?
- Basic is faster and Microsoft manages the storage. Standard means you own every resource the agent writes to and can put each behind a private endpoint. Regulated data, or a platform team with rules, means standard.
- Microsoft's template, or your own Bicep?
- Their template encodes decisions that took a team months. Your own is smaller and you understand it. Start from theirs, strip what you do not use, and keep the commit history showing what you removed and why.
- A gateway in front of the model, or direct?
- Direct is one hop faster. The gateway gives per-team token limits, a metric per team, content safety, load balancing and a place to swap models without touching agents. From the second team onward, the gateway pays for itself.
- Firewall, or NAT gateway?
- A NAT gateway gives egress without inspection. A firewall gives an allowlist and a log. The allowlist is the compliance answer; the log is how you debug it.
- Bicep, or Terraform?
- Terraform works for Foundry too and is the right answer in a multi-cloud shop. Bicep is what the Foundry samples are written in, so you inherit the most tested path. Pick the one your platform team already reviews.
In the interview
"How would you deploy this for real?"
- decisionBicep from Microsoft's private-network standard-setup template, driven by azd from GitHub Actions with a federated credential.
- reasonNo secret in the pipeline, and a second environment is a parameter change.
- watchedWhat the template does not create: the private endpoints for Search, Storage and Cosmos.
"How is it network-isolated?"
- decisionPublic access disabled, private endpoints and DNS zones for every service, the agent injected into a delegated subnet, egress through a firewall allowlist.
- reasonThe platform rule: nothing reachable from outside the network.
- watchedThe indexer that silently indexed nothing until its execution environment was private.
"How do you control model spend across teams?"
- decisionAPI Management as the AI gateway with a token limit and a token metric per team key.
- reasonOne place to cap, count and swap models.
- watchedA 429 at the limit, and the per-team chart in Application Insights.
Evidence
- The two nslookups
- The same hostname resolving private from inside and useless from outside.
- The 429
- The gateway refusing the third question under a test limit, and the per-team token chart.
- The repo
- Bicep, the workflow file with no secret in it, and a README that lists what the template does not create.
- One sentence for the résumé
- "Deployed a Microsoft Foundry agent platform as Bicep from GitHub Actions with federated credentials into a private-endpoint-only network with VNet injection and firewall egress control, fronted by an API Management AI gateway enforcing per-team token limits."
Next
Notes
- Checked against Microsoft Learn on 2 September 2026. The standard setup requires bring-your-own Storage, Azure AI Search and Cosmos DB; VNet injection needs a subnet delegated to
Microsoft.App/environmentsat /27 or larger; private endpoints for the three BYO resources are not created by the Foundry deployment; outbound networking cannot be added to an existing resource. The sample templates are11-private-network-basic-vnet,15-private-network-standard-agent-setupand19-private-network-agent-toolsin the foundry-samples repository. - API Management's AI gateway policies used here are
llm-token-limit,llm-emit-token-metricandllm-content-safety; semantic caching (llm-semantic-cache-storeand-lookup) needs a Redis-compatible cache and is left out to keep the bill down. The AI gateway inside the Foundry portal was in preview, and a gateway created from there is public until you isolate it yourself. - Run in a real Azure subscription on 3 September 2026, in East US. The subnet test went through the ARM API at
api-version=2025-06-01withnetworkInjectionsset toscenario: agent; note the Azure CLI has no flag for this, so VNet injection is an ARM or Bicep property, which is part of why the samples are Bicep. The gateway test used a Developer-tier instance, anllm-token-limitpolicy andauthentication-managed-identityagainst agpt-5-minideployment. Azure Firewall, the private endpoints and the DNS steps were finished on 4 September 2026 in East US, in one resource group torn down the same hour: a Standard-tier firewall with an FQDN allowlist, private endpoints on Storage, Azure AI Search and Cosmos DB, the three matching private DNS zones linked to the virtual network, and public network access disabled on all three resources. Egress was driven through the firewall for real, from a container in a subnet whose route table sent0.0.0.0/0to the firewall private address, not merely configured and inspected. The one step still not exercised end to end is the indexer running in the private execution environment, which needs documents and an index this run did not build. - Hourly list prices in East US, checked against the Azure retail price API on 3 September 2026: Azure Firewall Basic $0.395 an hour and Standard $1.25, API Management Developer $0.066 and Standard $0.94, Azure AI Search Basic $0.101, private endpoints about a cent each an hour. Firewall is most of the bill, which is why the range above moves so much with the SKUs you pick. Everything here bills while it exists, not while it is used, so the number that matters is how many hours you leave it up: deploy in the morning and tear down before dinner, or this footnote is wrong by a lot.