# Azure Bicep IaC Patterns Standard

> **Scope:** blazor-azure
> **Layer:** 2 (on keyword)
> **Keywords:** bicep, iac, infrastructure as code, azure, deployment
> **Load When:** bicep or azure infrastructure keywords detected

Infrastructure as Code patterns for Azure using Bicep.

---

## Overview

Bicep provides:
- Declarative Azure resource definitions
- Type safety and IntelliSense
- Modular templates with parameters
- Automatic dependency management
- Preview deployments with `what-if`

**Stack:** Blazor Server + Azure Container Apps + Azure SQL

---

## Core Principles

1. **Modules First**: Break infrastructure into reusable modules
2. **Parameters Over Hardcoding**: Use parameters for environment-specific values
3. **Naming Conventions**: Follow Azure naming conventions (lowercase, hyphens)
4. **Outputs**: Export resource IDs and connection strings
5. **Idempotency**: All deployments should be idempotent

---

## Project Structure

```
infra/
├── main.bicep                # Entry point
├── parameters/
│   ├── dev.bicepparam
│   ├── staging.bicepparam
│   └── prod.bicepparam
└── modules/
    ├── containerapp.bicep
    ├── sql.bicep
    ├── keyvault.bicep
    └── storage.bicep
```

---

## Main Template

```bicep
// infra/main.bicep
targetScope = 'resourceGroup'

@description('Environment name (dev, staging, prod)')
param environment string

@description('Location for all resources')
param location string = resourceGroup().location

@description('Container image tag')
param imageTag string = 'latest'

// Variables
var appName = 'myapp'
var resourcePrefix = '${appName}-${environment}'

// Modules
module containerApp 'modules/containerapp.bicep' = {
  name: '${resourcePrefix}-containerapp-deployment'
  params: {
    name: '${resourcePrefix}-app'
    location: location
    imageTag: imageTag
  }
}

module sql 'modules/sql.bicep' = {
  name: '${resourcePrefix}-sql-deployment'
  params: {
    serverName: '${resourcePrefix}-sql'
    databaseName: '${appName}db'
    location: location
  }
}

module keyVault 'modules/keyvault.bicep' = {
  name: '${resourcePrefix}-kv-deployment'
  params: {
    name: '${resourcePrefix}-kv'
    location: location
  }
}

// Outputs
output containerAppUrl string = containerApp.outputs.fqdn
output sqlConnectionString string = sql.outputs.connectionString
output keyVaultUri string = keyVault.outputs.vaultUri
```

---

## Module Examples

### Container App Module

```bicep
// modules/containerapp.bicep
param name string
param location string
param imageTag string

resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' = {
  name: '${name}-env'
  location: location
  properties: {
    appLogsConfiguration: {
      destination: 'log-analytics'
    }
  }
}

resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
  name: name
  location: location
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    managedEnvironmentId: containerAppEnvironment.id
    configuration: {
      ingress: {
        external: true
        targetPort: 8080
        allowInsecure: false
      }
    }
    template: {
      containers: [
        {
          name: 'api'
          image: 'myregistry.azurecr.io/myapp:${imageTag}'
          resources: {
            cpu: json('0.5')
            memory: '1Gi'
          }
        }
      ]
      scale: {
        minReplicas: 1
        maxReplicas: 10
      }
    }
  }
}

output fqdn string = containerApp.properties.configuration.ingress.fqdn
output principalId string = containerApp.identity.principalId
```

### SQL Database Module

```bicep
// modules/sql.bicep
param serverName string
param databaseName string
param location string

@secure()
param adminPassword string

resource sqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = {
  name: serverName
  location: location
  properties: {
    administratorLogin: 'sqladmin'
    administratorLoginPassword: adminPassword
    version: '12.0'
  }
}

resource sqlDatabase 'Microsoft.Sql/servers/databases@2023-05-01-preview' = {
  parent: sqlServer
  name: databaseName
  location: location
  sku: {
    name: 'Basic'
    tier: 'Basic'
    capacity: 5
  }
  properties: {
    collation: 'SQL_Latin1_General_CP1_CI_AS'
    maxSizeBytes: 2147483648 // 2GB
  }
}

// Allow Azure services
resource firewallRule 'Microsoft.Sql/servers/firewallRules@2023-05-01-preview' = {
  parent: sqlServer
  name: 'AllowAzureServices'
  properties: {
    startIpAddress: '0.0.0.0'
    endIpAddress: '0.0.0.0'
  }
}

output connectionString string = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Initial Catalog=${databaseName};Persist Security Info=False;User ID=sqladmin;Password=${adminPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'
```

### Key Vault Module

```bicep
// modules/keyvault.bicep
param name string
param location string
param principalId string = ''

resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
  name: name
  location: location
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
    tenantId: subscription().tenantId
    enableRbacAuthorization: true
    enabledForDeployment: false
    enabledForDiskEncryption: false
    enabledForTemplateDeployment: false
    enableSoftDelete: true
    softDeleteRetentionInDays: 90
  }
}

// Grant Container App access to secrets
resource kvAccessPolicy 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(principalId)) {
  scope: keyVault
  name: guid(keyVault.id, principalId, 'Key Vault Secrets User')
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') // Key Vault Secrets User
    principalId: principalId
    principalType: 'ServicePrincipal'
  }
}

output vaultUri string = keyVault.properties.vaultUri
```

---

## Parameter Files

```bicep
// parameters/dev.bicepparam
using '../main.bicep'

param environment = 'dev'
param imageTag = 'latest'
param location = 'East US'
```

```bicep
// parameters/prod.bicepparam
using '../main.bicep'

param environment = 'prod'
param imageTag = readEnvironmentVariable('IMAGE_TAG')
param location = 'East US'
```

---

## Deployment Commands

### Deploy to Resource Group

```bash
# Create resource group
az group create --name myapp-dev-rg --location eastus

# Preview deployment (what-if)
az deployment group what-if \
  --resource-group myapp-dev-rg \
  --template-file infra/main.bicep \
  --parameters infra/parameters/dev.bicepparam

# Deploy
az deployment group create \
  --resource-group myapp-dev-rg \
  --template-file infra/main.bicep \
  --parameters infra/parameters/dev.bicepparam
```

### Deploy with CI/CD (GitHub Actions)

```yaml
# .github/workflows/deploy-infra.yml
name: Deploy Infrastructure

on:
  push:
    branches: [main]
    paths:
      - 'infra/**'

env:
  AZURE_RESOURCE_GROUP: myapp-prod-rg

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Azure Login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Deploy Bicep
        uses: azure/arm-deploy@v1
        with:
          resourceGroupName: ${{ env.AZURE_RESOURCE_GROUP }}
          template: ./infra/main.bicep
          parameters: ./infra/parameters/prod.bicepparam imageTag=${{ github.sha }}
```

---

## Best Practices

### Naming Conventions

```bicep
// Azure resource naming: lowercase with hyphens
var storageAccountName = replace('${resourcePrefix}st', '-', '') // Storage accounts: no hyphens
var containerAppName = '${resourcePrefix}-app'  // Other resources: hyphens OK
var keyVaultName = '${resourcePrefix}-kv'
```

### Resource Tags

```bicep
var commonTags = {
  environment: environment
  project: 'myapp'
  managedBy: 'bicep'
  costCenter: 'engineering'
}

resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
  name: name
  location: location
  tags: commonTags
  // ...
}
```

### Conditional Resources

```bicep
@description('Deploy Redis cache?')
param deployRedis bool = false

resource redis 'Microsoft.Cache/redis@2023-08-01' = if (deployRedis) {
  name: '${resourcePrefix}-redis'
  location: location
  properties: {
    sku: {
      name: 'Basic'
      family: 'C'
      capacity: 0
    }
  }
}
```

---

## Troubleshooting

### Common Errors

| Error | Cause | Solution |
|-------|-------|----------|
| Resource name already exists | Name conflict | Use unique `resourcePrefix` with environment |
| Invalid location | Unsupported region | Check `az account list-locations` |
| Missing role assignments | Insufficient permissions | Grant Contributor role to service principal |
| Deployment timeout | Large template | Break into smaller modules |

### Debugging

```bash
# View deployment operations
az deployment group show \
  --resource-group myapp-dev-rg \
  --name main \
  --query properties.outputResources

# View deployment errors
az deployment operation group list \
  --resource-group myapp-dev-rg \
  --name main \
  --query "[?properties.statusMessage.error!=null]"
```

---

## References

- [Bicep Documentation](https://learn.microsoft.com/azure/azure-resource-manager/bicep/)
- [Bicep Playground](https://aka.ms/bicepdemo)
- [Azure Resource Reference](https://learn.microsoft.com/azure/templates/)

---

*MORPH-SPEC by Polymorphism Tech*
