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

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

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

Building on our storage account foundation from Part 1, we’ll now create a complete web application infrastructure. This part covers virtual networks, app services, SQL databases, and how to properly manage dependencies between resources.

Welcome to Part 2 of our Infrastructure as Code series. Today we’re diving deep into production-ready ARM templates that can handle real-world enterprise scenarios.

What You’ll Build

  • 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

Architecture Overview

Our template will deploy this architecture:

Resource Group
├── Virtual Network
│   ├── Web Subnet
│   ├── Database Subnet
│   └── Network Security Groups
├── App Service Plan
├── App Service (Web App)
├── SQL Server
├── SQL Database
├── Application Insights
└── Storage Account

Production-Ready Template Structure

Here’s our comprehensive ARM template with advanced features:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "projectName": {
      "type": "string",
      "metadata": {
        "description": "Base name for all resources"
      },
      "minLength": 3,
      "maxLength": 10
    },
    "environment": {
      "type": "string",
      "allowedValues": ["dev", "staging", "prod"],
      "metadata": {
        "description": "Environment designation"
      }
    },
    "sqlAdministratorLogin": {
      "type": "string",
      "metadata": {
        "description": "SQL Server administrator login"
      }
    },
    "sqlAdministratorPassword": {
      "type": "securestring",
      "metadata": {
        "description": "SQL Server administrator password"
      }
    },
    "appServicePlanSku": {
      "type": "object",
      "defaultValue": {
        "name": "S1",
        "capacity": 1
      }
    }
  },
  "variables": {
    "naming": {
      "prefix": "[concat(parameters('projectName'), '-', parameters('environment'))]",
      "vnet": "[concat('vnet-', variables('naming').prefix)]",
      "appServicePlan": "[concat('asp-', variables('naming').prefix)]",
      "appService": "[concat('app-', variables('naming').prefix, '-', uniqueString(resourceGroup().id))]",
      "sqlServer": "[concat('sql-', variables('naming').prefix, '-', uniqueString(resourceGroup().id))]"
    },
    "commonTags": {
      "Environment": "[parameters('environment')]",
      "Project": "[parameters('projectName')]",
      "CreatedBy": "ARM-Template"
    }
  }
}

Network Security Implementation

Implementing proper network security with Network Security Groups:

{
  "type": "Microsoft.Network/networkSecurityGroups",
  "apiVersion": "2023-04-01",
  "name": "[concat('nsg-web-', variables('naming').prefix)]",
  "location": "[resourceGroup().location]",
  "tags": "[variables('commonTags')]",
  "properties": {
    "securityRules": [
      {
        "name": "AllowHTTPS",
        "properties": {
          "protocol": "Tcp",
          "sourcePortRange": "*",
          "destinationPortRange": "443",
          "sourceAddressPrefix": "*",
          "destinationAddressPrefix": "*",
          "access": "Allow",
          "priority": 100,
          "direction": "Inbound"
        }
      },
      {
        "name": "AllowHTTP",
        "properties": {
          "protocol": "Tcp",
          "sourcePortRange": "*",
          "destinationPortRange": "80",
          "sourceAddressPrefix": "*",
          "destinationAddressPrefix": "*",
          "access": "Allow",
          "priority": 110,
          "direction": "Inbound"
        }
      }
    ]
  }
}

SQL Server with Security Best Practices

{
  "type": "Microsoft.Sql/servers",
  "apiVersion": "2023-05-01-preview",
  "name": "[variables('naming').sqlServer]",
  "location": "[resourceGroup().location]",
  "tags": "[variables('commonTags')]",
  "properties": {
    "administratorLogin": "[parameters('sqlAdministratorLogin')]",
    "administratorLoginPassword": "[parameters('sqlAdministratorPassword')]",
    "version": "12.0",
    "publicNetworkAccess": "Disabled"
  }
}

App Service with Monitoring

{
  "type": "Microsoft.Web/sites",
  "apiVersion": "2023-01-01",
  "name": "[variables('naming').appService]",
  "location": "[resourceGroup().location]",
  "dependsOn": [
    "[resourceId('Microsoft.Web/serverfarms', variables('naming').appServicePlan)]",
    "[resourceId('Microsoft.Insights/components', variables('naming').appInsights)]"
  ],
  "properties": {
    "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('naming').appServicePlan)]",
    "siteConfig": {
      "appSettings": [
        {
          "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
          "value": "[reference(resourceId('Microsoft.Insights/components', variables('naming').appInsights)).InstrumentationKey]"
        },
        {
          "name": "Environment",
          "value": "[parameters('environment')]"
        }
      ]
    },
    "httpsOnly": true
  }
}

Environment-Specific Parameters

Development Environment (dev.parameters.json):

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "projectName": {
      "value": "webapp"
    },
    "environment": {
      "value": "dev"
    },
    "sqlAdministratorLogin": {
      "value": "sqladmin"
    },
    "sqlAdministratorPassword": {
      "value": "YourSecureP@ssw0rd!"
    },
    "appServicePlanSku": {
      "value": {
        "name": "F1",
        "capacity": 1
      }
    }
  }
}

Production Environment (prod.parameters.json):

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "projectName": {
      "value": "webapp"
    },
    "environment": {
      "value": "prod"
    },
    "sqlAdministratorLogin": {
      "value": "sqladmin"
    },
    "sqlAdministratorPassword": {
      "reference": {
        "keyVault": {
          "id": "/subscriptions/{subscription-id}/resourceGroups/rg-shared/providers/Microsoft.KeyVault/vaults/kv-shared"
        },
        "secretName": "SqlAdminPassword"
      }
    },
    "appServicePlanSku": {
      "value": {
        "name": "P1V2",
        "capacity": 2
      }
    }
  }
}

Deployment Commands

# Deploy to development
az deployment group create \
  --resource-group rg-webapp-dev \
  --template-file webapp-infrastructure.json \
  --parameters @dev.parameters.json

# Deploy to production
az deployment group create \
  --resource-group rg-webapp-prod \
  --template-file webapp-infrastructure.json \
  --parameters @prod.parameters.json

# Validate template before deployment
az deployment group validate \
  --resource-group rg-webapp-dev \
  --template-file webapp-infrastructure.json \
  --parameters @dev.parameters.json

Advanced Concepts Covered

1. Resource Dependencies

  • Explicit dependencies using dependsOn
  • Implicit dependencies through resource references
  • Proper ordering for network security and VNet integration

2. Naming Conventions

  • Structured naming using variables
  • Unique string generation for globally unique resources
  • Environment-specific prefixes

3. Security Best Practices

  • Network Security Groups with least privilege
  • SQL Server with disabled public access
  • HTTPS-only configuration
  • Storage account security settings

4. Configuration Management

  • App Settings and Connection Strings
  • Application Insights integration
  • Environment-specific parameters

Testing Your Deployment

  1. Connectivity Test: Verify app can connect to database
  2. Security Test: Confirm SQL Server is not publicly accessible
  3. Monitoring Test: Check Application Insights is receiving telemetry
  4. Performance Test: Validate App Service Plan scaling

What’s Next?

In Part 3, we’ll explore advanced ARM template patterns including nested templates, conditional deployments, and copy loops for creating multiple similar resources efficiently.

We’ll cover:

  • Nested and linked template patterns
  • Conditional resource deployment
  • Copy loops for arrays and objects
  • Template modularity and reusability
  • Advanced parameter handling

This is Part 2 of our 7-part series on Infrastructure as Code with ARM Templates and Bicep. We’re building production-ready infrastructure templates step by step.

Navigate<< Infrastructure as Code with ARM Templates and Bicep: Part 1 – ARM Template FundamentalsInfrastructure as Code with ARM Templates and Bicep: Part 3 – Advanced ARM Template Patterns >>

Written by:

343 Posts

View All Posts
Follow Me :