Raw New Markdown
Generating updated version of doc...
Rendered New Markdown
Generating updated version of doc...
---
title: Run Bicep deployment script privately over a private endpoint
description: Learn how to run Bicep deployment script privately over a private endpoint.
ms.custom: devx-track-bicep
ms.topic: how-to
ms.date: 08/18/2026
---
# Run Bicep deployment script privately over a private endpoint
With the [`Microsoft.Resources/deploymentScripts`](/azure/templates/microsoft.resources/deploymentscripts?pivots=deployment-language-bicep) resource API version `2023-08-01`, you can run deployment scripts privately within an Azure Container Instance (ACI).
## Configure the environment
In this setup, the ACI created by deployment script runs within a virtual network and obtains a private IP address. It then establishes a connection to a new or pre-existing storage account via a private endpoint. The `containerSettings/subnetIds` property specifies the ACI that must be deployed in a subnet of the virtual network.
:::image type="content" source="./media/deployment-script-vnet-private-endpoint/bicep-deployment-script-vnet-private-endpoint-diagram.jpg" alt-text="Screenshot of high-level architecture showing how the infrastructure is connected to run deployment scripts privately.":::
To run deployment scripts privately, you need the following infrastructure as seen in the architecture diagram:
- Create a virtual network with two subnets:
- A subnet for the private endpoint.
- A subnet for the ACI, this subnet needs a `Microsoft.ContainerInstance/containerGroups` delegation.
- Create a storage account without public network access.
- Create a private endpoint within the virtual network configured with the `file` sub-resource on the storage account.
- Create a private DNS zone `privatelink.file.core.windows.net` and register the private endpoint IP address as an A record. Link the private DNS zone to the created virtual network.
- Create a user-assigned managed identity with `Storage File Data Privileged Contributor` permissions on the storage account and specify it in the `identity` property in the deployment script resource. To assign the identity, see [Identity](/azure/azure-resource-manager/bicep/deployment-script-develop#identity).
- The ACI resource is created automatically by the deployment script resource.
> [!IMPORTANT]
> If you assign a managed identity with permissions that exceed the script's operational requirements, the script author is responsible for ensuring the storage account is protected from unintended access. Wherever possible, follow the principle of least privilege.
The following Bicep file configures the infrastructure required for running a deployment script privately:
```bicep
@maxLength(10) // Required maximum length, because the storage account has a maximum of 26 characters
param namePrefix string
param location string = resourceGroup().location
param userAssignedIdentityName string = '${namePrefix}Identity'
param storageAccountName string = '${namePrefix}stg${uniqueString(resourceGroup().id)}'
param vnetName string = '${namePrefix}Vnet'
param deploymentScriptName string = '${namePrefix}ds'
var roleNameStorageFileDataPrivilegedContributor = '69566ab7-960f-475b-8e7c-b3118f30c6bd'
var vnetAddressPrefix = '192.168.4.0/23'
var subnetEndpointAddressPrefix = '192.168.4.0/24'
var subnetACIAddressPrefix = '192.168.5.0/24'
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2025-01-31-preview' = {
name: userAssignedIdentityName
location: location
}
resource storageAccount 'Microsoft.Storage/storageAccounts@2025-06-01' = {
name: storageAccountName
kind: 'StorageV2'
location: location
sku: {
name: 'Standard_LRS'
}
properties: {
publicNetworkAccess: 'Disabled'
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices'
}
}
}
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2025-01-01' = {
name: storageAccount.name
location: location
properties: {
privateLinkServiceConnections: [
{
name: storageAccount.name
properties: {
privateLinkServiceId: storageAccount.id
groupIds: [
'file'
]
}
}
]
customNetworkInterfaceName: '${storageAccount.name}-nic'
subnet: {
id: virtualNetwork::privateEndpointSubnet.id
}
}
}
resource storageFileDataPrivilegedContributorReference 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: roleNameStorageFileDataPrivilegedContributor
scope: tenant()
}
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(storageFileDataPrivilegedContributorReference.id, managedIdentity.id, storageAccount.id)
scope: storageAccount
properties: {
principalId: managedIdentity.properties.principalId
roleDefinitionId: storageFileDataPrivilegedContributorReference.id
principalType: 'ServicePrincipal'
}
}
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
name: 'privatelink.file.core.windows.net'
location: 'global'
resource virtualNetworkLink 'virtualNetworkLinks' = {
name: uniqueString(virtualNetwork.name)
location: 'global'
properties: {
registrationEnabled: false
virtualNetwork: {
id: virtualNetwork.id
}
}
}
resource resRecord 'A' = {
name: storageAccount.name
properties: {
ttl: 10
aRecords: [
{
ipv4Address: first(first(privateEndpoint.properties.customDnsConfigs)!.ipAddresses)
}
]
}
}
}
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2025-01-01' = {
name: vnetName
location: location
properties:{
addressSpace: {
addressPrefixes: [
vnetAddressPrefix
]
}
}
resource privateEndpointSubnet 'subnets' = {
name: 'PrivateEndpointSubnet'
properties: {
addressPrefixes: [
subnetEndpointAddressPrefix
]
}
}
resource containerInstanceSubnet 'subnets' = {
name: 'ContainerInstanceSubnet'
properties: {
addressPrefix: subnetACIAddressPrefix
delegations: [
{
name: 'containerDelegation'
properties: {
serviceName: 'Microsoft.ContainerInstance/containerGroups'
}
}
]
}
}
}
resource privateDeploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: deploymentScriptName
dependsOn: [
privateEndpoint
privateDnsZone::virtualNetworkLink
]
location: location
kind: 'AzurePowerShell'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${managedIdentity.id}' : {}
}
}
properties: {
storageAccountSettings: {
storageAccountName: storageAccount.name
}
containerSettings: {
subnetIds: [
{
id: virtualNetwork::containerInstanceSubnet.id
}
]
}
azPowerShellVersion: '14.0'
retentionInterval: 'P1D'
scriptContent: 'Write-Host "Hello World!"'
}
}
```
The ACI downloads container images from the Microsoft Container Registry. If you use a firewall, allowlist the URL [mcr.microsoft.com](https://mcr.microsoft.com) to download the image. Failure to download the container image results in the ACI entering a `waiting` state, eventually leading to a timeout error.
## Next steps
In this article, you learned how to run deployment scripts over a private endpoint. To learn more:
> [!div class="nextstepaction"]
> [Use deployment scripts in Bicep](./deployment-script-bicep.md)