Background


Last week at Microsoft Reactor Shanghai, I addressed a speech "Automating Infrastructure as Code (IaC) Deployment with Azure Bicep". One of the demo was how to use Bicep to deploy a complete Azure web app environment, including an App Service and an Azure SQL Database. I know we didn’t have time to walk through the whole script, so I wanted to break it down here in a blog post.

Let’s walk through this Bicep file together, and see what each part does!

The Bicep File


First, the complete Bicep file is:

@description('Web App Name')
param webAppName string = 'demowebapp${uniqueString(resourceGroup().id)}'

@description('SQL Server administrator username')
param sqlAdminUsername string = 'demoadmin'

@secure()
@description('SQL Server administrator password')
param sqlAdminPassword string

@description('SQL Server Name')
param sqlServerName string = 'sqlserver${uniqueString(resourceGroup().id)}'

@description('SQL Database Name')
param sqlDbName string = 'demodb'

@description('App Service region')
param location string = resourceGroup().location

// Create App Service Plan
resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
  name: 'demoplan'
  location: location
  sku: {
    name: 'S1'
    tier: 'Standard'
    capacity: 1
  }
}

// Create Web App
resource webApp 'Microsoft.Web/sites@2022-03-01' = {
  name: webAppName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
    siteConfig: {
      appSettings: [
        // Connection string to be added later
      ]
    }
  }
}

// Create SQL Server
resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = {
  name: sqlServerName
  location: location
  properties: {
    administratorLogin: sqlAdminUsername
    administratorLoginPassword: sqlAdminPassword
    version: '12.0'
  }
}

// Create SQL Database
resource sqlDb 'Microsoft.Sql/servers/databases@2022-02-01-preview' = {
  parent: sqlServer
  location: location
  name: sqlDbName
  properties: {
    collation: 'SQL_Latin1_General_CP1_CI_AS'
    maxSizeBytes: 2147483648
    sampleName: 'AdventureWorksLT'
  }
  sku: {
    name: 'S0'
    tier: 'Standard'
    capacity: 10
  }
}

// Allow Azure Services to access SQL Server (firewall rule)
resource allowAzureServices 'Microsoft.Sql/servers/firewallRules@2022-02-01-preview' = {
  name: 'AllowAzureServices'
  parent: sqlServer
  properties: {
    startIpAddress: '0.0.0.0'
    endIpAddress: '0.0.0.0'
  }
}

// Build database connection string
var connectionString = 'Server=tcp:${sqlServer.name}.database.windows.net,1433;Initial Catalog=${sqlDb.name};Persist Security Info=False;User ID=${sqlAdminUsername};Password=${sqlAdminPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'

// Add the connection string to the Web App's application settings
resource webAppConnectionString 'Microsoft.Web/sites/config@2022-03-01' = {
  parent: webApp
  name: 'connectionstrings'
  properties: {
    DefaultConnection: {
      value: connectionString
      type: 'SQLAzure'
    }
  }
}

output webAppUrl string = webApp.properties.defaultHostName
output sqlServerName string = sqlServer.name
output sqlDbName string = sqlDb.name

To deploy it, run this command in PowerShell:

az deployment group create --resource-group bicep-demo-group --template-file .\webappsql.bicep --parameters sqlAdminPassword=DemoSQLAdmin123!

The result will be:

Step by Step Explanation


Parameters

At the top, we define a few parameters. These are like variables that let you customize your deployment without editing the code every time.

param webAppName string = 'demowebapp${uniqueString(resourceGroup().id)}'
param sqlAdminUsername string = 'demoadmin'
@secure()
param sqlAdminPassword string
param sqlServerName string = 'sqlserver${uniqueString(resourceGroup().id)}'
param sqlDbName string = 'demodb'
param location string = resourceGroup().location

webAppName: The name of your web app. It default to a unique name so you don’t get name conflicts.
sqlAdminUsername and sqlAdminPassword: The admin login for SQL Server. Note the password is marked as @secure(), so it won’t show up in logs or outputs.
sqlServerName: The name for your SQL Server, also unique.
sqlDbName: The name of your SQL Database.
location: Where all your resources will be (like “westus” or “eastasia”).

App Service Plan

Before you can run a web app, you need an App Service Plan, it's the host of your app. Here, we’re creating a plan called demoplan, using the Standard S1 tier.

resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
  name: 'demoplan'
  location: location
  sku: {
    name: 'S1'
    tier: 'Standard'
    capacity: 1
  }
}

Web App

Next, we create the Web App itself, using the plan above.  It’s set to use HTTPS only. For now, the app settings are empty, we’ll add the database connection string later.

resource webApp 'Microsoft.Web/sites@2022-03-01' = {
  name: webAppName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
    siteConfig: {
      appSettings: [
        // Connection string to be added later
      ]
    }
  }
}

SQL Server and SQL Database

Create a SQL Server (not a VM, but a logical server in Azure). You set the admin username and password here.

resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = {
  name: sqlServerName
  location: location
  properties: {
    administratorLogin: sqlAdminUsername
    administratorLoginPassword: sqlAdminPassword
    version: '12.0'
  }
}

Next, create a SQL Database inside the server. It uses the AdventureWorksLT sample data (so you get some tables and data to play with right away). The database is set to the Standard S0 tier.

resource sqlDb 'Microsoft.Sql/servers/databases@2022-02-01-preview' = {
  parent: sqlServer
  location: location
  name: sqlDbName
  properties: {
    collation: 'SQL_Latin1_General_CP1_CI_AS'
    maxSizeBytes: 2147483648
    sampleName: 'AdventureWorksLT'
  }
  sku: {
    name: 'S0'
    tier: 'Standard'
    capacity: 10
  }
}

Now, we need to let Azure services access the database, in this case, we are letting the App Service instance access our database.

resource allowAzureServices 'Microsoft.Sql/servers/firewallRules@2022-02-01-preview' = {
  name: 'AllowAzureServices'
  parent: sqlServer
  properties: {
    startIpAddress: '0.0.0.0'
    endIpAddress: '0.0.0.0'
  }
}

Connection String

First, we build the connection string. For demo purpose, I hard code database.windows.net, please use environment() function in real world apps. Then, we set the connection string into the web app’s settings with name DefaultConnection.

// Build database connection string
var connectionString = 'Server=tcp:${sqlServer.name}.database.windows.net,1433;Initial Catalog=${sqlDb.name};Persist Security Info=False;User ID=${sqlAdminUsername};Password=${sqlAdminPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'

// Add the connection string to the Web App's application settings
resource webAppConnectionString 'Microsoft.Web/sites/config@2022-03-01' = {
  parent: webApp
  name: 'connectionstrings'
  properties: {
    DefaultConnection: {
      value: connectionString
      type: 'SQLAzure'
    }
  }
}

Outputs

After deployment, you’ll get the URL for your web app, plus the names of your SQL Server and Database.

output webAppUrl string = webApp.properties.defaultHostName
output sqlServerName string = sqlServer.name
output sqlDbName string = sqlDb.name

Wrapping Up


So, in a nutshell, this Bicep file lets you deploy a ready-to-go Web App and SQL database in Azure, all connected and secured, with just a single deployment.