Getting your Azure infrastructure defined and deployed shouldn’t feel like wrestling with a complex puzzle. This guide dives deep into Bicep resource declaration, cutting through the complexity to show you exactly how to define and manage your Azure infrastructure with clarity and confidence. Let’s build something awesome together!
This is a series of Bicep tutorials. You can read about the previous posts here
Understanding Variables, Parameters, and Output (Part 2)
Understanding the basics and why you need Bicep and how to install VSCode(Par1)
The below audio is an AI generated that summarize the previous 2 post and this one too.
Table of Contents
Resource Declaration in Bicep
Bicep is a declarative language that simplifies Azure resource deployment. Think of it as a blueprint for your Azure infrastructure, allowing you to define the desired state of your resources consistently and repeatably. Instead of writing complex ARM templates with a lot of JSON, Bicep offers a cleaner, more intuitive syntax. This makes it easier to manage your cloud environments, reduce errors, and ensure your deployments are always in a known, good state. If you’re looking to automate your Azure deployments, understanding resource declaration in Bicep is fundamental.
What is Resource Declaration in Bicep?
At its core, resource declaration in Bicep is about telling Azure what resources you want to deploy and how you want them configured. You specify the type of resource (like a virtual machine, storage account, or virtual network), its properties (like the SKU, name, and location), and any dependencies it might have on other resources. Bicep then translates these declarations into the necessary Azure Resource Manager (ARM) API calls to create or update your infrastructure. This declarative approach means you focus on what you want, not how to achieve it step-by-step.
How to Declare a Resource in Bicep
Declaring a resource in Bicep is straightforward. You use the resource keyword, followed by a symbolic name for the resource, the resource type along with its API version, and an object containing its properties.
Make sure to use VSCode with Bicep extension installed as it saves you a lot of time
resource <symbolic-name> '<full-type-name>@<api-version>' = {
<resource-properties>
}
Symbolic names are case-sensitive. They may contain letters, numbers, and underscores
Here’s a basic example of declaring an Azure Storage Account:
resource myStorageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = {
...
}

In this snippet:
resource: This keyword signals that you’re declaring a resource.myStorageAccount: This is the symbolic name you’ll use to refer to this storage account elsewhere in your Bicep file. This can be any other name, like “mycompanystorage” or whatever fitsMicrosoft.Storage/storageAccounts@2021-09-01: This is the fully qualified resource type and API version. You can find these details in:- Azure Bicep documentation
- Using VSCode, as it will complete it automatically (best option),
- Using Azure CLI commands like az provider show –namespace Microsoft.Storage –query “resourceTypes[?resourceType==’storageAccounts’].apiVersions | [0]” The
Microsoft.Storage/storageAccountsis always fixed, but you need to know the API version
{ ... }: This block contains the resource’s properties.
Always try to use the latest version of the API as it support more features.
Understanding Resource Properties
The properties block is where you define the specific configurations for your resource. These properties are specific to the resource type and API version you are using. For our storage account example, name, location, sku, and kind are all properties. For instance, the sku property for a storage account specifies the performance tier and replication strategy (like Standard_LRS for locally redundant standard storage). The kind property defines the type of storage account (e.g., StorageV2 for general-purpose v2 accounts).
What are Symbolic Names?
Symbolic names are unique identifiers you assign to resources within your Bicep file. You use these symbolic names to reference resources, access their properties, or define dependencies between them. For example, in resource MystorageAccount '...' = { ... }, is the symbolic name. This name is only valid within the scope of your Bicep file; it does not become the actual name of the deployed Azure resource unless you explicitly set the MystorageAccountname property to match it.
How to Reference Resources
You use the symbolic name you defined to access properties of a previously declared resource. This is often used when you need to, for example, set the virtual network for a virtual machine to an existing virtual network, or use the connection string of a newly created storage account in another resource.
Let’s say you declare a virtual network and then want to assign a subnet to a virtual machine that uses that virtual network. You’d reference the virtual network using its symbolic name:
resource myVirtualNetwork 'Microsoft.Network/virtualNetworks@2021-05-01' = {
name: 'myVNet'
location: 'eastus'
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
}
}
// Example of referencing a property from virtualNetwork for a subnet declaration
resource subnet 'Microsoft.Network/virtualNetworks/subnets@2021-05-01' = {
parent: myVirtualNetwork // <--- Using the symbolic name of the virtual network
name: 'default'
properties: {
addressPrefix: '10.0.0.0/24'
}
}
In this example, parent: myVirtualNetwork directly uses the symbolic name myVirtualNetwork to associate the subnet with the virtual network. This establishes a clear dependency.
How Resource Deployment Works in Bicep
When you declare a resource in a Bicep file, you are describing the final state you want that resource to be in, not just creating it once. During deployment, Azure compares the definition in your Bicep file with the existing resource in the target scope and then makes whatever changes are needed to bring the resource in line with that definition.
Because of this behavior, you need to be careful when redeploying templates. If your Bicep file includes changes you didn’t intend—such as a modified SKU, configuration setting, or property—Azure will apply those changes to the existing resource. This is powerful and intentional, but it also means that even small edits in a Bicep file can result in real updates to live resources, so reviewing changes before deployment is an important best practice.

If you want to restrict and ensure that the deployment will never update if the resource already exist, and only create new if not exist then you can use the decorator @onlyIfNotExists()
The following code will only create the storage account named as mystorageacct is its not exist, if its exist then no change will be made
@onlyIfNotExists()
resource example 'Microsoft.Storage/storageAccounts@2025-06-01' = {
name: 'mystorageacct'
location: resourceGroup().location
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
}
Working with Existing Resources
Sometimes, you don’t want to create new resources; you need to reference resources that already exist in your Azure environment. Bicep handles this gracefully with the concept of existing resources.
What is an Existing Resource?
An existing resource is a resource that has already been deployed and is managed outside of the current Bicep deployment. You might need to interact with existing resources for various reasons, such as configuring a new service to use an existing database or attaching a new virtual machine to an existing virtual network. When you reference an existing resource, Bicep does not attempt to create or modify it; it simply treats it as a known entity whose properties can be accessed.
How to Reference an Existing Resource in Bicep
To reference an existing resource, you use the existing keyword. This tells Bicep that the resource already exists and you are simply providing its details.
Consider a scenario where you need to deploy a web app into an existing App Service Plan:
// Assume 'myAppServicePlan' already exists in the resource group
resource existingAppServicePlan 'Microsoft.Web/serverfarms@2021-03-01' existing = {
name: 'myAppServicePlan' // The actual name of the existing resource
}
resource webApp 'Microsoft.Web/sites@2021-03-01' = {
name: 'my-unique-webapp-name'
location: 'eastus'
properties: {
serverFarmId: existingAppServicePlan.id // <-- Referencing the ID of the existing App Service Plan
}
}
In this example:
existingkeyword signifies thatexistingAppServicePlanis not being created by this deployment.name: 'myAppServicePlan': You must provide the actual name of the existing resource.existingAppServicePlan.id: We access theidproperty of the existing resource, which is a common way to link resources.


When to Use Existing Resources
You should use the existing keyword when:
- Integrating with existing infrastructure: You’re deploying new components that need to connect to or utilize resources already deployed in Azure, such as databases, storage accounts, or virtual networks.
- Modifying specific properties of existing resources: While Bicep is primarily for deployment, you might use
existingto reference a resource and then modify a specific, updatable property of it if that’s the only change needed. However, be cautious as this can sometimes lead to unexpected behavior if not managed carefully. For straightforward updates, consider Azure CLI or PowerShell. - Phased deployments: When breaking down a large deployment into smaller, manageable Bicep files, you’ll use
existingto reference resources deployed by previous files. - Managing resources outside of Bicep: If certain resources are managed by other tools or processes, but you need to deploy other resources that depend on them,
existingis the way to go.
Best Practices for Resource Declaration
Writing Bicep files that are easy to read, maintain, and scale is key to successful infrastructure as code. Following best practices for resource declaration will save you a lot of headaches down the line.
How to Organize Resource Declarations
Keep your Bicep files organized by grouping related resources. For example, you might have a file for networking resources (VNet, subnets, NSGs), another for compute resources (VMs, App Services), and a third for storage. This modular approach makes your code more readable and easier to manage.
Use meaningful symbolic names that clearly indicate the purpose and type of the resource. For instance, instead of r1, use virtualNetworkEastUS or storageAccountPrimary.
// Example of organized declarations
// Networking Module
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-05-01' = {
name: 'production-vnet'
location: 'eastus'
// ... properties
}
resource subnetFrontend 'Microsoft.Network/virtualNetworks/subnets@2021-05-01' = {
parent: virtualNetwork // Using the symbolic name of the virtual network
name: 'frontend-subnet'
// ... properties
}
// Compute Module
resource webApp 'Microsoft.Web/sites@2021-03-01' = {
name: 'production-webapp'
location: 'eastus'
properties: {
serverFarmId: appServicePlan.id // Assuming appServicePlan is declared elsewhere
}
}
// Database Module
resource sqlDatabase 'Microsoft.Sql/servers/databases@2021-11-01' = {
name: 'production-db'
location: 'eastus'
// ... properties
}
Should I Declare All Resources in One File?
Generally, it’s not recommended to declare all resources in a single, monolithic Bicep file, especially for larger or complex environments. Here’s why:
- Maintainability: Large files become difficult to navigate, understand, and update.
- Reusability: It’s harder to reuse specific components if they are buried within a massive file.
- Teamwork: Multiple people working on the same giant file can lead to merge conflicts and coordination issues.
- Deployment Scope: You might only want to update a subset of resources, but a single file requires deploying everything, increasing the risk of unintended changes.
Instead, break down your infrastructure into logical modules. You can then create a main Bicep file that deploys these modules, or use Bicep’s module feature to compose deployments. This makes your infrastructure as code much more manageable and scalable.
What are Common Mistakes to Avoid?
- Incorrect API Versions: Using outdated or incorrect API versions can lead to unexpected behavior or prevent resources from deploying. Always check the latest stable API versions in the Azure documentation.
- Missing Dependencies: Not explicitly defining dependencies between resources can cause deployment failures if a resource is deployed before a resource it relies on. Bicep’s implicit dependencies (based on resource references) help, but sometimes explicit
dependsOnproperties are needed for complex scenarios. - Hardcoding Values: Embedding sensitive information (like passwords) or environment-specific values (like resource names or locations) directly in your Bicep files is a security risk and reduces reusability. Use parameters and secure parameter files instead.
- Not Using
existingProperly: Forgetting to use theexistingkeyword when referencing resources that are not part of the current deployment can lead to Bicep trying to create them, resulting in deployment errors. - Overly Complex Expressions: While Bicep is expressive, overly complex
ifconditions or nested expressions can make files hard to read. Simplify logic where possible.
By understanding these points and applying them in your Bicep deployments, you’ll be well on your way to managing your Azure infrastructure more effectively and efficiently.