Skip to content
Learni
View all tutorials
Cloud & DevOps

How to Deploy Azure Resources with Bicep in 2026

12 minBEGINNER
Lire en français

Introduction

Bicep is the Domain-Specific Language (DSL) developed by Microsoft to simplify Azure Resource Manager (ARM) templates. Unlike verbose ARM JSON, Bicep offers concise, readable, type-safe syntax, making Infrastructure as Code (IaC) accessible even for beginners. In 2026, Bicep is the standard tool for Azure deployments, used by millions of DevOps developers.

Why adopt it? It reduces syntax errors, supports reusable modules, native loops, and conditions. Imagine deploying a storage account, App Service, and database in a single readable file instead of nested JSON. This beginner tutorial guides you from zero: installation, first template, parameters, variables, outputs, and deployment. By the end, you'll master the basics to scale to complex environments. Ready to turn manual deployments into idempotent code? (142 words)

Prerequisites

  • A free Azure account (create one at portal.azure.com)
  • Azure CLI installed (version 2.30+)
  • Code editor like VS Code with the Bicep extension
  • Basic command-line knowledge

Install Bicep and Log in to Azure

terminal-install.sh
az login
az extension add --name bicep --yes
az bicep version
az group create --name rg-bicep-demo --location francecentral

These commands log you into Azure, install the official Bicep extension, and create a resource group rg-bicep-demo in France Central. Verify the installation with az bicep version. Pitfall: Without az login, deployments fail with an authentication error.

Your First Bicep Template: A Storage Account

Let's start with a simple template: deploy an Azure storage account. Bicep uses TypeScript-like syntax with resource, param, var, and output. Copy the following code into main.bicep.

Create the Simple main.bicep File

main.bicep
@description('Compte de stockage pour le demo Bicep')
param storageAccountName string = 'stbicep${uniqueString(resourceGroup().id)}'

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: storageAccountName
  location: resourceGroup().location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
}

This template declares a parameter for the unique storage account name (avoids collisions) and creates a StorageV2 account with Standard_LRS SKU. uniqueString() generates a hashed suffix based on the RG. Pitfall: Non-unique names cause deployment errors; always use uniqueString() for global resources.

Deploy Your First Template

terminal-deploy1.sh
az deployment group create --resource-group rg-bicep-demo --template-file main.bicep

Deploys the template to the created RG. Bicep automatically compiles to ARM JSON in the background. Check it in the Azure portal. Pitfall: If the .bicep file has errors, use az bicep build --file main.bicep to debug the generated JSON.

Adding Parameters and a JSON File

For more flexibility, externalize parameters into a JSON file. This enables reusable deployments without modifying the Bicep file.

Template with Required Parameters

main-params.bicep
@description('Compte de stockage paramétré')
@minLength(3)
@maxLength(24)
param storageAccountName string

@description('Emplacement')
@allowed([
  'francecentral'
  'northeurope'
])
param location string = 'francecentral'

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
}

output storageAccountId string = storageAccount.id
output storageAccountName string = storageAccount.name

Adds decorators like @minLength and @allowed to validate inputs at compile time. Outputs expose the ID and name for chaining. Pitfall: Without validation, invalid params crash runtime deployment; Bicep catches them early.

Parameters File params.json

params.json
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": {
      "value": "stbicepdemo123"
    },
    "location": {
      "value": "francecentral"
    }
  }
}

This JSON provides concrete values. The schema enables VS Code autocompletion. Pitfall: A value not matching @allowed triggers a Bicep validation error before deployment.

Deploy with Parameters

terminal-deploy-params.sh
az deployment group create --resource-group rg-bicep-demo --template-file main-params.bicep --parameters @params.json

Uses --parameters @params.json to inject values. Outputs display in the console. Pitfall: Forgetting @ treats the file as a string; always use @file for JSON.

Variables, Conditions, and Loops

Level up: Use var for computations, if for conditionals, and for for arrays. Example: Deploy 2 containers if needed.

Advanced Template with var, if, and for

main-advanced.bicep
param storageAccountName string
param location string = 'francecentral'
param enableContainers bool = true
param containerNames array = [
  'data'
  'logs'
]

var tags = {
  'project': 'bicep-demo'
  'env': 'dev'
}

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: storageAccountName
  location: location
  tags: tags
  sku: { name: 'Standard_LRS' }
  kind: 'StorageV2'
}

resource container 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = [for name in containerNames: {
  name: '${name}'
  properties: {
    publicAccess: 'None'
  }
  dependsOn: [
    storageAccount
  ]
  if: enableContainers
}]

var tags centralizes metadata. The for loop creates containers dynamically. if: enableContainers makes it conditional. dependsOn manages order. Pitfall: Without dependsOn, creation order is unpredictable; Bicep often infers it, but be explicit for safety.

Advanced Deployment and Cleanup

terminal-deploy-advanced.sh
az deployment group create --resource-group rg-bicep-demo --template-file main-advanced.bicep --parameters storageAccountName=stbicepadv123 location=francecentral enableContainers=true containerNames='["data","logs"]'

# Nettoyage
az group delete --name rg-bicep-demo --yes --no-wait

Passes params inline for quick testing. Cleanup deletes everything. Pitfall: CLI array params require JSON strings; use a file for complexity.

Best Practices

  • Modularize: Break into reusable .bicep modules with module for DRY principles.
  • Always validate: Use decorators like @secure and @minValue for safe inputs.
  • Use targets: Set targetScope = 'subscription' for multi-RG deployments.
  • Integrate CI/CD: GitHub Actions or Azure DevOps with az bicep build.
  • Version APIs: Pin like @2023-05-01 for stability.

Common Errors to Avoid

  • Non-unique names: Forgetting uniqueString() for global storage/App Services.
  • Wrong scope: Deploying at resource group level without targetScope = 'resourceGroup'.
  • Unsecured params: Passing secrets in plain text; use @secure() and Key Vault.
  • Ignoring dependsOn: Wrong creation order leads to timeouts.

Next Steps

Master modules, advanced loops, and existing resources. Check the official Bicep docs, Bicep playground. For pro expertise, explore our Learni DevOps Azure trainings.