Bicep Tips and Tricks | #3 | Naming Convention and Functions

Overview

One of my bugbears is seeing either a complete lack of naming conventions or manual naming mechanisms that introduce human error through mistakes and misunderstandings. Naming conventions are incredibly important, but equally critical is how they’re implemented and maintained. Let’s explore a better approach.

What is a Naming Convention and Why Do We Need One?

A naming convention is a set of rules for naming Resource Groups and Resources (among other Azure components). These rules define the structure and composition of names, ensuring clarity, consistency, and compliance with Azure naming requirements such as length limits, valid characters, and scope uniqueness.

Common components that make up resource names include:

  • Location
  • Environment
  • Project Prefix
  • Resource Abbreviation

Naming conventions aren’t one-size-fits-all and require tailoring to your specific needs. Any changes should be considered across all systems to maintain a single source of truth. Well-defined naming conventions significantly improve the readability and maintainability of your Azure estate, aligning with best practices outlined in the Cloud Adoption Framework.

Example convention: {Resource Type}-{Project}-{Environment}-{Location}
Result: LogicAppName: logic-btt-dev-ukwest

Anti-patterns to Avoid

Hard-coded resource names:

name: 'MyLogicApp'

Issues: Poor maintainability, readability, consistency, and convention adherence

Hard-coded naming variables:

var lgName = 'MyLogicApp'

Issues: Poor maintainability across templates, inconsistency, and no convention

Hard-coded naming convention:

var lgName = 'logic-${project}-${environment}-${location}'

Issues: Poor maintainability across templates, template inconsistency, and scattered convention logic

My recommended approach leverages User-defined functions to provide consistent and maintainable naming conventions. Combined with Bicep Imports, this ensures consistency across all templates.

For enhanced maintainability, I also recommend implementing centralized core parameters with types and constructors. This approach significantly reduces the number of function parameters needed for each naming operation.

Implementation Structure

Create a Common folder within your IaC directory:

IaC
  ↳ Common
    ↳ types.bicep
    ↳ nameConventionFunctions.bicep
  ↳ main.bicep
  • types.bicep - Contains core parameter user-defined types and constructor functions
  • nameConventionFunctions.bicep - Contains naming convention functions

Core Types Setup

/**********************************
  Bicep Template: Shared Types
  Author: Andrew Wilson
***********************************/

// ** User Defined Types and Constructors **
// *****************************************

// TYPE: Core Parameters
// *********************

@export()
@description('Core Parameters Definition for Bicep Templates')
@sealed()
type coreParams = {
  @description('Location to deploy resources to')
  location: string
  @description('Location Short Name for resource naming')
  locationShortName: string
  @description('Environment to deploy to')
  environment: string
  @description('Project Prefix for resource naming')
  projectPrefix: string
}

@export()
@description('Core Parameters Constructor')
func newCoreParams(
  location string,
  locationShortName string,
  environment string,
  projectPrefix string
) coreParams => {
  location: location
  locationShortName: locationShortName
  environment: environment
  projectPrefix: projectPrefix
}

Naming Convention Functions

I’ve structured my naming conventions using these key parameters:

  • Resource Abbreviation - Identifies the resource type (Azure abbreviations reference)
  • Project Prefix - Identifies the project
  • Environment - Identifies the deployment environment
  • Location - Identifies the deployment location (optional for global resources)
  • Context - Describes the resource’s purpose (optional)

The following functions cover some common naming scenarios:

/*******************************************
  Bicep Template: Name Constructor Functions
  Author: Andrew Wilson
*******************************************/

// ** Shared Imports **
// ********************

import { coreParams } from './types.bicep'

// Resource Name Constructors
//***************************

// Basic
@export()
@description('Generates a resource name based on the provided parameters (used for common usage resources)')
func basicResource(resourceAbbreviation string, coreParameters coreParams) string =>
  '${resourceAbbreviation}-${coreParameters.projectPrefix}-${coreParameters.environment}-${coreParameters.location}'

@export()
@description('Generates a resource name based on the provided parameters but ignoring the location (used for common usage resources that are not location specific)')
func unlocalisedBasicResource(resourceAbbreviation string, coreParameters coreParams) string =>
  '${resourceAbbreviation}-${coreParameters.projectPrefix}-${coreParameters.environment}'

// Context Specific
@export()
@description('Generates a Context Specific Resource Name based on the provided parameters')
func csResource(resourceAbbreviation string, coreParameters coreParams, contextName string) string =>
  '${resourceAbbreviation}-${coreParameters.projectPrefix}-${contextName}-${coreParameters.environment}-${coreParameters.location}'

// Resource Group
@export()
@description('Generates a Resource Group name based on the provided parameters')
func resourceGroup(contextName string, coreParameters coreParams) string =>
  'rg-${coreParameters.projectPrefix}-${contextName}-${coreParameters.environment}-${coreParameters.location}'

@export()
@description('Generates a Resource Group name based on the provided parameters but without an env specified')
func resourceGroupNonEnvSpecific(contextName string, coreParameters coreParams) string =>
  'rg-${coreParameters.projectPrefix}-${contextName}-${coreParameters.location}-shared'

// Storage Account
@export()
@description('Generates a Storage Account Resource Name based on the provided parameters')
func storageAccountResource(coreParameters coreParams, contextName string?) string =>
  empty(contextName)
    ? 'st${coreParameters.projectPrefix}${coreParameters.environment}${coreParameters.locationShortName}'
    : 'st${coreParameters.projectPrefix}${contextName}${coreParameters.environment}${coreParameters.locationShortName}'

Usage Example

Here’s how to use these functions in your main deployment template:

/***************************************
Bicep Template: Main Deploy
Author: Andrew Wilson
****************************************/

targetScope = 'resourceGroup'

// ** Shared Imports **
// ********************

import { newCoreParams } from './Common/types.bicep'
import * as newName from './Common/nameConventionFunctions.bicep'

// ** Parameters **
// ****************

@description('Location to deploy resources to')
param location string

@description('Location Short Name for resource naming')
param locationShortName string

@description('Environment to deploy to')
param environment string = 'dev'

@description('Project Prefix for resource naming')
param projectPrefix string = 'myproject'

// ** Variables **
// ***************

var coreParameters = newCoreParams(location, locationShortName, environment, projectPrefix)

// Standard Logic App Resource Names
var logicAppName = newName.basicResource('logic', coreParameters)
var appServicePlanName = newName.csResource('asp', coreParameters, 'lg')
var storageAccountName = newName.storageAccountResource(coreParameters, 'lg')

// ** Resources **
// ***************

...

Generated Names:

  • Logic App: logic-myproject-dev-ukwest
  • App Service Plan: asp-myproject-lg-dev-ukwest
  • Storage Account: stmyprojectlgdevukw

Summary

Implementing a robust naming convention is crucial for Azure resource management, but the mechanism matters just as much as the convention itself. By using Bicep user-defined functions combined with centralized core parameters, you can:

  • Eliminate human error from manual naming processes
  • Ensure consistency across all templates and environments
  • Improve maintainability with centralized naming logic
  • Enhance readability of your Infrastructure as Code
  • Align with best practices from the Cloud Adoption Framework

This approach transforms naming from a scattered, error-prone process into a centralized, reliable system that scales with your infrastructure needs. The initial setup investment pays dividends in reduced maintenance overhead and improved deployment reliability. Happy Bicep-ing!

For the original version of this post see Andrew Wilson's personal blog at Bicep Tips and Tricks | #3 | Naming Convention and Functions