Create an App Service for Containers with Bicep
Azure App Service has supported running containers for a while now, both in standard App Service and in Azure Functions. Setting this up through the UI is pretty straightforward, however, creating the same setup using Infrastructure as code can be a bit tricky. There are several different configuration settings that need to be added that are not always obvious. In this article, we will cover them what these are and when to use them and put them all together in one place.
Plan Settings
First things, we need to set up the app plan with the right SKU and settings. For Linux containers, you can use the basic or above SKU (not free or shared), for Windows you need to use the premium plan. In addition, you also need to set the “reserved” property to true.
resource appServicePlan 'Microsoft.Web/serverfarms@2020-12-01' = {
name: appServicePlanName
location: location
kind: 'linux'
properties: {
reserved: true
}
sku: {
name: 'B1',
tier: 'Basic'
}
}
Container Reference
Next, we need to tell the web app what container to use and which registry to get it from. There are actually two ways to do this, via the linuxFxVersion setting or the DOCKER_CUSTOM_IMAGE_NAME app setting. Both of these will work, but linuxFxVersion takes precedence so I would recommend using this.
resource webApp 'Microsoft.Web/sites@2021-01-01' = {
name: webAppName
location: location
tags: {}
properties: {
siteConfig: {
appSettings: []
linuxFxVersion: 'DOCKER|mcr.microsoft.com/appsvc/staticsite:latest'
}
serverFarmId: appServicePlan.id
}
}
Private Registry Authentication
If your image is coming from a public repository then that is all you need to do. However, if you are using a private registry then you need to authenticate. There are two ways to do this.
Provide Login Credentials
The first option works with any kind of private repo, and that is providing a username and password. These are set as app config settings in the app service:
- DOCKER_REGISTRY_SERVER_USERNAME
- DOCKER_REGISTRY_SERVER_URL (full URL, ex:
https://<server-name>.azurecr.io
) - DOCKER_REGISTRY_SERVER_PASSWORD
resource webApp 'Microsoft.Web/sites@2021-01-01' = {
name: webAppName
location: location
tags: {}
properties: {
siteConfig: {
appSettings: [ {
name: 'DOCKER_REGISTRY_SERVER_PASSWORD'
value: dockerRegistryPassword
}
{
name: 'DOCKER_REGISTRY_SERVER_URL'
value: '${registryName}.azurecr.io'
}
{
name: 'DOCKER_REGISTRY_SERVER_USERNAME'
value: dockerRegistryUserName
}]
linuxFxVersion: 'DOCKER|${registryName}.azurecr.io:myimage:latest'
}
serverFarmId: appServicePlan.id
}
}
Managed Identity
Alternatively, if you are pulling your image from Azure Container Registry then you can use a managed identity to connect rather than providing credentials. First we need to create a managed identity (or use the system assigned identity) and grant it permissions to pull containers from the ACR. Then we need to set the acrUseManagedIdentityCreds
property to true. If we are using a user assigned managed identity then we also need to set the acrUserManagedIdentityID
value as the ID of the managed Identity.
resource webApp 'Microsoft.Web/sites@2021-01-01' = {
name: webAppName
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${managedIdentity.id}': {}
}
}
tags: {}
properties: {
siteConfig: {
acrUseManagedIdentityCreds: true
acrUserManagedIdentityID: managedIdentity.id
appSettings: []
linuxFxVersion: 'DOCKER|${registryName}.azurecr.io:myimage:latest'
}
serverFarmId: appServicePlan.id
}
}
Using Private Link
Up to now all the connections to the container registry have assumed it is available over the public internet and not network restricted. However, if your ACR is behind a private endpoint you can have the app service use this. You will need to join the App Service to the vNet first so that it can communicate with the private endpoints. Then you need to set the vnetRouteAllEnabled
property to true and add the WEBSITE_PULL_IMAGE_OVER_VNET
app setting.
resource webApp 'Microsoft.Web/sites@2021-01-01' = {
name: webAppName
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${managedIdentity.id}': {}
}
}
tags: {}
properties: {
virtualNetworkSubnetId: appServiceSubnetId
siteConfig: {
vnetRouteAllEnabled: true
acrUseManagedIdentityCreds: true
acrUserManagedIdentityID: managedIdentity.id
appSettings: [
{
name: 'WEBSITE_PULL_IMAGE_OVER_VNET'
value: 'true'
}
]
linuxFxVersion: 'DOCKER|${registryName}.azurecr.io:myimage:latest'
}
serverFarmId: appServicePlan.id
}
}