End-to-End Azure Deployment with IaC
Architecture Overview
This guide walks through deploying a production-ready Azure environment:
- Virtual Network with subnets for application, data, and management tiers
- API Management instance for API gateway
- Logic App (Standard) with VNET integration
- Key Vault for secrets management
- Application Insights for monitoring
Project Structure
infra/
├── main.bicep # or main.tf
├── parameters/
│ ├── dev.json # Dev environment parameters
│ └── prod.json # Prod environment parameters
├── modules/
│ ├── networking/ # VNET, subnets, NSGs
│ ├── apim/ # API Management
│ ├── logic-app/ # Logic App Standard
│ ├── key-vault/ # Key Vault
│ └── monitoring/ # App Insights + Log Analytics
└── pipeline.yml # Azure DevOps pipeline
Networking Module (Bicep)
param location string
param environment string
param vnetAddressPrefix string = '10.0.0.0/16'
resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = {
name: 'vnet-${environment}'
location: location
properties: {
addressSpace: {
addressPrefixes: [
vnetAddressPrefix
]
}
subnets: [
{
name: 'snet-app'
properties: {
addressPrefix: '10.0.1.0/24'
delegations: [
{
name: 'delegation'
properties: {
serviceName: 'Microsoft.Web/serverFarms'
}
}
]
}
}
{
name: 'snet-apim'
properties: {
addressPrefix: '10.0.2.0/24'
}
}
{
name: 'snet-data'
properties: {
addressPrefix: '10.0.3.0/24'
serviceEndpoints: [
{ service: 'Microsoft.Storage' }
{ service: 'Microsoft.KeyVault' }
]
}
}
]
}
}
output vnetId string = vnet.id
output appSubnetId string = vnet.properties.subnets[0].id
output apimSubnetId string = vnet.properties.subnets[1].id
Deployment Pipeline
trigger:
branches:
include:
- main
paths:
include:
- infra/*
parameters:
- name: environment
displayName: 'Environment'
type: string
default: 'dev'
values:
- dev
- prod
stages:
- stage: Validate
jobs:
- job: ValidateTemplate
steps:
- task: AzureCLI@2
displayName: 'Validate Bicep'
inputs:
azureSubscription: 'service-connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az deployment group validate \
--resource-group rg-${{ parameters.environment }} \
--template-file infra/main.bicep \
--parameters @infra/parameters/${{ parameters.environment }}.json
- stage: Deploy
dependsOn: Validate
jobs:
- deployment: DeployInfra
environment: 'infra-${{ parameters.environment }}'
strategy:
runOnce:
deploy:
steps:
- checkout: self
- task: AzureCLI@2
displayName: 'Deploy Infrastructure'
inputs:
azureSubscription: 'service-connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az deployment group create \
--resource-group rg-${{ parameters.environment }} \
--template-file infra/main.bicep \
--parameters @infra/parameters/${{ parameters.environment }}.json
Environment Separation
Dev
Development environment — cost-optimised
APIM TierDeveloper
Logic App PlanWS1
Key VaultSoft delete
VNETBasic NSGs
MonitoringBasic
Prod
Production environment — fully hardened
APIM TierStandard
Logic App PlanWS2
Key VaultSoft delete + Purge protection
VNETFull NSG rules + DDoS protection
MonitoringFull diagnostics + alerts
Deployment Order
Resources must be deployed in dependency order:
- Resource Groups (if not pre-existing)
- Networking — VNET, subnets, NSGs
- Monitoring — Log Analytics workspace, Application Insights
- Key Vault — With access policies for other services
- APIM — With VNET integration
- Logic App — With VNET integration and Key Vault references
Best Practices
- Use What-If / Plan before every deployment
- Parameterise per environment — never hardcode environment-specific values
- Enable diagnostic settings on all resources
- Use Managed Identities between services
- Implement naming conventions consistently (e.g.,
{type}-{workload}-{environment}-{region}) - Store secrets in Key Vault and reference via Bicep/Terraform