Introduction to Bicep: Create Everything You Need to Deploy Your Full-Stack Application on Azure
Best Practices for Deploying and Managing Azure Infrastructure with Code

Hi there 👋🏻, my name is Marko and I'm a software engineer at CodeChem (https://codechem.com/). I'm currently working Full Stack + DevOps. I also like to dabble with Flutter.
Introduction
Hey there! Welcome back to our Infrastructure as Code series on the CodeChem blog! 👋
If you missed our last article on the topic of Managing Azure Services & Resources, definitely give it a read first. But don’t worry, you can dive into this one right away because today we’re talking about something exciting—Microsoft’s new, declarative language: Bicep! 🦾
After a brief introduction, we’ll explore a practical example of how to create everything you need to deploy and host a full-stack application using Bicep!
Let’s get started!
What’s Bicep?
Bicep is a domain-specific language (DSL) created by Microsoft for deploying and managing Azure resources. Throughout the development lifecycle, you can utilize a Bicep file to define the infrastructure you want to deploy to Azure and then repeatedly and incrementally deploy it. This guarantees consistent deployment cycles of your resources.
What makes Bicep stand out is its code reuse assistance, dependable type safety, and clear syntax. Bicep provides top-notch authoring for your Azure infrastructure-as-code solutions and it offers a much better developer experience.
Benefits of Bicep
Let’s talk about what makes Bicep awesome and why I’m a big fan:
Support for all resource types and API versions: If you didn’t know, every Azure resource is controlled through its respected API. When a resource provider introduces new resource types and API versions, you can use them in your Bicep file. No waiting around for updated tools, just jump in and go.
Simpler Syntax, Happier Developer Life: If you’ve ever wrestled with JSON templates, you know the struggle is real. They’re long, clunky, and prone to errors. Bicep, though, feels like a breath of fresh air—simple, clear, and so much easier to work with. You don’t need to be a coding wizard to understand it; the syntax is clean and declarative, so you just describe what you want to deploy. Easy-peasy!
Modularity: Keep Your Infrastructure Organized and Scalable: One of the cool things about Bicep is its modularity. You can easily break your resources into multiple Bicep files and manage them with modules, giving you the flexibility to keep everything neat and organized. I prefer keeping all of my infrastructure in a single
main.bicepfile. However, if you use a lot of resources and get to a point where the file gets too bulky, no problem, just split it up!Seamless integration with Azure services: I mean, of course, right? Bicep was born for Azure, so it’s no surprise that it plays perfectly with all Azure services. The language was created to deploy.
Preview changes: You can use the what-if operation to get a preview of changes before deploying the Bicep file, much like the
git diffcommand.Repeatable results: Bicep files are idempotent. Repeatedly deploy your infrastructure throughout the development lifecycle and have confidence your resources are deployed in a consistent manner.
Orchestration: You don't have to worry about the complexities of ordering operations. Resource Manager orchestrates the deployment of interdependent resources so they're created in the correct order.
How to Create the Resources Needed to Deploy Your Full-Stack Application?
Note: Before we dive into the details, here’s a quick note: The code formatter here doesn’t support Bicep syntax highlighting. For the best experience, I recommend installing the official Microsoft Bicep extension and opening the file in VS Code. It’ll make everything much easier to read and work with!
Now, let’s get into how to set up all the resources you need for deploying and hosting your full-stack application using Bicep.
bicep
// Parameters
param location string = resourceGroup().location
param appServicePlanSku string = 'P1v2'
param sqlServerAdminLogin string = 'adminuser'
param sqlServerAdminPassword string = 'P@ssword123!'
param databaseName string = 'appdb'
param frontendAppName string = 'frontendstaticapp${uniqueString(resourceGroup().id)}'
param backendAppName string = 'backendapp${uniqueString(resourceGroup().id)}'
param sqlServerName string = 'sqlserver${uniqueString(resourceGroup().id)}'
param keyVaultName string = 'keyvault${uniqueString(resourceGroup().id)}'
param staticWebAppSkuName string = 'Free'
param staticWebAppSkuTier string = 'Free'
// Resource: Static Web App for our Frontend
resource staticWebApp 'Microsoft.Web/staticSites@2022-03-01' = {
name: frontendAppName
location: location
sku: {
name: staticWebAppSku
tier: staticWebAppSkuTier
}
properties: {
repositoryUrl: 'https://github.com/your-org/your-repo' // GitHub repo containing frontend code
branch: 'main'
buildProperties: {
appLocation: 'app' // Directory containing the frontend app
apiLocation: '' // Optional: leave empty if no API is required here
outputLocation: 'build' // Output directory for the frontend app
}
}
}
// Resource: App Service Plan for our Backend
resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
name: 'appServicePlan${uniqueString(resourceGroup().id)}'
sku: {
name: appServicePlanSku
}
kind: 'linux'
}
// Resource: Backend App Service
resource backendApp 'Microsoft.Web/sites@2022-03-01' = {
name: backendAppName
location: location
serverFarmId: appServicePlan.id
httpsOnly: true
dependsOn: [appServicePlan]
siteConfig: {
appSettings: [
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: applicationInsights.properties.InstrumentationKey
}
{
name: 'SQL_CONNECTION_STRING'
value: sqlDbConnectionString
}
{
name: 'KeyVaultUri'
value: keyVault.properties.vaultUri
}
]
}
}
// Resource: SQL Database
resource sqlDatabase 'Microsoft.Sql/servers/databases@2022-02-01-preview' = {
name: '${sqlServerName}/${databaseName}'
location: location
sku: {
name: 'S1'
tier: 'Standard'
}
}
// Resource: SQL Server
resource sqlServer 'Microsoft.Sql/servers@2022-02-01-preview' = {
name: sqlServerName
location: location
administratorLogin: sqlServerAdminLogin
administratorLoginPassword: sqlServerAdminPassword
version: '12.0'
}
// Resource: Key Vault
resource keyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' = {
name: keyVaultName
location: location
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: subscription().tenantId
accessPolicies: [
{
tenantId: subscription().tenantId
objectId: 'your-principal-object-id' // Replace this with your Object ID for access
permissions: {
secrets: [
'get'
'list'
'set'
]
}
}
]
}
}
// Resource: Application Insights
resource applicationInsights 'Microsoft.Insights/components@2021-04-01' = {
name: 'appInsights${uniqueString(resourceGroup().id)}'
location: location
kind: 'web'
properties: {
Application_Type: 'web'
}
}
// Output connection details
output sqlDbConnectionString string = 'Server=tcp:${sqlServerName}.database.windows.net,1433;Database=${databaseName};User ID=${sqlServerAdminLogin};Password=${sqlServerAdminPassword};'
output frontendAppUrl string = 'https://${staticWebApp.defaultHostname}'
output backendAppUrl string = backendApp.defaultHostName
output keyVaultUri string = keyVault.properties.vaultUri
Quick Summary
The example bicep file will provision the following resources:
Static Web App: Hosting the frontend app;
Backend App Service: Hosting the backend services;
SQL Server & Database: Hosting the SQL Server and the app's database;
Key Vault: For securely storing secrets;
Application Insights: For monitoring the frontend and backend services.
This is the basic infrastructure for a full-stack application with essential resources. You can modify and extend this according to your specific requirements. The Static Web App, Backend App Service, and the SQL Server & Database are all you need. The Key Vault and Application Insights are optional but nice to have.
At the end of the bicep file, we have some output values. You can return output variables valuable to you for subsequent scripts in the pipeline or the application itself, e.g. the sqlDbConnectionString or the backend API URL.
Keynotes to Keep in Mind
The order in which the resources are declared in the bicep file doesn’t matter. For example, notice how we declare the SQL Server after the SQL Database. Of course, we can’t create the SQL Database before provisioning the SQL Server. The Azure task that executes your compiled bicep code is smart enough to know the order in which it has to provision the resources. If you want to ensure your order is followed, there is an optional parameter called dependsOn, which takes an array of resources. You can see this in the backendApp resource, where we say, “this backendApp depends on the appServicePlan resource.”
For the sake of simplicity, you might’ve noticed some of the declared parameters really shouldn’t be hard coded. Or, at least, not be a part of the bicep file. That’s true, most if not all parameters should be input parameters from the pipeline. For example, they should be declared as Azure Pipeline variables and passed to the bicep file.
Never expose sensitive information in your bicep file. Your bicep file is version-controlled and should be treated like any other file you keep in your remote repository.
Bicep also offers the @secure() annotation. When you set a parameter to a secure string or secure object, the parameter's value isn't saved to the deployment history and isn't logged. However, if you set that secure value to a property that isn't expecting a secure value, the value isn't protected.
Deploying Your Resources
After you have defined your bicep file and declared your necessary resources, it’s time to deploy! You’re going to need the AzureResourceManagerTemplateDeployment@3 task in your Azure Pipeline.
Example:
- task: AzureResourceManagerTemplateDeployment@3
inputs:
deploymentScope: 'Resource Group'
azureSubscription: '${{ parameters.azureServiceConnection }}'
action: 'Create Or Update Resource Group'
resourceGroupName: 'exampleRG'
location: '<your-resource-group-location>'
templateLocation: 'Linked artifact'
csmFile: './main.bicep'
deploymentMode: 'Incremental'
deploymentName: 'DeployPipelineTemplate'
deploymentOutputs: deploymentOutputs
This task will deploy the resources declared in the main.bicep file in a Resource Group. The deployment mode is ‘incremental’ to update existing resources and provision new first-time ones. Also, all the output variables will be available in the deploymentOutputs object.
Final Thoughts
I hope the example bicep file and example deployment task were useful to you! This is just the tip of the iceberg if you want to use Bicep in your CI/CD pipelines! In this article we explored deploying with Azure Pipelines, but you can also use Bicep in GitHub Actions! I’ve provided some additional reading material. And remember—when working with Bicep, the official Microsoft documentation is your best go-to guide.
Happy deploying!






