Develop a deployment script in Bicep
This article provides examples to show you how to develop a deployment script in Bicep.
Deployment script resources might have a deployment duration. For efficient development and testing of these scripts, consider establishing a dedicated development environment, such as an Azure container instance(ACI) or a Docker instance. For more information, see Create a development environment.
Syntax
The following Bicep file is an example of a deployment script resource. For more information, see the latest deployment script schema.
resource <symbolic-name> 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: '<resource-name>'
location: resourceGroup().location
tags: {}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'<user-assigned-identity-id>': {}
}
}
kind: 'AzureCLI'
properties: {
storageAccountSettings: {
storageAccountName: '<storage-account-name>'
storageAccountKey: '<storage-account-key>'
}
containerSettings: {
containerGroupName: '<container-group-name>'
subnetIds: [
{
id: '<subnet-id>'
}
]
}
environmentVariables: []
azCliVersion: '2.52.0'
arguments: '<script-arguments>'
scriptContent: '''<azure-cli-or-azure-powershell-script>''' // or primaryScriptUri: 'https://raw.githubusercontent.com/Azure/azure-docs-bicep-samples/main/samples/deployment-script/inlineScript.ps1'
supportingScriptUris: []
timeout: 'P1D'
cleanupPreference: 'OnSuccess'
retentionInterval: 'P1D'
forceUpdateTag: '1'
}
}
In your deployment script, specify these property values:
tags
: Specify deployment script tags. If the deployment script service creates the two supporting resources (a storage account and a container instance), the tags are passed to both resources. You can use the tags to identify the resources. Another way to identify these supporting resources is through their suffixes, which contain azscripts. For more information, see Monitor and troubleshoot deployment scripts.identity
: For deployment script API version2020-10-01
or later, a user-assigned managed identity is optional unless you need to perform any Azure-specific actions in the script or you're running the deployment script in a private network. API version2019-10-01-preview
requires a managed identity because the deployment script service uses it to run the scripts.When you specify the
identity
property, the script service callsConnect-AzAccount -Identity
before invoking the user script. Currently, only a user-assigned managed identity is supported. To log in with a different identity in the deployment script, you can call Connect-AzAccount. For more information, see Configure the minimum permissions.kind
: Specify the type of script, eitherAzurePowerShell
orAzureCLI
. In addition tokind
, you need to specify theazPowerShellVersion
orazCliVersion
property.storageAccountSettings
: Specify the settings to use an existing storage account. IfstorageAccountName
isn't specified, a storage account is automatically created. For more information, see Use an existing storage account.containerSettings
: Customize the name of the Azure container instance. For information about configuring the container's group name, see Configure a container instance later in this article. For information about configuringsubnetIds
to run the deployment script in a private network, see Access a private virtual network.environmentVariables
: Specify the environment variables to pass over to the script.azPowerShellVersion
/azCliVersion
: Specify the module version to use.See a list of supported Azure CLI versions.
Important
The deployment script uses the available CLI images from Microsoft Artifact Registry. It typically takes about one month to certify a CLI image for a deployment script. Don't use CLI versions that were released within the past 30 days. To find the release dates for the images, see Azure CLI release notes. If you use an unsupported version, the error message lists the supported versions.
arguments
: Specify the parameter values. The values are separated by spaces.The deployment script splits the arguments into an array of strings by invoking the CommandLineToArgvW system call. This step is necessary because the arguments are passed as a command property to Azure Container Instances, and the command property is an array of strings.
If the arguments contain escaped characters, double escape the characters. For example, in the previous sample Bicep syntax, the argument is
-name \"John Dole\"
. The escaped string is-name \\"John Dole\\"
.To pass a Bicep parameter of type
object
as an argument, convert the object to a string by using the string() function, and then use the replace() function to replace any quotation marks ("
) with double-escaped quotation marks (\\"
). For example:replace(string(parameters('tables')), '"', '\\"')
For more information, see the sample Bicep file.
scriptContent
: Specify the script content. It can be an inline script or an external script file that you imported by using the loadTextContent function. For more information, see Inline vs. external file later in this article. To run an external script, useprimaryScriptUri
instead.primaryScriptUri
: Specify a publicly accessible URL to the primary deployment script with supported file extensions. For more information, see Use external scripts later in this article.supportingScriptUris
: Specify an array of publicly accessible URLs to supporting files that are called in eitherscriptContent
orprimaryScriptUri
. For more information, see Inline vs. external file later in this article.timeout
: Specify the maximum allowed time for script execution, in ISO 8601 format. The default value isP1D
.forceUpdateTag
: Changing this value between Bicep file deployments forces the deployment script to run again. If you use thenewGuid()
orutcNow()
function, you can use it only in the default value for a parameter. To learn more, see Run a script more than once later in this article.cleanupPreference
. Specify the preference for cleaning up the two supporting deployment resources (the storage account and the container instance) when the script execution gets in a terminal state. The default setting isAlways
, which calls for the deletion of supporting resources regardless of the terminal state (Succeeded
,Failed
, orCanceled
). To learn more, see Clean up deployment script resources later in this article.retentionInterval
: Specify the interval for which the service retains the deployment script resource after the deployment script execution reaches a terminal state. The deployment script resource is deleted when this duration expires. Duration is based on the ISO 8601 pattern. The retention interval is between 1 hour (PT1H
) and 26 hours (PT26H
). You use this property whencleanupPreference
is set toOnExpiration
. To learn more, see Clean up deployment script resources later in this article.
More samples
- Sample 1: Create a key vault and use a deployment script to assign a certificate to the key vault.
- Sample 2: Create a resource group at the subscription level, create a key vault in the resource group, and then use a deployment script to assign a certificate to the key vault.
- Sample 3: Create a user-assigned managed identity, assign the contributor role to the identity at the resource group level, create a key vault, and then use a deployment script to assign a certificate to the key vault.
- Sample 4: Manually create a user-assigned managed identity and assign it permission to use the Microsoft Graph API to create Microsoft Entra applications. In the Bicep file, use a deployment script to create a Microsoft Entra application and service principal, and to output the object IDs and client ID.
Inline vs. external file
A deployment script can reside within a Bicep file, or you can store it externally as a separate file.
Use an inline script
The following Bicep file shows how to use an inline script.
param name string = 'John Dole'
param location string = resourceGroup().location
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'inlineCLI'
location: location
kind: 'AzureCLI'
properties: {
azCliVersion: '2.52.0'
arguments: name
scriptContent: 'set -e; output="Hello $1"; echo $output'
retentionInterval: 'P1D'
}
}
Include set -e
in your script to enable immediate exit if a command returns a nonzero status. This practice streamlines error debugging processes.
Load a script file
Use the loadTextContent function to retrieve a script file as a string. This function allows you to maintain the script in an external file and access it as a deployment script. The path specified for the script file is relative to the Bicep file.
You can extract the inline script from the preceding Bicep file into a hello.sh file, and then place the file into a subfolder called scripts.
output="Hello $1"
echo $output
Then, you can revise the preceding Bicep file like the following example:
param name string = 'John Dole'
param location string = resourceGroup().location
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'loadTextContentCLI'
location: location
kind: 'AzureCLI'
properties: {
azCliVersion: '2.52.0'
arguments: name
scriptContent: loadTextContent('./scripts/hello.sh')
retentionInterval: 'P1D'
}
}
Use external scripts
You can use external script files instead of inline scripts. Only primary PowerShell scripts with the .ps1 extension are supported. For CLI scripts, primary scripts can carry any valid Bash script extensions or have no extension at all. To employ external script files, swap out scriptContent
with primaryScriptUri
.
param name string = 'John Dole'
param location string = resourceGroup().location
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'externalScriptCLI'
location: location
kind: 'AzureCLI'
properties: {
azCliVersion: '2.52.0'
primaryScriptUri: 'https://raw.githubusercontent.com/Azure/azure-docs-bicep-samples/main/samples/deployment-script/hello.sh'
arguments: '-name ${name}'
retentionInterval: 'P1D'
}
}
The external script files must be accessible. To help secure your script files that are stored in Azure storage accounts, generate a shared access signature (SAS) token and include it in the URI for the template. Set the expiration to allow enough time to complete the deployment. For more information, see Deploy a private ARM template with a SAS token.
You're responsible for ensuring the integrity of the script that the deployment script references (either primaryScriptUri
or supportingScriptUris
). Reference only scripts that you trust.
Use supporting scripts
You can separate complicated logics into one or more supporting script files. Use the supportingScriptUris
property to provide an array of URIs to the supporting script files if necessary.
param name string = 'John Dole'
param location string = resourceGroup().location
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'supportingScriptCLI'
location: location
kind: 'AzureCLI'
properties: {
azCliVersion: '2.52.0'
arguments: name
scriptContent: 'output="Hello $1"; echo $output; ./hello.sh "$1"'
supportingScriptUris: [
'https://raw.githubusercontent.com/Azure/azure-docs-bicep-samples/master/samples/deployment-script/hello.sh'
]
retentionInterval: 'P1D'
}
}
You can call supporting script files from both inline scripts and primary script files. Supporting script files have no restrictions on the file extension.
The supporting files are copied to azscripts/azscriptinput at runtime. Use a relative path to reference the supporting files from inline scripts and primary script files.
Access Azure resources
To access Azure resources, you must configure the identity
element. The following Bicep file demonstrates how to retrieve a list of Azure key vaults. Granting the user-assignment management identity permission to access the key vault is also necessary.
param identity string
param location string = resourceGroup().location
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'listKvCLI'
location: location
kind: 'AzureCLI'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${identity}': {}
}
}
properties: {
azCliVersion: '2.52.0'
scriptContent: 'result=$(az keyvault list); echo $result | jq -c \'{Result: map({id: .id})}\' > $AZ_SCRIPTS_OUTPUT_PATH'
retentionInterval: 'P1D'
}
}
output result object = deploymentScript.properties.outputs
Note
Retry logic for Azure sign-in is now built in to the wrapper script. If you grant permissions in the same Bicep file as your deployment scripts, the deployment script service retries sign-in for 10 minutes (with 10-second intervals) until the managed identity's role assignment is replicated.
Work with outputs
The approach to handling outputs varies based on the type of script you're using—the Azure CLI or Azure PowerShell.
The Azure CLI deployment script uses an environment variable named AZ_SCRIPTS_OUTPUT_PATH
to indicate the location of the file for script outputs. When you're running a deployment script within a Bicep file, the Bash shell automatically configures this environment variable for you. Its predefined value is set as /mnt/azscripts/azscriptoutput/scriptoutputs.json
.
The outputs must conform to a valid JSON string object structure. The file's contents should be formatted as a key/value pair. For instance, save an array of strings as { "MyResult": [ "foo", "bar"] }
. Storing only the array results, such as [ "foo", "bar" ]
, is invalid.
param name string = 'John Dole'
param location string = resourceGroup().location
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'outputCLI'
location: location
kind: 'AzureCLI'
properties: {
azCliVersion: '2.52.0'
arguments: name
scriptContent: 'jq -n -c --arg st "Hello ${name}" \'{"text": $st}\' > $AZ_SCRIPTS_OUTPUT_PATH'
retentionInterval: 'P1D'
}
}
output text string = deploymentScript.properties.outputs.text
The preceding sample uses jq for constructing outputs. The jq tool comes with the container images. For more information, see Configure a development environment.
Use environment variables
Pass secured strings to a deployment script
You can set environment variables (EnvironmentVariable
) in your container instances to provide dynamic configuration of the application or script that the container runs. A deployment script handles nonsecured and secured environment variables in the same way as Azure Container Instances. For more information, see Set environment variables in container instances.
The maximum allowed size for environment variables is 64 KB.
param location string = resourceGroup().location
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'passEnvVariablesCLI'
location: location
kind: 'AzureCLI'
properties: {
azCliVersion: '2.52.0'
environmentVariables: [
{
name: 'UserName'
value: 'jdole'
}
{
name: 'Password'
secureValue: 'jDolePassword'
}
]
scriptContent: 'echo "Username is :$Username"; echo "Password is: $Password"'
retentionInterval: 'P1D'
}
}
System-defined environment variables
The following table lists the system-defined environment variables:
Environment variable | Default value (CLI) | Default value (PowerShell) | System reserved |
---|---|---|---|
AZ_SCRIPTS_AZURE_ENVIRONMENT |
AzureCloud |
AzureCloud |
No |
AZ_SCRIPTS_CLEANUP_PREFERENCE |
Always |
Always |
No |
AZ_SCRIPTS_OUTPUT_PATH |
/mnt/azscripts/azscriptoutput/scriptoutputs.json |
Not applicable | Yes |
AZ_SCRIPTS_PATH_INPUT_DIRECTORY |
/mnt/azscripts/azscriptinput|/mnt/azscripts/azscriptinput |
Not applicable | Yes |
AZ_SCRIPTS_PATH_OUTPUT_DIRECTORY |
/mnt/azscripts/azscriptoutput|/mnt/azscripts/azscriptoutput |
Not applicable | Yes |
AZ_SCRIPTS_PATH_USER_SCRIPT_FILE_NAME |
userscript.sh |
userscript.ps1 |
Yes |
AZ_SCRIPTS_PATH_PRIMARY_SCRIPT_URI_FILE_NAME |
primaryscripturi.config |
primaryscripturi.config |
Yes |
AZ_SCRIPTS_PATH_SUPPORTING_SCRIPT_URI_FILE_NAME |
supportingscripturi.config |
supportingscripturi.config |
Yes |
AZ_SCRIPTS_PATH_SCRIPT_OUTPUT_FILE_NAME |
scriptoutputs.json |
scriptoutputs.json |
Yes |
AZ_SCRIPTS_PATH_EXECUTION_RESULTS_FILE_NAME |
executionresult.json |
executionresult.json |
Yes |
AZ_SCRIPTS_USER_ASSIGNED_IDENTITY |
Not applicable | Not applicable | No |
For a sample of using AZ_SCRIPTS_OUTPUT_PATH
, see Work with outputs earlier in this article.
To access the environment variables, use the following code.
param location string = resourceGroup().location
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'listEnvVariablesCLI'
location: location
kind: 'AzureCLI'
properties: {
azCliVersion: '2.52.0'
scriptContent: 'echo "AZ_SCRIPTS_AZURE_ENVIRONMENT is : $AZ_SCRIPTS_AZURE_ENVIRONMENT",echo "AZ_SCRIPTS_CLEANUP_PREFERENCE is : $AZ_SCRIPTS_CLEANUP_PREFERENCE",echo "AZ_SCRIPTS_OUTPUT_PATH is : $AZ_SCRIPTS_OUTPUT_PATH",echo "AZ_SCRIPTS_PATH_INPUT_DIRECTORY is : $AZ_SCRIPTS_PATH_INPUT_DIRECTORY",echo "AZ_SCRIPTS_PATH_OUTPUT_DIRECTORY is : $AZ_SCRIPTS_PATH_OUTPUT_DIRECTORY",echo "AZ_SCRIPTS_PATH_USER_SCRIPT_FILE_NAME is : $AZ_SCRIPTS_PATH_USER_SCRIPT_FILE_NAME",echo "AZ_SCRIPTS_PATH_PRIMARY_SCRIPT_URI_FILE_NAME is : $AZ_SCRIPTS_PATH_PRIMARY_SCRIPT_URI_FILE_NAME",echo "AZ_SCRIPTS_PATH_SUPPORTING_SCRIPT_URI_FILE_NAME is : $AZ_SCRIPTS_PATH_SUPPORTING_SCRIPT_URI_FILE_NAME",echo "AZ_SCRIPTS_PATH_SCRIPT_OUTPUT_FILE_NAME is : $AZ_SCRIPTS_PATH_SCRIPT_OUTPUT_FILE_NAME",echo "AZ_SCRIPTS_PATH_EXECUTION_RESULTS_FILE_NAME is : $AZ_SCRIPTS_PATH_EXECUTION_RESULTS_FILE_NAME",echo "AZ_SCRIPTS_USER_ASSIGNED_IDENTITY is : $AZ_SCRIPTS_USER_ASSIGNED_IDENTITY"'
retentionInterval: 'P1D'
}
}
Use an existing storage account
For the script to run and allow for troubleshooting, you need a storage account and a container instance. You can either designate an existing storage account or let the script service create both the storage account and the container instance automatically.
Here are the requirements for using an existing storage account:
The following table lists the supported account types. The column for tiers refers to the value of the
-SkuName
or--sku
parameter. The column for supported types refers to the-Kind
or--kind
parameter.Tier Supported type Premium_LRS
FileStorage
Premium_ZRS
FileStorage
Standard_GRS
Storage
,StorageV2
Standard_GZRS
StorageV2
Standard_LRS
Storage
,StorageV2
Standard_RAGRS
Storage
,StorageV2
Standard_RAGZRS
StorageV2
Standard_ZRS
StorageV2
These combinations support file shares. For more information, see Create an Azure file share and Types of storage accounts.
Firewall rules for storage accounts aren't supported yet. For more information, see Configure Azure Storage firewalls and virtual networks.
The deployment principal must have permissions to manage the storage account, which includes reading, creating, and deleting file shares. For more information, see Configure the minimum permissions.
The
allowSharedKeyAccess
property of the storage account must be set totrue
. The only way to mount a storage account in Azure Container Instance(ACI) is via an access key.
To specify an existing storage account, add the following Bicep code to the property element of Microsoft.Resources/deploymentScripts
:
param storageAccountName string = 'myStorageAccount'
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
...
properties: {
...
storageAccountSettings: {
storageAccountName: storageAccountName
storageAccountKey: listKeys(resourceId('Microsoft.Storage/storageAccounts', storageAccountName), '2023-01-01').keys[0].value
}
}
}
For a complete Microsoft.Resources/deploymentScripts
definition sample, see Syntax earlier in this article.
When you use an existing storage account, the script service creates a file share that has a unique name. To learn how the script service cleans up the file share, see Clean up deployment script resources later in this article.
Configure a container instance
A deployment script requires a new Azure container instance. You can't specify an existing container instance. However, you can customize the container's group name by using containerGroupName
. If you don't specify a group name, it's automatically generated. Additional configurations are required for creating this container instance. For more information, see Configure the minimum permissions.
You can also specify subnetId
values for running the deployment script in a private network. For more information, see Access a private virtual network.
param containerGroupName string = 'mycustomaci'
param subnetId string = '/subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet'
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
...
properties: {
...
containerSettings: {
containerGroupName: containerGroupName
subnetIds: [
{
id: subnetId
}
]
}
}
}
Run a script more than once
Deployment script execution is an idempotent operation. If there are no changes to any of the deploymentScripts
resource properties, including the inline script, the script doesn't run when you redeploy the Bicep file.
The deployment script service compares the resource names in the Bicep file with the existing resources in the same resource group. There are two options if you want to run the same deployment script multiple times:
Change the name of your
deploymentScripts
resource. For example, use the utcNow function as the resource name or as a part of the resource name. You can use theutcNow
function only in the default value for a parameter.Changing the resource name creates a new
deploymentScripts
resource. It's good for keeping a history of script execution.Specify a different value in the
forceUpdateTag
property. For example, useutcNow
as the value.
Write deployment scripts to ensure idempotence, so accidental reruns won't result in system alterations. For example, when you're creating an Azure resource through the deployment script, validate its absence before creation to ensure that the script either succeeds or avoids redundant resource creation.
Use Microsoft Graph within a deployment script
A deployment script can use Microsoft Graph to create and work with objects in Microsoft Entra ID.
Commands
When you use Azure CLI deployment scripts, you can use commands within the az ad
command group to work with applications, service principals, groups, and users. You can also directly invoke Microsoft Graph APIs by using the az rest
command.
When you use Azure PowerShell deployment scripts, you can use the Invoke-RestMethod
cmdlet to directly invoke the Microsoft Graph APIs.
Permissions
The identity that your deployment script uses needs to be authorized to work with the Microsoft Graph API, with the appropriate permissions for the operations that it performs. You must authorize the identity outside your Bicep file, such as by precreating a user-assigned managed identity and assigning it an app role for Microsoft Graph. For more information, see this quickstart example.
Clean up deployment script resources
The two automatically created supporting resources can never outlive the deploymentScript
resource, unless failures delete them. The cleanupPreference
property controls the life cycle of the supporting resources. The retentionInterval
property controls the life cycle of the deploymentScript
resource. Here's how to use these properties:
cleanupPreference
: Specify the cleanup preference of the two supporting resources when the script execution gets in a terminal state. The supported values are:Always
: Delete the two supporting resources after script execution gets in a terminal state. If you use an existing storage account, the script service deletes the file share that the service created. Because thedeploymentScripts
resource might still be present after the supporting resources are cleaned up, the script service persists the script execution results (for example,stdout
), outputs, and return value before the resources are deleted.OnSuccess
: Delete the two supporting resources only when the script execution is successful. If you use an existing storage account, the script service removes the file share only when the script execution is successful.If the script execution isn't successful, the script service waits until the
retentionInterval
value expires before it cleans up the supporting resources and then the deployment script resource.OnExpiration
: Delete the two supporting resources only when theretentionInterval
setting is expired. If you use an existing storage account, the script service removes the file share but retains the storage account.
The container instance and storage account are deleted according to the
cleanupPreference
value. However, if the script fails andcleanupPreference
isn't set toAlways
, the deployment process automatically keeps the container running for one hour or until the container is cleaned up. You can use the time to troubleshoot the script.If you want to keep the container running after successful deployments, add a sleep step to your script. For example, add Start-Sleep to the end of your script. If you don't add the sleep step, the container is set to a terminal state and can't be accessed even if you haven't deleted it yet.
retentionInterval
: Specify the time interval that adeploymentScript
resource will be retained before it's expired and deleted.
Note
We don't recommend that you use the storage account and the container instance that the script service generates for other purposes. The two resources might be removed, depending on the script's life cycle.
Next steps
In this article, you learned how to create deployment script resources. To learn more: