# Azure Virtual Network

<Image
  src={azureIcon}
  alt="Azure Virtual Network logo"
  width={100}
  height={100}
  class:list={'float-inline-left icon'}
  data-zoom-off
/>

This article is the reference for the Aspire Azure Virtual Network integration. It enumerates the AppHost APIs — with examples for both `AppHost.cs` and `apphost.mts` — that you use to configure virtual networks, subnets, NAT gateways, network security groups (NSGs), private endpoints, and network security perimeters in your [`AppHost`](/get-started/app-host/) project.

[Azure Virtual Network](https://learn.microsoft.com/azure/virtual-network/) enables Azure resources to securely communicate with each other, the internet, and on-premises networks.

## Hosting integration

The Azure Virtual Network hosting integration models network resources as the following types:

- `AzureVirtualNetworkResource`: Represents an Azure Virtual Network.
- `AzureSubnetResource`: Represents a subnet within a virtual network.
- `AzureNetworkSecurityGroupResource`: Represents a network security group for traffic control.
- `AzureNatGatewayResource`: Represents a NAT gateway for deterministic outbound IP addresses.
- `AzurePublicIPAddressResource`: Represents a public IP address.
- `AzureNetworkSecurityPerimeterResource`: Represents an Azure Network Security Perimeter for PaaS service isolation.
**Note:** The Azure Virtual Network integration is a **hosting** and **publish-only**
  integration. It provisions Azure networking infrastructure (virtual networks,
  subnets, NSGs, etc.) and has no corresponding client integration. During local
  development, these resources are not provisioned in your Azure subscription.

## Installation

To start building an Aspire app that uses Azure Virtual Network, install the [📦 Aspire.Hosting.Azure.Network](https://www.nuget.org/packages/Aspire.Hosting.Azure.Network) NuGet package:

```bash title="Terminal"
aspire add azure-network
```

<LearnMore>
  Learn more about [`aspire add`](/reference/cli/commands/aspire-add/) in the
  command reference.
</LearnMore>

Or, choose a manual installation approach:

```csharp title="C# — AppHost.cs"
#:package Aspire.Hosting.Azure.Network@*
```

```xml title="XML — AppHost.csproj"
<PackageReference Include="Aspire.Hosting.Azure.Network" Version="*" />
```

```bash title="Terminal"
aspire add azure-network
```

<LearnMore>
  Learn more about [`aspire add`](/reference/cli/commands/aspire-add/) in the
  command reference.
</LearnMore>

This updates your `aspire.config.json` with the Azure Network hosting integration package:

```json title="aspire.config.json" ins={3}
{
  "packages": {
    "Aspire.Hosting.Azure.Network": "13.3.0"
  }
}
```
**Tip:** When you call any of the Azure Virtual Network APIs, it implicitly calls
  `AddAzureProvisioning`—which adds support for generating Azure resources
  dynamically during app startup. The app must configure the appropriate
  subscription and location. For more information, see [Local provisioning:
  Configuration](/integrations/cloud/azure/local-provisioning/#configuration).

### Add a virtual network

To add a virtual network, call `AddAzureVirtualNetwork` (or `addAzureVirtualNetwork`) on the `builder` instance:

```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);

var vnet = builder.AddAzureVirtualNetwork("vnet");

// After adding all resources, run the app...
builder.Build().Run();

````
```typescript title="TypeScript — apphost.mts"
import { createBuilder } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

const vnet = await builder.addAzureVirtualNetwork("vnet");

// After adding all resources, run the app...
await builder.build().run();
````

By default, the virtual network uses the address prefix `10.0.0.0/16`. You can specify a custom address prefix:

```csharp title="C# — AppHost.cs"
var vnet = builder.AddAzureVirtualNetwork("vnet", "10.1.0.0/16");
```

```typescript title="TypeScript — apphost.mts"
const vnet = await builder.addAzureVirtualNetwork('vnet', '10.1.0.0/16');
```

### Add subnets

Call `AddSubnet` (or `addSubnet`) on the virtual network builder to add a subnet with a name and address prefix:

```csharp title="C# — AppHost.cs"
var vnet = builder.AddAzureVirtualNetwork("vnet");

var subnet = vnet.AddSubnet("my-subnet", "10.0.1.0/24");

````
```typescript title="TypeScript — apphost.mts"
const vnet = await builder.addAzureVirtualNetwork("vnet");

const subnet = vnet.addSubnet("my-subnet", "10.0.1.0/24");
````

You can add multiple subnets with different address ranges to a single virtual network. Subnet address prefixes must fall within the virtual network's address space (for example, `10.0.1.0/24` is valid within a `10.0.0.0/16` network).

### Add NAT gateways

A [NAT gateway](https://learn.microsoft.com/azure/nat-gateway/) provides deterministic outbound public IP addresses for subnet resources. Call `AddNatGateway` (or `addNatGateway`) to create a NAT gateway, then associate it with a subnet using `WithNatGateway` (or `withNatGateway`):

```csharp title="C# — AppHost.cs"
var natGateway = builder.AddNatGateway("nat");

var vnet = builder.AddAzureVirtualNetwork("vnet");
var subnet = vnet.AddSubnet("aca-subnet", "10.0.0.0/23")
.WithNatGateway(natGateway);

````
```typescript title="TypeScript — apphost.mts"
const natGateway = await builder.addNatGateway("nat");

const vnet = await builder.addAzureVirtualNetwork("vnet");
const subnet = vnet.addSubnet("aca-subnet", "10.0.0.0/23")
    .withNatGateway(natGateway);
````

By default, a public IP address is automatically created for the NAT gateway. To provide an explicit public IP address for full control, call `AddPublicIPAddress` (or `addPublicIPAddress`) and associate it with `WithPublicIPAddress` (or `withPublicIPAddress`):

```csharp title="C# — AppHost.cs"
var pip = builder.AddPublicIPAddress("nat-pip");
var natGateway = builder.AddNatGateway("nat")
    .WithPublicIPAddress(pip);
```

```typescript title="TypeScript — apphost.mts"
const pip = await builder.addPublicIPAddress('nat-pip');
const natGateway = await builder.addNatGateway('nat').withPublicIPAddress(pip);
```

For advanced settings like idle timeout or availability zones, use [`ConfigureInfrastructure`](/integrations/cloud/azure/customize-resources/).

### Add network security groups

[Network security groups (NSGs)](https://learn.microsoft.com/azure/virtual-network/network-security-groups-overview) control inbound and outbound traffic flow on subnets. The integration provides two APIs for configuring NSGs: a **shorthand API** for common scenarios and an **explicit API** for full control.

#### Shorthand API

The shorthand API uses fluent methods on subnet builders that automatically create an NSG, auto-increment priority, and auto-generate rule names:

```csharp title="C# — AppHost.cs"
var vnet = builder.AddAzureVirtualNetwork("vnet");

var subnet = vnet.AddSubnet("web", "10.0.1.0/24")
.AllowInbound(
port: "443",
from: AzureServiceTags.AzureLoadBalancer,
protocol: SecurityRuleProtocol.Tcp)
.DenyInbound(from: AzureServiceTags.Internet);

````
```typescript title="TypeScript — apphost.mts"
const vnet = await builder.addAzureVirtualNetwork("vnet");

const subnet = vnet.addSubnet("web", "10.0.1.0/24")
    .allowInbound("443", "AzureLoadBalancer", undefined, "Tcp")
    .denyInbound(undefined, "Internet");
````

The available shorthand methods are:

- `AllowInbound` (or `allowInbound`) — Allow inbound traffic.
- `DenyInbound` (or `denyInbound`) — Deny inbound traffic.
- `AllowOutbound` (or `allowOutbound`) — Allow outbound traffic.
- `DenyOutbound` (or `denyOutbound`) — Deny outbound traffic.

Priority auto-increments by 100 (100, 200, 300...) and rule names are auto-generated from the access, direction, port, and source (for example, `allow-inbound-443-AzureLoadBalancer`).
**Note:** The `SecurityRuleProtocol`, `SecurityRuleDirection`, and `SecurityRuleAccess`
  types require a `using` directive for the `Azure.Provisioning.Network`
  namespace. The `AzureServiceTags` class is in the `Aspire.Hosting.Azure`
  namespace.

#### Explicit API

For full control over security rules, create a standalone NSG with `AddNetworkSecurityGroup` (or `addNetworkSecurityGroup`) on the `builder` and attach it to a subnet with `WithNetworkSecurityGroup` (or `withNetworkSecurityGroup`):

```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);

var vnet = builder.AddAzureVirtualNetwork("vnet");

var nsg = builder.AddNetworkSecurityGroup("web-nsg")
.WithSecurityRule(new AzureSecurityRule
{
Name = "allow-https",
Priority = 100,
Direction = SecurityRuleDirection.Inbound,
Access = SecurityRuleAccess.Allow,
Protocol = SecurityRuleProtocol.Tcp,
DestinationPortRange = "443"
});

var subnet = vnet.AddSubnet("web-subnet", "10.0.1.0/24")
.WithNetworkSecurityGroup(nsg);

````
```typescript title="TypeScript — apphost.mts"
import { createBuilder } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

const vnet = await builder.addAzureVirtualNetwork("vnet");

const nsg = builder.addNetworkSecurityGroup("web-nsg")
    .withSecurityRule({
        name: "allow-https",
        priority: 100,
        direction: "Inbound",
        access: "Allow",
        protocol: "Tcp",
        destinationPortRange: "443"
    });

const subnet = vnet.addSubnet("web-subnet", "10.0.1.0/24")
    .withNetworkSecurityGroup(nsg);
````

The `AzureSecurityRule` type provides sensible defaults—`SourcePortRange`, `SourceAddressPrefix`, and `DestinationAddressPrefix` all default to `"*"`. The `Name`, `Priority`, `Direction`, `Access`, `Protocol`, and `DestinationPortRange` properties are required.

A single NSG can be shared across multiple subnets.
**Caution:** Don't mix the shorthand and explicit APIs on the same subnet. Calling
  `WithNetworkSecurityGroup` after shorthand methods throws an
  `InvalidOperationException` to prevent silent rule loss.

### Add private endpoints

[Private endpoints](https://learn.microsoft.com/azure/private-link/) enable secure connectivity to Azure services over a private network. Call `AddPrivateEndpoint` (or `addPrivateEndpoint`) on a subnet builder and pass the Azure resource to connect to:

```csharp title="C# — AppHost.cs"
var vnet = builder.AddAzureVirtualNetwork("vnet");
var peSubnet = vnet.AddSubnet("private-endpoints", "10.0.2.0/24");

var storage = builder.AddAzureStorage("storage");
var blobs = storage.AddBlobs("blobs");

peSubnet.AddPrivateEndpoint(blobs);

````
```typescript title="TypeScript — apphost.mts"
const vnet = await builder.addAzureVirtualNetwork("vnet");
const peSubnet = vnet.addSubnet("private-endpoints", "10.0.2.0/24");

const storage = await builder.addAzureStorage("storage");
const blobs = storage.addBlobs("blobs");

peSubnet.addPrivateEndpoint(blobs);
````

When you add a private endpoint, the following resources are automatically created:

1. A **Private DNS Zone** for the service (for example, `privatelink.blob.core.windows.net`).
2. A **Virtual Network Link** connecting the DNS zone to your virtual network.
3. A **DNS Zone Group** on the private endpoint for automatic DNS registration.
4. The target resource is automatically configured to **deny public network access**.

The private DNS zone ensures that services within the virtual network automatically resolve the target resource's hostname to its private IP address. Resources that reference the target (for example, via `WithReference`) use the same connection information—DNS resolution handles routing traffic over the private network.
**Caution:** Adding a private endpoint automatically disables public network access on the
  target resource. This means any clients outside the virtual network won't be
  able to reach the resource. See [Override public network
  access](#override-public-network-access) if you need to keep public access
  enabled.

#### Override public network access

To keep public access enabled on a resource that has a private endpoint, use `ConfigureInfrastructure` to override the automatic lockdown:

```csharp title="C# — AppHost.cs"
storage.ConfigureInfrastructure(infra =>
{
    var storageAccount = infra.GetProvisionableResources()
        .OfType<StorageAccount>()
        .Single();
    storageAccount.PublicNetworkAccess = StoragePublicNetworkAccess.Enabled;
});
```
**Note:** TypeScript AppHosts can use curated provisioning helper APIs when an
  integration exposes them. This example directly customizes Azure.Provisioning
  objects through `ConfigureInfrastructure`, which is currently C#-only unless
  the integration wraps the scenario in a polyglot-friendly helper.

#### Service-specific requirements

##### Azure Service Bus

[Azure Service Bus](/integrations/cloud/azure/azure-service-bus/azure-service-bus-get-started/) requires the **Premium** tier to support private endpoints. When you add a private endpoint for a Service Bus resource, Aspire automatically sets the SKU to Premium.

##### Azure SQL Server

[Azure SQL Server](/integrations/cloud/azure/azure-sql-database/azure-sql-database-get-started/) uses a deployment script to grant your application's managed identity access to the SQL database. When you add a private endpoint for a SQL Server resource, the deployment script needs to run inside the virtual network via Azure Container Instances (ACI). Aspire automatically creates a delegated subnet and a storage account for the deployment script container. For details on customizing this behavior, see [Admin deployment script](/integrations/cloud/azure/azure-sql-database/azure-sql-database-host/#admin-deployment-script).

##### Azure Container Registry

[Azure Container Registry](/integrations/cloud/azure/azure-container-registry/azure-container-registry-host/) requires the **Premium** SKU to support private endpoints. When you add a private endpoint for an Azure Container Registry resource, Aspire automatically upgrades the SKU to Premium.
**Caution:** When public network access is disabled on the registry (the default when a
  private endpoint is added), [`aspire
  deploy`](/reference/cli/commands/aspire-deploy/) cannot push container images
  to the registry unless it runs from a machine that has network access through
  the private endpoint (for example, an Azure DevOps or GitHub Actions
  self-hosted runner inside the virtual network). Consider using self-hosted
  runners in the same VNet, or re-enable public access on the registry for
  deployment scenarios.

### Add a network security perimeter

[Azure Network Security Perimeters (NSPs)](https://learn.microsoft.com/azure/private-link/network-security-perimeter-concepts) provide a logical security boundary for Azure PaaS services such as Storage, Key Vault, Cosmos DB, and SQL. Where virtual networks and private endpoints secure connectivity at the infrastructure layer, NSPs operate at the PaaS layer—grouping resources so they can communicate with each other while restricting public access via access rules.

#### Why use a network security perimeter?

NSPs complement private endpoints with a higher-level grouping model. Instead of configuring a private endpoint for every resource pair, you place all related PaaS resources inside a single perimeter. Resources within the perimeter communicate freely, and you define inbound and outbound access rules that apply to the entire boundary. This is useful when:

- You have many PaaS resources that need to talk to each other but shouldn't be publicly accessible.
- You want a single set of access rules (IP ranges, subscription allow-lists, FQDN allow-lists) that apply uniformly to the group.
- You want to audit traffic before enforcing restrictions by using **Learning** mode.

#### Create a perimeter

Call `AddNetworkSecurityPerimeter` (or `addNetworkSecurityPerimeter`) on the `builder` instance to create an NSP with a default profile:

```csharp title="C# — AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);

var nsp = builder.AddNetworkSecurityPerimeter("my-nsp");

// After adding all resources, run the app...
builder.Build().Run();

````
```typescript title="TypeScript — apphost.mts"
import { createBuilder } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

const nsp = await builder.addNetworkSecurityPerimeter("my-nsp");

// After adding all resources, run the app...
await builder.build().run();
````

#### Add access rules

Access rules control traffic flowing into and out of the perimeter. Call `WithAccessRule` (or `withAccessRule`) and pass an `AzureNspAccessRule` to define a rule:

```csharp title="C# — AppHost.cs"
var nsp = builder.AddNetworkSecurityPerimeter("my-nsp")
    .WithAccessRule(new AzureNspAccessRule
    {
        Name = "allow-my-ip",
        Direction = NetworkSecurityPerimeterAccessRuleDirection.Inbound,
        AddressPrefixes = { "203.0.113.0/24" }
    })
    .WithAccessRule(new AzureNspAccessRule
    {
        Name = "allow-outbound-fqdn",
        Direction = NetworkSecurityPerimeterAccessRuleDirection.Outbound,
        FullyQualifiedDomainNames = { "*.blob.core.windows.net" }
    });
```
```typescript title="TypeScript — apphost.mts"
const nsp = await builder.addNetworkSecurityPerimeter("my-nsp")
    .withAccessRule({
        name: "allow-my-ip",
        direction: "Inbound",
        addressPrefixes: ["203.0.113.0/24"]
    })
    .withAccessRule({
        name: "allow-outbound-fqdn",
        direction: "Outbound",
        fullyQualifiedDomainNames: ["*.blob.core.windows.net"]
    });
```
The following properties are available on `AzureNspAccessRule`:

| Property                    | Direction | Description                                                               |
| --------------------------- | --------- | ------------------------------------------------------------------------- |
| `AddressPrefixes`           | Inbound   | CIDR ranges allowed to reach the perimeter.                               |
| `Subscriptions`             | Inbound   | Subscription resource IDs (format: `/subscriptions/{id}`) allowed access. |
| `FullyQualifiedDomainNames` | Outbound  | FQDNs that resources inside the perimeter can communicate with.           |

Each property also has a corresponding `*References` variant (for example, `AddressPrefixReferences`) that accepts `ReferenceExpression` values resolved at deploy time.
**Note:** `Name` and `Direction` are required on every rule. Rule names must be unique
  within the perimeter. The `NetworkSecurityPerimeterAccessRuleDirection` type
  requires a `using` directive for the `Azure.Provisioning.Network` namespace.

#### Associate resources with a perimeter

Call `WithNetworkSecurityPerimeter` (or `withNetworkSecurityPerimeter`) on a PaaS resource builder to associate it with an NSP:

```csharp title="C# — AppHost.cs"
var nsp = builder.AddNetworkSecurityPerimeter("my-nsp");

var storage = builder.AddAzureStorage("storage")
.WithNetworkSecurityPerimeter(nsp);
var keyVault = builder.AddAzureKeyVault("kv")
.WithNetworkSecurityPerimeter(nsp);

````
```typescript title="TypeScript — apphost.mts"
const nsp = await builder.addNetworkSecurityPerimeter("my-nsp");

const storage = await builder.addAzureStorage("storage")
    .withNetworkSecurityPerimeter(nsp);
const keyVault = await builder.addAzureKeyVault("kv")
    .withNetworkSecurityPerimeter(nsp);
````

Once associated, resources within the perimeter can communicate with each other, while public access is governed by the perimeter's access rules.

The following Azure resources support NSP association:

- [Azure AI Foundry](/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-get-started/)
- [Azure AI Search](/integrations/cloud/azure/azure-ai-search/azure-ai-search-get-started/)
- [Azure Cosmos DB](/integrations/cloud/azure/azure-cosmos-db/azure-cosmos-db-get-started/)
- [Azure Event Hubs](/integrations/cloud/azure/azure-event-hubs/azure-event-hubs-get-started/)
- [Azure Key Vault](/integrations/cloud/azure/azure-key-vault/azure-key-vault-get-started/)
- [Azure Log Analytics](/integrations/cloud/azure/azure-log-analytics/)
- [Azure OpenAI](/integrations/cloud/azure/azure-openai/azure-openai-get-started/)
- [Azure Service Bus](/integrations/cloud/azure/azure-service-bus/azure-service-bus-get-started/)
- [Azure SQL Server](/integrations/cloud/azure/azure-sql-database/azure-sql-database-get-started/)
- [Azure Storage](/integrations/cloud/azure/azure-storage-blobs/azure-storage-blobs-get-started/)

#### Enforced vs. Learning mode

By default, associations use **Enforced** mode—resources within the perimeter can communicate with each other, and any public traffic that doesn't match an access rule is blocked. You can switch to **Learning** mode to log violations without blocking traffic, which is helpful when onboarding resources to identify which access rules are needed before switching to enforced mode:

```csharp title="C# — AppHost.cs"
// Enforced mode (default) — blocks traffic that violates the rules
storage.WithNetworkSecurityPerimeter(nsp);

// Learning mode — logs violations without blocking
keyVault.WithNetworkSecurityPerimeter(
nsp, NetworkSecurityPerimeterAssociationAccessMode.Learning);

````
```typescript title="TypeScript — apphost.mts"
// Enforced mode (default) — blocks traffic that violates the rules
storage.withNetworkSecurityPerimeter(nsp);

// Learning mode — logs violations without blocking
keyVault.withNetworkSecurityPerimeter(nsp, "Learning");
````
**Tip:** Start with **Learning** mode when you first add an NSP to an existing
  application. Review the logged violations to determine which access rules you
  need, then switch to **Enforced** mode once the rules are in place.

## Complete example

The following example demonstrates a complete network topology with a virtual network, subnets, NSG rules, a NAT gateway, private endpoints, and an Azure Container Apps environment:
**Note:** The `AZPROVISION001` warning suppressed below is required because the
  Azure.Provisioning.Network APIs are in preview. When the APIs are marked
  stable, the warning can be removed.

```csharp title="C# — AppHost.cs"
#pragma warning disable AZPROVISION001

using Aspire.Hosting.Azure;
using Azure.Provisioning.Network;

var builder = DistributedApplication.CreateBuilder(args);

// Create a virtual network with two subnets
var vnet = builder.AddAzureVirtualNetwork("vnet");

// Container Apps subnet with NSG rules and NAT gateway
var containerAppsSubnet = vnet.AddSubnet("container-apps", "10.0.0.0/23")
.AllowInbound(
port: "443",
from: AzureServiceTags.AzureLoadBalancer,
protocol: SecurityRuleProtocol.Tcp)
.DenyInbound(from: AzureServiceTags.VirtualNetwork)
.DenyInbound(from: AzureServiceTags.Internet);

var natGateway = builder.AddNatGateway("nat");
containerAppsSubnet.WithNatGateway(natGateway);

// Private endpoints subnet
var peSubnet = vnet.AddSubnet("private-endpoints", "10.0.2.0/27")
.AllowInbound(
port: "443",
from: AzureServiceTags.VirtualNetwork,
protocol: SecurityRuleProtocol.Tcp)
.DenyInbound(from: AzureServiceTags.Internet);

// Configure the Container App Environment to use the VNet.
// WithDelegatedSubnet assigns the subnet to the ACA environment,
// enabling subnet delegation so ACA can inject its infrastructure.
// For more information, see: https://learn.microsoft.com/azure/container-apps/custom-virtual-networks
builder.AddAzureContainerAppEnvironment("env")
.WithDelegatedSubnet(containerAppsSubnet);

// Add storage with private endpoints
var storage = builder.AddAzureStorage("storage");
var blobs = storage.AddBlobs("blobs");
var queues = storage.AddQueues("queues");

peSubnet.AddPrivateEndpoint(blobs);
peSubnet.AddPrivateEndpoint(queues);

builder.AddProject<Projects.Api>("api")
.WithExternalHttpEndpoints()
.WithReference(blobs)
.WithReference(queues);

builder.Build().Run();

````
```typescript title="TypeScript — apphost.mts"
import { createBuilder } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

// Create a virtual network with two subnets
const vnet = await builder.addAzureVirtualNetwork("vnet");

// Container Apps subnet with NSG rules and NAT gateway
const containerAppsSubnet = vnet.addSubnet("container-apps", "10.0.0.0/23")
    .allowInbound({ port: "443", from: "AzureLoadBalancer", protocol: "Tcp" })
    .denyInbound({ from: "VirtualNetwork" })
    .denyInbound({ from: "Internet" });

const natGateway = await builder.addNatGateway("nat");
containerAppsSubnet.withNatGateway(natGateway);

// Private endpoints subnet
const peSubnet = vnet.addSubnet("private-endpoints", "10.0.2.0/27")
    .allowInbound({ port: "443", from: "VirtualNetwork", protocol: "Tcp" })
    .denyInbound({ from: "Internet" });

// Configure the Container App Environment to use the VNet.
await builder.addAzureContainerAppEnvironment("env")
    .withDelegatedSubnet(containerAppsSubnet);

// Add storage with private endpoints
const storage = await builder.addAzureStorage("storage");
const blobs = storage.addBlobs("blobs");
const queues = storage.addQueues("queues");

peSubnet.addPrivateEndpoint(blobs);
peSubnet.addPrivateEndpoint(queues);

await builder.addProject("api", "../Api/Api.csproj")
    .withExternalHttpEndpoints()
    .withReference(blobs)
    .withReference(queues);

await builder.build().run();
````

## See also

- [Azure Virtual Network documentation](https://learn.microsoft.com/azure/virtual-network/)
- [Azure NAT Gateway documentation](https://learn.microsoft.com/azure/nat-gateway/)
- [Azure Private Link documentation](https://learn.microsoft.com/azure/private-link/)
- [Azure Network Security Perimeter documentation](https://learn.microsoft.com/azure/private-link/network-security-perimeter-concepts)
- [Configure Azure Container Apps environments](/integrations/cloud/azure/configure-container-apps/)
- [Customize Azure resources](/integrations/cloud/azure/customize-resources/)