Modifying Existing Templates

Complete the full lesson to earn 25 points

Work through each section, then tap “Mark as Complete” on the last one.

Module: Deploy and Manage Azure Compute Resources

Lesson: Modifying Existing ARM Templates and Bicep Files

Introduction: The Evolution of Infrastructure as Code

In the modern cloud environment, infrastructure is rarely a "set it and forget it" proposition. As your organization grows and your application requirements change, the virtual machines, storage accounts, and networking components you deployed last month will eventually require updates. Whether you are scaling up a compute cluster, updating security settings, or adding new dependencies, the ability to modify existing Infrastructure as Code (IaC) templates is a critical skill for any cloud engineer.

When we talk about "modifying existing templates," we are referring to the iterative process of updating Azure Resource Manager (ARM) templates or Bicep files to reflect changes in your environment. Initially, you might have deployed a simple web server. Six months later, you might need to add a load balancer, change the disk type of your virtual machine, or integrate an Azure Key Vault for secret management. If you cannot effectively modify your existing templates, you risk "configuration drift," where your actual cloud environment deviates from the code intended to define it.

Understanding how to safely modify these files is important because it prevents accidental downtime and ensures that your deployment history remains clean and traceable. By mastering this process, you move away from manual, error-prone changes in the Azure Portal and toward a repeatable, version-controlled workflow that serves as the backbone of professional cloud operations.


The Anatomy of an ARM Template vs. Bicep

Before diving into the modification process, it is essential to distinguish between the two primary ways we define Azure infrastructure. ARM templates use JSON, which is a data-interchange format that is highly structured but notoriously verbose. Bicep, on the other hand, is a domain-specific language (DSL) designed to simplify the authoring experience.

  • ARM Templates (JSON): These files are structured as a collection of resources, parameters, and variables. JSON requires strict syntax, including commas and braces, which can become difficult to manage in large, complex files.
  • Bicep: This language compiles down to ARM templates but provides a much cleaner syntax. It handles dependencies automatically and allows for easier modularization.

Regardless of the format, the logic of modification is identical: you identify the resource block you need to change, update the properties within that block, and redeploy the template to Azure. Azure’s deployment engine is "idempotent," meaning it compares your template to the existing state of the resource and applies only the necessary changes.

Callout: Idempotency Explained Idempotency is a core principle in cloud automation. It means that if you run the same deployment script multiple times, the final state of the environment remains the same. If you change a single property in your Bicep file and redeploy, Azure will update that specific property without recreating the entire resource, provided the modification is supported.


Step-by-Step: Modifying an Existing Bicep File

The most common task you will face is updating an existing resource's configuration. Let’s walk through a scenario where we need to update the SKU of an existing App Service Plan.

Step 1: Analyze the Current State

Before making any changes, retrieve your current Bicep file. If you do not have the original file, you can "decompile" the existing resource from the Azure Portal using the "Export Template" feature, though the resulting JSON will often need significant cleanup before it is usable as a clean Bicep file.

Step 2: Locate the Resource Block

Open your Bicep file in an editor like Visual Studio Code with the Bicep extension installed. Locate the resource block that defines your App Service Plan. It will look something like this:

resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
  name: 'my-existing-plan'
  location: resourceGroup().location
  sku: {
    name: 'F1'
    tier: 'Free'
  }
}

Step 3: Apply the Changes

To update the SKU to a 'Standard' tier, simply modify the values within the sku object.

resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
  name: 'my-existing-plan'
  location: resourceGroup().location
  sku: {
    name: 'S1'
    tier: 'Standard'
  }
}

Step 4: Validate and Deploy

Before pushing to production, use the Bicep build command to ensure there are no syntax errors. Then, use the deployment command to push the changes to Azure.

# Build the file
az bicep build --file main.bicep

# Deploy the updated template
az deployment group create --resource-group myResourceGroup --template-file main.bicep

Tip: Use What-If Analysis Before deploying, always run the what-if command. This command simulates the deployment and shows you exactly which resources will be created, updated, or deleted. It is the single most effective way to prevent accidental resource deletion.


Handling Complex Modifications: Adding Dependencies

Sometimes, a modification requires adding a new resource that depends on an existing one. For example, you might need to add a storage account to an existing virtual machine deployment.

In Bicep, you do not need to manually define the dependsOn property as often as in ARM templates because Bicep infers dependencies based on resource references. If you define a storage account and reference its connection string inside your virtual machine definition, Bicep automatically ensures the storage account is created first.

Example: Adding a Storage Account Reference

If you need to add a storage account and attach it to your VM, you would add the following block:

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

// Inside your VM resource, reference the storage account:
resource vm 'Microsoft.Compute/virtualMachines@2022-11-01' = {
  // ... other properties
  properties: {
    diagnosticsProfile: {
      bootDiagnostics: {
        enabled: true
        storageUri: storageAccount.properties.primaryEndpoints.blob
      }
    }
  }
}

By referencing storageAccount.properties.primaryEndpoints.blob, you have created a logical link. When you run the deployment, Azure sees this link and forces the storage account to be deployed before the virtual machine, ensuring no errors occur due to missing dependencies.


Best Practices for Infrastructure Modifications

Modifying infrastructure is a high-stakes activity. A single typo can lead to downtime or unintended costs. Following these industry standards will help keep your environment stable.

1. Version Control is Mandatory

Never modify a template directly on your local machine and deploy it without committing the change to a repository like Git. Version control allows you to track who changed what and, more importantly, allows you to revert to a "known good" state if a deployment fails.

2. Modularize Your Code

As your templates grow, they become difficult to manage. Break your infrastructure into smaller, reusable modules. For example, have a separate file for networking, one for compute, and one for storage. This makes modifications safer because you can test changes to a single module without touching the rest of the infrastructure.

3. Use Parameters and Variables

Hardcoding values is a recipe for disaster. Use parameters for values that change between environments (like VM sizes or instance counts) and variables for values that are consistent across your deployment (like naming conventions).

4. Environment Separation

Maintain separate parameter files for your development, staging, and production environments. When you make a modification, test it in development first. Only after verifying the change in a lower environment should you promote that change to production.

5. Keep Resource Providers Updated

Azure resource providers change frequently. Periodically review the apiVersion in your templates. Using an outdated API version might prevent you from accessing new features or, in some cases, result in your template being rejected by Azure.


Common Pitfalls and How to Avoid Them

Even experienced engineers fall into common traps when modifying templates. Being aware of these pitfalls can save you hours of debugging.

The "Destructive Update" Trap

Some properties, when changed, trigger a full replacement of the resource. For example, changing the name of a virtual machine or the osDisk.createOption property usually results in the old VM being deleted and a new one being created. Always check the official Azure documentation for the specific resource type to see which properties are "immutable" or "forces replacement."

The "Deployment Mode" Confusion

When deploying templates, you can choose between Incremental and Complete modes.

  • Incremental: This is the default. Azure adds or updates resources defined in the template. It does not touch resources in the resource group that are not in the template.
  • Complete: Azure ensures that the resource group contains only the resources defined in your template. If you have a resource in the portal that is not in your template, it will be deleted.

Warning: Complete Mode Dangers Never use "Complete" mode unless you have a very specific reason and you are absolutely certain your template contains every single resource intended for that resource group. Using it in production without strict oversight is a frequent cause of accidental data loss.

Ignoring Dependencies

If you have multiple resources that rely on each other, you must ensure the deployment order is correct. While Bicep handles most of this, there are edge cases where you must explicitly define a dependsOn property. If you see errors about resources not being found, check your dependency chain.


Quick Reference: Deployment Modes Comparison

Feature Incremental Mode Complete Mode
Default behavior Adds/Updates resources Replaces entire resource group
Unmanaged resources Left untouched Deleted
Best for Day-to-day updates Clean slate deployments
Risk level Low High

Advanced Techniques: Conditionals and Loops

When modifying templates, you often need to add logic that handles varying requirements. For example, you might want to deploy a premium storage account in production but a standard one in development.

Using Conditionals

You can use the if keyword to decide whether a resource should be deployed at all.

param deployStorage bool = true

resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = if (deployStorage) {
  name: 'mystorage'
  location: resourceGroup().location
  sku: {
    name: 'Standard_LRS'
  }
}

Using Loops

If you need to deploy multiple instances of a resource, such as three web servers, you can use a loop rather than copying and pasting the resource block three times.

param numberOfVMs int = 3

resource vmLoop 'Microsoft.Compute/virtualMachines@2022-11-01' = [for i in range(0, numberOfVMs): {
  name: 'vm-${i}'
  // ... other properties
}]

These techniques make your templates much more flexible and reduce the amount of code you need to maintain. When you need to change the number of VMs, you simply update the numberOfVMs parameter rather than editing the entire resource list.


Troubleshooting Failed Deployments

When a modification fails, the error messages provided by Azure can sometimes be cryptic. Here is a systematic approach to troubleshooting:

  1. Check the Deployment History: In the Azure Portal, go to the Resource Group, select "Deployments," and click on the failed deployment. The "Error details" tab often contains the specific JSON error code and message.
  2. Validate the Template: Run az deployment group validate before attempting the deployment. This command checks your template against the Azure Resource Manager API without actually creating or modifying resources.
  3. Inspect the Activity Log: If a resource is in a failed state, the Activity Log for that specific resource can provide insights into why it failed to update (e.g., "Policy violation" or "Quota exceeded").
  4. Review API Versions: Ensure that the apiVersion you are using is still supported. Microsoft periodically deprecates older versions.
  5. Look for Locks: If you get an "Access Denied" error, check if there is a "CanNotDelete" or "ReadOnly" resource lock on the resource or the resource group. You must remove the lock before the deployment can proceed.

Integrating with CI/CD Pipelines

To truly professionalize your infrastructure management, you should move away from manual CLI commands and integrate your template modifications into a Continuous Integration/Continuous Deployment (CI/CD) pipeline. Tools like GitHub Actions or Azure DevOps allow you to automate the validation and deployment process.

When you push a change to your Git repository, the pipeline can automatically trigger:

  • Linting: Checking the Bicep code for style and best practices.
  • Validation: Running the what-if command to preview changes.
  • Deployment: Applying the changes to the target environment.

This workflow ensures that every modification is tested, documented, and peer-reviewed before it reaches production. It eliminates the "it worked on my machine" problem and provides a reliable audit trail of every infrastructure change made to your environment.


Key Takeaways

As you continue your journey in managing Azure compute resources, keep these essential principles in mind:

  1. Always use version control: Treat your infrastructure templates exactly like application code. Every change should be committed, commented, and linked to a specific requirement or fix.
  2. Prioritize Bicep over ARM (JSON): Bicep is the industry standard for new development. It is easier to read, write, and maintain, reducing the likelihood of human error during modifications.
  3. Leverage the 'What-If' command: Never deploy a change blindly. Using the what-if analysis provides a safety net that warns you about potential deletions or destructive changes before they happen.
  4. Adopt a modular architecture: Break large templates into smaller, reusable pieces. This makes your infrastructure easier to test and modify without affecting unrelated components.
  5. Understand Deployment Modes: Stick to "Incremental" mode for daily operations. Use "Complete" mode only in controlled, automated environments where you have full governance over the resource group.
  6. Use parameters and variables: Avoid hardcoding values. By using parameter files for different environments, you ensure that your code remains portable and consistent across testing and production.
  7. Automate with CI/CD: Move toward a pipeline-driven deployment model. This removes the reliance on manual CLI access and ensures that all changes follow a standard, repeatable process.

By embracing these practices, you transform infrastructure management from a reactive task into a predictable, automated, and highly reliable component of your technical operations. The ability to safely and effectively modify existing templates is what distinguishes a proficient cloud administrator from a novice, allowing you to scale your environments with confidence as your organizational needs evolve.

Loading...