Bicep CI/CD Handbook
Bicep CI/CD HandbookToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage
Edit page

Developing with Azure Bicep

Azure Bicep ships with a growing set of features to help you develop your Infrastructure as Code. While the defaults are designed to help you get started quickly, you can customize your development experience to suit the needs of your organization.

Configure bicepconfig.json

While it is possible to work with multiple configuration files, we recommend keeping a top-level bicepconfig.json file at the root of each repo, and using inline linter overrides in individual Bicep files where necessary.

See here for an overview of the Bicep configuration file.

Linters

All linter rules come with a default level, but this can be overridden in the Bicep configuration file. You may want to think carefully about which rules you want to enforce (by upgrading the level to “error”), or disable (by downgrading the level to “off”) at the repository level.

Example bicepconfig.json

Here is an example Bicep configuration file with linter rule customizations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "analyzers": {
    "core": {
      "rules": {
        "use-parent-property": {
          "level": "error"
        },
        "adminusername-should-not-be-literal": {
          "level": "off"
        }
      }
    }
  }
}

Inline overrides

It is possible to disable linting violations inline in your Bicep file using the #disable-next-line directive. See Silencing false positives for more information.

If you don’t feel that the linter rule applies to your code generally, use the bicepconfig.json in your repo to disable it globally rather than using inline overrides.

Referencing modules

From registries

If you are consuming modules from one or more private Module Registries, it is worth configuring your bicepconfig.json with common module aliases to simplify usage.

For example, the following configuration:

{
  "moduleAliases": {
    "br": {
      "storage": {
        "registry": "myprivateregistry.azurecr.io",
        "modulePath": "bicep/modules/storage"
      }
    }
  }
}

Would allow you to simplify usage to:

module foo 'br/storage:account:v1' = {
  ...
}

Avoid ’latest'

Registry tags should be considered immutable when publishing modules. You should always pin to a specific tag (e.g. v0.2.1) instead of a tag that may be changed (latest), to ensure that your deployment is reproducible in the future should you need to redeploy.

Using metadata and comments

Prefer using @description() decorators over code comments for explaining functionality, because they can be understood programatically with tooling:

  • The Bicep VSCode extension will show hovers containing descriptions on parameters, outputs, and other declarations. This makes them easier for other developers to quickly understand.
  • There are tooling options to automatically generate Markdown documentation for Wikis or documentation sites based on @description() annotations.

Coding best practices

Importing files

If you’re defining files within your Bicep files to use in your deployment, consider instead using the loadTextContent function for non-binary content, the loadFileAsBase64 function for binary content, and the loadJsonContent / loadYamlContent for loading JSON / YAML respectively.

This is preferred:

var myScript = loadTextContent('bash.sh')

Over:

var myScript = '''
#!/bin/bash

...
'''

It’s generally better to be able to represent these as separate files in your repo, as it allows for editors to understand the syntax, and makes it simpler to test them when authoring. loadJsonContent & loadYamlContent also understand your files structurally, and will allow for completions and type validation when using them - making for a better editor experience.

Complex Types

Instead of defining the following:

@description('The name of the storage account')
param storageAccountName string
@description('The storage account SKU')
param storageAccountSku string

Consider instead using User-defined types to group related fields together:

@description('Storage account properties')
param storageAccount {
  @description('The resource name')
  name: string

  @description('The SKU')
  sku: string
}

Bicep Parameters

Defining your parameters with Bicep parameters files gives you the following advantages over JSON-based parameters:

  • Instant editor feedback about mismatches or type validation issues.
  • Support for expressions (including loading data from external files).
  • Easy-to-interpret diagnostics at deploy time if there are any failures compiling.

Pinning to a version of Bicep

It’s useful to have control over the version of Bicep you install in your CI pipeline. This ensures that your builds are reproducible, yielding the following benefits:

  • You are in control of upgrading Bicep: New validations or linters are added periodically, and may impact whether your Bicep files build successfully. Rather than having this happen automatically whenever a new version is published, we recommend being in control of your Bicep version by explicitly referencing it in your pipeline.
  • You have the guarantee that your CI & CD pipeline are running with the exact same version of Bicep: You’ll have a higher level of confidence that a pass in CI will translate to a pass in CD.
  • You can redeploy an old copy of your code having to fix things: Because the version of Bicep is declared in your codebase, you’ll have a guarantee that if your Bicep file built successfully 1 year ago, it’ll still build successfully today.

Example of pinning

For Azure CLI, you can achieve this with the following. Replace v0.21.1 with the version you wish to pin to.

az bicep install --version v0.21.1