In part one of the Azure Bicep tutorial, we covered the definitions and how to prepare our computer to start drafting and writing our first Bicep file.
If you missed that intro post, feel free and read it from here Master Azure Infrastructure Automation with Bicep – Introduction (Part 1)
Table of Contents
Understanding Bicep File Structure
A Bicep file is where you define your Azure infrastructure. Understanding its core components, which include parameters, variables, and outputs, is key to writing effective and reusable Bicep code. These elements allow you to create flexible templates that can be adapted for different environments and deployment needs.
The Bicep file is a text file ending with .bicep extension, you can view then using Notepad or VSCode (Recommended)
The Structure of a Azure Bicep File
A typical Azure Bicep file consists of several key sections that define resources and their configurations. While not all sections are mandatory for every file, understanding them provides a clear picture of how a Bicep file structures your infrastructure definition.
- Parameters: Inputs to your template.
- Variables: Computed values defined in the template (often from params) that are fixed for the duration of a deployment.
- Resource: The Azure resources you want to deploy (e.g., virtual machines, storage accounts, networks).
- Outputs: Values returned after a deployment (e.g., resource IDs, public IP addresses).
Here’s a sample example of a Bicep file defining a storage account:
param location string = 'westus'
param storageAccountName string
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = {
name: storageAccountName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}
output storageAccountId string = storageAccount.id
Dont get overwhelmed by the code, just read it see how the code look like. More explaination is on the way.
Previously, we mentioned that the Bicep may include Resources, Parameters, Variables, and output… Let’s now touch on the surface of each one
Understanding the Azure Basics of Bicep Parameter
Parameters allow you to pass values into your Azure Bicep template when you deploy it. This makes your templates reusable and flexible. Instead of hardcoding values like region names or instance sizes, you define parameters, and then provide specific values during deployment. This is essential for deploying identical infrastructure across various environments (e.g., development, staging, production). The Bicep file content remains the same across all the environments; you only change the input parameters.
In the example above, location and storageAccountName are parameters. The location parameter has a default value of westus, while storageAccountName it requires a value to be provided during deployment.
To define a parameter in a Azure Bicep file, you use the param keyword, which declares a value that will be provided at deployment time (or fall back to a default if you set one). After param, you write the parameter name (like location or environment) and then the type (such as string, int, bool, array, or object). For example: param location string defines a parameter named location of type string that you can pass in through a parameter file or the deployment command
param <myParameterName> <Type> = '<myDefaultValue>'
Bicep can get parameter values from a few places at deployment time. You can hardcode a value directly in the Bicep file, as in the location parameter, or you can supply values externally using a parameter file (like .bicepparam or a JSON parameters file). If you don’t provide a value and there’s no default, the deployment command/portal must supply it; otherwise, the deployment fails.
In the upcoming tutorial we will understand more details about the parameters and how to write it.
Understanding the Basics of Azure Bicep Variables
Variables are used within your Azure Bicep template to store values that are used multiple times or to simplify complex expressions. Unlike parameters, variables are defined within the template itself and are not typically passed in during deployment. They help make your code more readable and easier to manage.
For instance, if you frequently use a Location property, you could define it as a variable and refer to it in multiple resources and places in the Bicep file
var location= 'westus'
resource myVM 'Microsoft.Compute/virtualMachines@2021-07-01' = {
// ... other properties
// use location here
}
resources andOtherResource '.........'={
// use location here
}
In Azure Bicep, variables are defined using the var keyword and are used to store. After var, you write the variable name, then assign it a value using =the type is inferred automatically based on what you assign. For example: var storageName = 'mystorageaccount' creates a reusable value you can reference throughout the file without passing anything during deployment.
var <myVariableName> = <MyValue>
Another more advance example which will set the myStorageAccountLocation variable value to match the destination resource group location
var myStorageAccountLocation= resourceGroup().location
As you can see its not only static value that can be assigned, we can build the value dynamically so it fit your deployment strategy and policy
What are Outputs in Bicep?
Outputs are values that are returned from your Bicep deployment. These are an important pieces of information about the deployed resources, such as their resource IDs, fully qualified domain names (FQDNs), or connection strings. Outputs are useful for chaining deployments together or for providing users with necessary information after a deployment is complete.
In our earlier storage account example:
output adminUsername string = myVm.properties.osProfile.adminUsername defines an output that returns the unique resource ID of the created storage account.

In Bicep, you define an output using the output keyword to return a value after the deployment finishes. The syntax is: output output name type = the expression you want to return. For example: output adminUsername string = myVm.properties.osProfile.adminUsername returns the VM’s admin username to the deployment results.
Understanding the Basics of targetScope in Bicep
targetScope tells Azure where your Bicep template will deploy. In other words, it defines the deployment “level” for your resources. Are you deploying into a resource group, at the subscription level, across a management group, or even at the tenant level? This matters because some resources (like a virtual network or storage account) are typically created in a resource group, while others (like a policy assignment or role assignment) might be applied at the subscription or management group level.
When you set targetScope, you’re basically deciding what context your template runs in, and what functions and resource types make sense inside it. For example, if your template is meant to deploy into a resource group, you’ll usually use the default scope (resourceGroup). But if you’re creating things like a subscription-level policy assignment, you’ll set it to subscription so the deployment matches the resource’s real scope.
Here’s how you define it in your Bicep file:
targetScope = 'resourceGroup'
The supported values are: resourceGroup, subscription, managementGroup, and tenant. In the upcoming tutorial, we will go deeper into each scope, when to use it, and how it affects resource declarations and deployments.
Showing case and example
This Bicep file deploys a single Storage Account into a resource group because targetScope = 'resourceGroup' sets the deployment scope. It defines a location parameter that automatically defaults to the resource group’s region using resourceGroup().location, then creates a simple variable called name with the storage account name mystorageaccount. The resource block (st) is the actual Azure resource being deployed Microsoft.Storage/storageAccounts and it uses the name and location values while setting the required sku (Standard_LRS) and kind (StorageV2). Finally, the output returns the deployed storage account’s name (st.name) so you can easily see or reuse it after the deployment completes.
targetScope = 'resourceGroup'
param location string = resourceGroup().location
var name = 'mystorageaccount'
resource st 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: name
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
}
output storageName string = st.name
In the next post we will cover the Resource scope
Conclusion
Today, we walked through the core building blocks of a Bicep file targetScope, parameters, variables, and outputs. But it’s important to note that none of these actually deploy anything on their own, because we haven’t covered the resource block yet (that’s where the real deployment happens). Still, understanding these elements first is a key step, because they’re what make your templates clean, efficient, and scalable as your resource definitions grow.