DEV_NET_CORE
GET_STARTED
AzureDelivery, infrastructure as code, scaling, and cost control

Bicep fundamentals, modules, and reusable environment definitions

Overview

Bicep is Azure's domain-specific language for declarative infrastructure as code. Instead of writing imperative scripts that create resources step by step, a Bicep file describes the desired Azure resources, properties, relationships, parameters, and outputs. Azure Resource Manager then orchestrates deployment.

Bicep matters because production Azure environments need repeatable infrastructure, reviewable changes, environment consistency, secure parameter handling, and predictable deployments. It is commonly used for resource groups, App Service, Azure SQL, Key Vault, managed identities, role assignments, networking, monitoring, and platform configuration.

For interviews, candidates should be able to explain idempotency, scopes, parameters, variables, resources, outputs, modules, .bicepparam files, secrets, what-if, complete versus incremental thinking, module registries, environment-specific configuration, and how Bicep fits into CI/CD.

Core Concepts

Declarative Infrastructure

Bicep is declarative. You describe what Azure should look like, not the exact sequence of commands to get there.

Code
param location string = resourceGroup().location
param storageAccountName string

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

The deployment engine determines ordering from resource references and dependencies.

Idempotency

Bicep deployments are intended to be repeatable. If a resource already exists with the desired configuration, redeploying should not recreate it unnecessarily.

This enables:

  • CI/CD-driven infrastructure changes.
  • Recovery from failed deployments.
  • Drift correction.
  • Environment rebuilds.
  • Pull-request review of infrastructure changes.

Idempotency does not mean every change is safe. Changing a SKU, name, region, network rule, or data setting can still have operational consequences.

Bicep and ARM Templates

Bicep is a more readable abstraction over Azure Resource Manager templates. Bicep compiles to ARM JSON templates during deployment.

Important implications:

  • Bicep supports Azure resource types and API versions supported by ARM.
  • ARM deployment behavior still applies.
  • Bicep does not maintain a separate state file.
  • Azure stores deployment state and resource state.
  • You can use what-if to preview changes.

Deployment Scopes

Bicep can deploy to different scopes:

  • Resource group.
  • Subscription.
  • Management group.
  • Tenant.

Choose scope based on what the template creates. A resource group-scoped template can deploy App Service and storage accounts. A subscription-scoped template can create resource groups and assign policies. Management group and tenant scopes are used for governance.

Parameters

Parameters are values supplied to a Bicep file at deployment time. They make templates reusable across environments.

Code
@allowed([
  'dev'
  'test'
  'prod'
])
param environmentName string

@minValue(1)
@maxValue(10)
param appInstanceCount int = 1

Use decorators such as @allowed, @minValue, @maxValue, @description, and @secure to make contracts clearer and safer.

Variables

Variables compute values inside a template. They reduce repetition and make naming or tagging rules consistent.

Code
var namePrefix = 'contoso-${environmentName}'
var commonTags = {
  environment: environmentName
  workload: 'orders'
}

Use variables for derived values, not for values that should be supplied by the environment or pipeline.

Resources

Resources declare Azure objects.

Code
resource appPlan 'Microsoft.Web/serverfarms@2023-12-01' = {
  name: '${namePrefix}-plan'
  location: location
  sku: {
    name: environmentName == 'prod' ? 'P1v3' : 'B1'
    capacity: environmentName == 'prod' ? 2 : 1
  }
}

Good resource declarations use stable names, explicit API versions, consistent tags, and environment-aware settings.

Outputs

Outputs expose values from a deployment for later pipeline steps or dependent templates.

Code
output appServiceName string = app.name
output appUrl string = 'https://${app.properties.defaultHostName}'

Avoid outputting secrets. Outputs can be visible in deployment history.

Existing Resources

Use existing when a template needs to reference a resource it does not create.

Code
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = {
  name: keyVaultName
}

This is common for shared Key Vaults, existing virtual networks, shared log workspaces, or centralized identities.

Dependencies

Bicep infers dependencies when one resource references another by symbolic name.

Code
resource site 'Microsoft.Web/sites@2023-12-01' = {
  name: appName
  location: location
  properties: {
    serverFarmId: appPlan.id
  }
}

Avoid manual dependsOn unless inference is not possible. Excessive explicit dependencies can serialize deployments and hide the actual resource relationship.

Modules

A module is a Bicep file deployed by another Bicep file. Modules encapsulate related resources and support reuse.

Code
module webApp './modules/web-app.bicep' = {
  params: {
    name: appName
    location: location
    appServicePlanId: appPlan.id
    tags: commonTags
  }
}

Use modules for stable boundaries such as:

  • Web app plus settings.
  • Storage account plus containers.
  • Key Vault plus diagnostics.
  • SQL server plus database.
  • Monitoring resources.

Do not create tiny modules for every single property. Module boundaries should match ownership and reuse.

Module Outputs

Modules can expose outputs to the parent template.

Code
module app './modules/web-app.bicep' = {
  params: {
    name: appName
    location: location
  }
}

output appHostname string = app.outputs.defaultHostName

Use outputs to connect modules without duplicating naming logic.

Module Registry and Template Specs

Shared modules can be published to a private registry or packaged as template specs. This helps organizations standardize patterns such as secure storage, diagnostics, networking, and identity.

Use shared modules when:

  • The pattern is used by multiple teams.
  • Security and compliance settings must be consistent.
  • Versioning and review are required.
  • A platform team owns the baseline.

Avoid central modules that become so generic they are impossible to understand.

Parameter Files

.bicepparam files separate environment values from the main template.

Code
using './main.bicep'

param environmentName = 'dev'
param location = 'eastus'
param appSku = 'B1'
param minInstanceCount = 1

Use separate parameter files such as:

Code
main.dev.bicepparam
main.test.bicepparam
main.prod.bicepparam

This keeps the infrastructure definition consistent while allowing environment-specific capacity, region, naming, and feature choices.

Reusable Environment Definitions

A reusable environment definition usually combines:

  • A shared main.bicep.
  • Modules for resource groups, app hosting, data, identity, networking, and monitoring.
  • Environment-specific .bicepparam files.
  • Tags for ownership and cost attribution.
  • Naming conventions.
  • Secure parameter handling.
  • Pipeline stages that pass the correct parameters.

Example layout:

Code
infra/
  main.bicep
  main.dev.bicepparam
  main.test.bicepparam
  main.prod.bicepparam
  modules/
    app-service.bicep
    key-vault.bicep
    monitoring.bicep
    sql-database.bicep

This is easier to review than maintaining unrelated templates for every environment.

Secrets

Do not store passwords, connection strings, or certificates in parameter files. Parameter files are source-controlled plain text unless you build a separate secure process.

Use:

  • Key Vault references.
  • Managed identities.
  • @secure() parameters only when the secret must be passed.
  • Pipeline secret variables only when unavoidable.
  • No secret outputs.

Prefer identity and Key Vault over injecting secrets into templates.

What-If

What-if previews planned resource changes before deployment. It helps reviewers understand whether a template will create, modify, or delete resources.

Typical pipeline flow:

Code
bicep build
bicep lint
az deployment group what-if
approval
az deployment group create

What-if is not a substitute for review or testing, but it is a strong guardrail.

Validation and Linting

Use Bicep build and linting in CI:

Code
az bicep build --file infra/main.bicep
az bicep lint --file infra/main.bicep

Validation catches syntax, type, and rule issues before deployment. Organization-specific policies should also be enforced with Azure Policy, pipeline checks, and pull-request review.

Incremental and Complete Thinking

Most deployments use incremental behavior: resources declared in the template are created or updated, and unrelated resources are left alone.

Complete-mode style thinking is dangerous unless the template truly owns everything in scope. Accidentally removing resources can cause outages.

For environment definitions, be explicit about ownership:

  • Which resources are managed by this template?
  • Which resources are referenced as existing?
  • Which resources are intentionally outside the deployment?

Naming and Tags

Names and tags are not decoration. They support operations, cost attribution, automation, and security.

Common tags:

Code
environment
workload
owner
costCenter
dataClassification
managedBy

Names should be deterministic but not leak sensitive business details.

Common Mistakes

  • Copying portal-generated templates without simplifying them.
  • Hardcoding production values in main.bicep.
  • Storing secrets in parameter files.
  • Creating modules with unclear ownership.
  • Overusing explicit dependsOn.
  • Ignoring what-if output.
  • Not pinning meaningful API versions.
  • Mixing manually managed and IaC-managed settings without ownership rules.
  • Using one giant template with no modular boundaries.
  • Making dev and prod templates drift.

Best Practices

  • Keep the desired infrastructure in source control.
  • Use modules for real reuse and ownership boundaries.
  • Use .bicepparam files for environment differences.
  • Use what-if before production deployments.
  • Use linting and pull requests.
  • Keep secrets in Key Vault or identity-based access.
  • Tag resources consistently.
  • Prefer managed identities over connection strings.
  • Document module contracts with parameters and outputs.
  • Review cost and reliability differences between environments intentionally.

Interview Practice

PreviousMetrics vs logs vs tracesNext UpDeployment slots, rollout safety, autoscaling, availability zones, and cost-aware tiering