Quickstart: Intro to Bicep with Web App and DB

8 minute read • By Jordan Selig • July 23, 2021

Get started with Azure App Service by deploying an app to the cloud using a Bicep file and Azure CLI in Cloud Shell. Because you use a free App Service tier, you incur no costs to complete this quickstart.

Bicep is a domain-specific language (DSL) that uses declarative syntax to deploy Azure resources. It provides concise syntax, reliable type safety, and support for code reuse. You can use Bicep instead of JSON to develop your Azure Resource Manager templates (ARM templates). The JSON syntax to create an ARM template can be verbose and require complicated expressions. Bicep syntax reduces that complexity and improves the development experience. Bicep is a transparent abstraction over ARM template JSON and doesn’t lose any of the JSON template capabilities. During deployment, the Bicep CLI transpiles a Bicep file into ARM template JSON.

Prerequisites

If you don’t have an Azure subscription, create a free account before you begin.

In order to effectively create resources with Bicep, you will need to install Bicep. The Bicep extension for Visual Studio Code provides language support and resource autocompletion. The extension helps you create and validate Bicep files and is recommended for those that will continue to create resources using Bicep upon completing this quickstart.

Review the template

The template used in this quickstart is shown below. It deploys an App Service plan, an App Service app on Linux, and a sample Node.js “Hello World” app from the Azure Samples repo.

param webAppName string = uniqueString(resourceGroup().id) // Generate unique String for web app name
param sku string = 'P1V2' // The SKU of App Service Plan
param linuxFxVersion string = 'node|14-lts' // The runtime stack of web app
param location string = resourceGroup().location // Location for all resources
param repositoryUrl string = 'https://github.com/Azure-Samples/nodejs-docs-hello-world'
param branch string = 'master'

var appServicePlanName = toLower('AppServicePlan-${webAppName}')
var webSiteName = toLower('wapp-${webAppName}')

resource appServicePlan 'Microsoft.Web/serverfarms@2020-06-01' = {
  name: appServicePlanName
  location: location
  properties: {
    reserved: true
  }
  sku: {
    name: sku
  }
  kind: 'linux'
}

resource appService 'Microsoft.Web/sites@2020-06-01' = {
  name: webSiteName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      linuxFxVersion: linuxFxVersion
    }
  }
}

resource srcControls 'Microsoft.Web/sites/sourcecontrols@2021-01-01' = {
  name: '${appService.name}/web'
  properties: {
    repoUrl: repositoryUrl
    branch: branch
    isManualIntegration: true
  }
}

Three Azure resources are defined in the template:

This template contains several parameters that are predefined for your convenience. See the table below for parameter defaults and their descriptions:

Parameters Type Default value Description
webAppName string “webApp-<uniqueString> App name
location string “resourceGroup().location” App region
sku string “P1V2” Instance size
linuxFxVersion string “NODE|14-LTS” A two-part string defining the runtime and version: “language|Version”
repositoryUrl string “https://github.com/Azure-Samples/nodejs-docs-hello-world” External Git repo (optional)
branch string “master” Default branch for code sample

Deploy the template

Copy and paste the template to your preferred editor/IDE and save the file to your local working directory.

Azure CLI is used here to deploy the template. You can also use the Azure portal, Azure PowerShell, or REST API. To learn other deployment methods, see Bicep Deployment Commands.

The following code creates a resource group, an App Service plan, and a web app. A default resource group, App Service plan, and location have been set for you. Replace <app-name> with a globally unique app name (valid characters are a-z, 0-9, and -).

Open up a terminal where the Azure CLI has been installed and run the code below to create a Node.js app on Linux.

az group create --name myResourceGroup --location "southcentralus" &&
az deployment group create --resource-group myResourceGroup --template-file <path-to-template>

To deploy a different language stack, update linuxFxVersion with appropriate values. Samples are shown below. To show current versions, run the following command in the Cloud Shell: az webapp config show --resource-group myResourceGroup --name <app-name> --query linuxFxVersion

Language Example
.NET linuxFxVersion=”DOTNETCORE|3.0”
PHP linuxFxVersion=”PHP|7.4”
Node.js linuxFxVersion=”NODE|10.15”
Java linuxFxVersion=”JAVA|1.8|TOMCAT|9.0”
Python linuxFxVersion=”PYTHON|3.7”
Ruby linuxFxVersion=”RUBY|2.6”

Validate the deployment

Browse to http://<app_name>.azurewebsites.net/ and verify it’s been created.

Clean up resources

When no longer needed, delete the resource group.

Optional additional sample apps

If you would like to continue learning about Bicep and App Service, below are additional samples you can review and deploy following the same process described above. Note that if you did not already delete the previously created resources, updating your existing template and redeploying will update the current app. Optionally, you can start from scratch and create new resources for the next part of this quickstart.

Webapp with CosmosDB

@description('Application Name')
@maxLength(30)
param applicationName string = 'to-do-app${uniqueString(resourceGroup().id)}'

@description('Location for all resources.')
param location string = resourceGroup().location

@description('App Service Plan\'s pricing tier. Details at https://azure.microsoft.com/en-us/pricing/details/app-service/')
param appServicePlanTier string = 'P1V2'

@minValue(1)
@maxValue(3)
@description('App Service Plan\'s instance count')
param appServicePlanInstances int = 1

@description('The URL for the GitHub repository that contains the project to deploy.')
param repositoryUrl string = 'https://github.com/Azure-Samples/cosmos-dotnet-core-todo-app.git'

@description('The branch of the GitHub repository to use.')
param branch string = 'main'

@description('The Cosmos DB database name.')
param databaseName string = 'Tasks'

@description('The Cosmos DB container name.')
param containerName string = 'Items'

var cosmosAccountName = toLower(applicationName)
var websiteName = applicationName
var appServicePlanName = applicationName

resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2021-04-15' = {
  name: cosmosAccountName
  kind: 'GlobalDocumentDB'
  location: location
  properties: {
    consistencyPolicy: {
      defaultConsistencyLevel: 'Session'
    }
    locations: [
      {
        locationName: location
        failoverPriority: 0
        isZoneRedundant: false
      }
    ]
    databaseAccountOfferType: 'Standard'
  }
}

resource appServicePlan 'Microsoft.Web/serverfarms@2021-01-01' = {
  name: appServicePlanName
  location: location
  sku: {
    name: appServicePlanTier
    capacity: appServicePlanInstances
  }
  kind: 'linux'
}

resource appService 'Microsoft.Web/sites@2021-01-01' = {
  name: websiteName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
    siteConfig: {
      http20Enabled: true

      appSettings: [
        {
          name: 'CosmosDb:Account'
          value: cosmosAccount.properties.documentEndpoint
        }
        {
          name: 'CosmosDb:Key'
          value: listKeys(cosmosAccount.id, cosmosAccount.apiVersion).primaryMasterKey
        }
        {
          name: 'CosmosDb:DatabaseName'
          value: databaseName
        }
        {
          name: 'CosmosDb:ContainerName'
          value: containerName
        }
      ]
    }
  }
}

resource srcControls 'Microsoft.Web/sites/sourcecontrols@2021-01-01' = {
  name: '${appService.name}/web'
  properties: {
    repoUrl: repositoryUrl
    branch: branch
    isManualIntegration: true
  }
}

This app uses Microsoft Azure Cosmos DB service to store and access data from an ASP.NET Core MVC application hosted on Azure App Service. More details for this app can be found here. Most of the parameters and resources are the same, but you now additionally have resources for the Cosmos DB account and you set the app settings as part of the “sites” (web app) resource.

Webapp with custom DNS name with a TLS/SSL binding

If you would like to continue with this app by adding a SSL binding and a custom domain, the sample bicep files will be given below. Please note that you will need to purchase a custom domain and input the relevant info into the dnsZone parameter of the template. For more information about custom domains and SSL bindings, please see Secure a custom DNS name with a TLS/SSL binding in Azure App Service.

In order to complete this sample, you will need to modify the above file or create a new Bicep file as well as create an additional Bicep file in the same directory for sni enablement. This is due to an ARM limitation that forbids using resources with this same type-name combination twice in one deployment.

Create another bicep file named sni-enable.bicep in the same directory with the contents below.

param webAppName string
param webAppHostname string
param certificateThumbprint string

resource webAppCustomHostEnable 'Microsoft.Web/sites/hostNameBindings@2020-06-01' = {
  name: '${webAppName}/${webAppHostname}'
  properties: {
    sslState: 'SniEnabled'
    thumbprint: certificateThumbprint
  }
}

The following highlight the changes to the webapp-cosmosdb file for reference. Making these updates to your existing file will update your existing deployment if you did not already delete your app.

  1. Add a parameter for dnsZone where you input your custom domain (i.e. “customdomain.com”)
  2. Add a Microsoft.Network/dnsZones/TXT resource
  3. Add a Microsoft.Network/dnsZones/CNAME resource
  4. Add a Microsoft.Web/sites/hostNameBindings resource
  5. Add a Microsoft.Web/certificates resource
  6. Chain the sni-enable module you created earlier

The file should end up like the below.

@description('Application Name')
@maxLength(30)
param applicationName string = 'to-do-app${uniqueString(resourceGroup().id)}'

@description('Location for all resources.')
param location string = resourceGroup().location

@description('App Service Plan\'s pricing tier. Details at https://azure.microsoft.com/en-us/pricing/details/app-service/')
param appServicePlanTier string = 'P1V2'

@minValue(1)
@maxValue(3)
@description('App Service Plan\'s instance count')
param appServicePlanInstances int = 1

@description('The URL for the GitHub repository that contains the project to deploy.')
param repositoryUrl string = 'https://github.com/Azure-Samples/cosmos-dotnet-core-todo-app.git'

@description('The branch of the GitHub repository to use.')
param branch string = 'main'

@description('Existing Azure DNS zone in target resource group')
param dnsZone string = '<YOUR CUSTOM DOMAIN i.e. "customdomain.com">'

@description('The Cosmos DB database name.')
param databaseName string = 'Tasks'

@description('The Cosmos DB container name.')
param containerName string = 'Items'

var cosmosAccountName = toLower(applicationName)
var websiteName = applicationName
var appServicePlanName = applicationName

resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2021-04-15' = {
  name: cosmosAccountName
  kind: 'GlobalDocumentDB'
  location: location
  properties: {
    consistencyPolicy: {
      defaultConsistencyLevel: 'Session'
    }
    locations: [
      {
        locationName: location
        failoverPriority: 0
        isZoneRedundant: false
      }
    ]
    databaseAccountOfferType: 'Standard'
  }
}

resource appServicePlan 'Microsoft.Web/serverfarms@2021-01-01' = {
  name: appServicePlanName
  location: location
  sku: {
    name: appServicePlanTier
    capacity: appServicePlanInstances
  }
  kind: 'linux'
}

resource appService 'Microsoft.Web/sites@2021-01-01' = {
  name: websiteName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
    siteConfig: {
      http20Enabled: true
      
      appSettings: [
        {
          name: 'CosmosDb:Account'
          value: cosmosAccount.properties.documentEndpoint
        }
        {
          name: 'CosmosDb:Key'
          value: listKeys(cosmosAccount.id, cosmosAccount.apiVersion).primaryMasterKey
        }
        {
          name: 'CosmosDb:DatabaseName'
          value: databaseName
        }
        {
          name: 'CosmosDb:ContainerName'
          value: containerName
        }
      ]
    }
  }
}

resource srcControls 'Microsoft.Web/sites/sourcecontrols@2021-01-01' = {
  name: '${appService.name}/web'
  properties: {
    repoUrl: repositoryUrl
    branch: branch
    isManualIntegration: true
  }
}

resource dnsTxt 'Microsoft.Network/dnsZones/TXT@2018-05-01' = {
  name: '${dnsZone}/asuid.${applicationName}'
  properties: {
    TTL: 3600
    TXTRecords: [
      {
        value: [
          '${appService.properties.customDomainVerificationId}'
        ]
      }
    ]
  }
}

resource dnsCname 'Microsoft.Network/dnsZones/CNAME@2018-05-01' = {
  name: '${dnsZone}/${applicationName}'
  properties: {
    TTL: 3600
    CNAMERecord: {
      cname: '${appService.name}.azurewebsites.net'
    }
  }
}

// Enabling Managed certificate for a webapp requires 3 steps
// 1. Add custom domain to webapp with SSL in disabled state
// 2. Generate certificate for the domain
// 3. enable SSL

// The last step requires deploying again Microsoft.Web/sites/hostNameBindings - and ARM template forbids this in one deplyment, therefore we need to use modules to chain this.

resource webAppCustomHost 'Microsoft.Web/sites/hostNameBindings@2020-06-01' = {
  name: '${appService.name}/${applicationName}.${dnsZone}'
  dependsOn: [
    dnsTxt
    dnsCname
  ]
  properties: {
    hostNameType: 'Verified'
    sslState: 'Disabled'
    customHostNameDnsRecordType: 'CName'
    siteName: appService.name
  }
}

resource webAppCustomHostCertificate 'Microsoft.Web/certificates@2020-06-01' = {
  name: '${applicationName}.${dnsZone}'
  // name: dnsZone
  location: location
  dependsOn: [
    webAppCustomHost
  ]
  properties: any({
    serverFarmId: appServicePlan.id
    canonicalName: '${applicationName}.${dnsZone}'
  })
}

// we need to use a module to enable sni, as ARM forbids using resource with this same type-name combination twice in one deployment.
module webAppCustomHostEnable './sni-enable.bicep' = {
  name: '${deployment().name}-${applicationName}-sni-enable'
  params: {
    webAppName: appService.name
    webAppHostname: '${webAppCustomHostCertificate.name}'
    certificateThumbprint: webAppCustomHostCertificate.properties.thumbprint
  }
}

Clean up resources

As before, when no longer needed, delete the resource group to prevent incurring any additional costs.