Infrastructure as Code with ARM Templates and Bicep: Part 1 – ARM Template Fundamentals

Infrastructure as Code with ARM Templates and Bicep: Part 1 – ARM Template Fundamentals

This entry is part 1 of 7 in the series Infrastructure as Code templates using ARM and Bicep

Infrastructure as Code (IaC) has revolutionized how we deploy and manage cloud resources. Instead of clicking through Azure portal screens, we can define our entire infrastructure in declarative templates that are version-controlled, repeatable, and auditable.

This is the first part of our comprehensive 7-part series on Infrastructure as Code using Azure Resource Manager (ARM) templates and Bicep. In this series, we’ll take you from basic concepts to enterprise-scale deployment patterns.

What You’ll Learn in This Series

  • Part 1: ARM template anatomy and core concepts
  • Part 2: Building production-ready infrastructure
  • Part 3: Advanced ARM template patterns
  • Part 4: Introduction to Bicep – Modern Infrastructure as Code
  • Part 5: Advanced Bicep patterns and enterprise modules
  • Part 6: CI/CD integration and deployment strategies
  • Part 7: Enterprise governance and compliance automation

ARM Template Fundamentals

Azure Resource Manager (ARM) templates are Azure’s native Infrastructure as Code solution, using JSON to describe what resources you want and how they should be configured.

ARM Template Structure

Every ARM template follows this basic structure:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {},
  "variables": {},
  "resources": [],
  "outputs": {}
}

Your First ARM Template: Storage Account

Let’s create a template that deploys a storage account with configurable settings:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": {
      "type": "string",
      "metadata": {
        "description": "Name of the storage account"
      },
      "minLength": 3,
      "maxLength": 24
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]",
      "metadata": {
        "description": "Location for the storage account"
      }
    },
    "storageAccountType": {
      "type": "string",
      "defaultValue": "Standard_LRS",
      "allowedValues": [
        "Standard_LRS",
        "Standard_GRS",
        "Standard_RAGRS",
        "Premium_LRS"
      ],
      "metadata": {
        "description": "Storage account replication type"
      }
    },
    "environment": {
      "type": "string",
      "allowedValues": [
        "dev",
        "staging",
        "prod"
      ],
      "metadata": {
        "description": "Environment designation"
      }
    }
  },
  "variables": {
    "storageAccountNameFormatted": "[toLower(parameters('storageAccountName'))]",
    "commonTags": {
      "Environment": "[parameters('environment')]",
      "Project": "IaC-Demo",
      "CreatedBy": "ARM-Template"
    }
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2023-01-01",
      "name": "[variables('storageAccountNameFormatted')]",
      "location": "[parameters('location')]",
      "tags": "[variables('commonTags')]",
      "sku": {
        "name": "[parameters('storageAccountType')]"
      },
      "kind": "StorageV2",
      "properties": {
        "accessTier": "Hot",
        "allowBlobPublicAccess": false,
        "allowSharedKeyAccess": true,
        "encryption": {
          "services": {
            "blob": {
              "enabled": true
            },
            "file": {
              "enabled": true
            }
          },
          "keySource": "Microsoft.Storage"
        },
        "networkAcls": {
          "defaultAction": "Allow"
        }
      }
    }
  ],
  "outputs": {
    "storageAccountId": {
      "type": "string",
      "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountNameFormatted'))]"
    },
    "storageAccountName": {
      "type": "string",
      "value": "[variables('storageAccountNameFormatted')]"
    },
    "primaryEndpoints": {
      "type": "object",
      "value": "[reference(variables('storageAccountNameFormatted')).primaryEndpoints]"
    }
  }
}

Parameter File Example

Create a separate parameters file for environment-specific values:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "storageAccountName": {
      "value": "mystoragedev001"
    },
    "storageAccountType": {
      "value": "Standard_LRS"
    },
    "environment": {
      "value": "dev"
    }
  }
}

Deployment Commands

Using Azure CLI:

# Create resource group
az group create --name rg-iac-demo --location eastus

# Deploy template
az deployment group create \
  --resource-group rg-iac-demo \
  --template-file storage-account.json \
  --parameters @storage-account.parameters.json

Using PowerShell:

# Create resource group
New-AzResourceGroup -Name "rg-iac-demo" -Location "East US"

# Deploy template
New-AzResourceGroupDeployment `
  -ResourceGroupName "rg-iac-demo" `
  -TemplateFile "storage-account.json" `
  -TemplateParameterFile "storage-account.parameters.json"

Key Concepts Explained

  • Parameters: Input values that make templates reusable across environments
  • Variables: Calculated values that simplify complex expressions
  • Functions: Built-in functions like resourceGroup(), toLower(), reference()
  • Outputs: Return values from deployment for use in other templates or scripts

Best Practices Introduced

  1. Naming Conventions: Use consistent, descriptive names
  2. Parameter Validation: Use minLength, maxLength, and allowedValues
  3. Default Values: Provide sensible defaults where appropriate
  4. Tags: Implement consistent tagging strategy
  5. Security: Disable public blob access by default

What’s Next?

In Part 2, we’ll expand this foundation to create a complete web application infrastructure including virtual networks, app services, and databases, while exploring resource dependencies and advanced ARM template features.

We’ll build a production-ready template that includes:

  • Virtual Network with multiple subnets
  • App Service with custom domain support
  • Azure SQL Database with security configurations
  • Application Insights for monitoring
  • Proper resource dependencies and ordering

Stay tuned for the next part where we’ll dive deeper into building enterprise-ready infrastructure templates!


This is Part 1 of our 7-part series on Infrastructure as Code with ARM Templates and Bicep. Follow along as we build from basic concepts to enterprise-scale deployment patterns.

NavigateInfrastructure as Code with ARM Templates and Bicep: Part 2 – Building Production-Ready Infrastructure >>

Written by:

387 Posts

View All Posts
Follow Me :