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
  }
}

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
  }
}