Azure Bicep and AKS expert for infrastructure-as-code deployments, Kubernetes cluster management, networking, identity, monitoring, and Container Apps. Generates modular Bicep templates with environment-specific parameter files.
Install
npx skillscat add anton-abyzov/specweave/plugins-specweave-infrastructure-skills-azure-bicep-aks Install via the SkillsCat registry.
We need to produce a 2-3 sentence plain-text summary, objective, factual, no marketing language, no superlatives, no calls to action. At most 60 words. No quotes, no markdown. Just plain text. We need to explain what the skill does, what problem it solves, when to use it. We must not use bullet points, headings, markdown. Just natural prose. We need 2-3 sentences. Let's craft maybe 3 sentences, under 60 words. Possible summary: "The skill designs and implements Azure infrastructure using Bicep templates, focusing on AKS clusters, Container Apps, networking, identity, and monitoring.
Azure Bicep & AKS Expert
Purpose
Design and implement Azure infrastructure using Bicep templates with a focus on AKS cluster management, Container Apps, networking, identity, and operational excellence. Produces modular, reusable Bicep code following Azure Well-Architected Framework principles.
When to Use
- Writing Bicep templates for Azure resource deployments
- Creating or managing AKS clusters
- Designing Azure networking (VNet, NSG, Application Gateway)
- Implementing Managed Identity and Workload Identity
- Setting up monitoring with Azure Monitor and Container Insights
- Choosing between AKS and Azure Container Apps
- Building CI/CD pipelines with Azure DevOps or GitHub Actions for Azure
Bicep Language
Resource Declarations
// Parameters with decorators
@description('The Azure region for all resources')
param location string = resourceGroup().location
@allowed(['dev', 'staging', 'production'])
param environment string
@minLength(3)
@maxLength(24)
param storageAccountName string
@secure()
param adminPassword string
// Variables
var prefix = 'myapp-${environment}'
var tags = {
Environment: environment
ManagedBy: 'Bicep'
Project: 'MyApp'
}
// Resource declaration
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageAccountName
location: location
tags: tags
kind: 'StorageV2'
sku: {
name: environment == 'production' ? 'Standard_GRS' : 'Standard_LRS'
}
properties: {
minimumTlsVersion: 'TLS1_2'
supportsHttpsTrafficOnly: true
allowBlobPublicAccess: false
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices'
}
}
}
// Outputs
output storageId string = storageAccount.id
output storageName string = storageAccount.nameConditions and Loops
// Conditional deployment
param deployRedis bool = false
resource redis 'Microsoft.Cache/redis@2023-08-01' = if (deployRedis) {
name: '${prefix}-redis'
location: location
properties: {
sku: {
name: environment == 'production' ? 'Premium' : 'Basic'
family: environment == 'production' ? 'P' : 'C'
capacity: environment == 'production' ? 1 : 0
}
enableNonSslPort: false
minimumTlsVersion: '1.2'
}
}
// Loop over array
param appServices array = [
{ name: 'api', sku: 'P1v3' }
{ name: 'web', sku: 'P1v3' }
{ name: 'worker', sku: 'B1' }
]
resource appServicePlans 'Microsoft.Web/serverfarms@2023-12-01' = [
for app in appServices: {
name: '${prefix}-${app.name}-plan'
location: location
tags: tags
sku: {
name: app.sku
}
kind: 'linux'
properties: {
reserved: true
}
}
]
// Loop with index
resource nsgRules 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = [
for (rule, i) in allowedPorts: {
name: 'Allow-${rule.name}'
parent: nsg
properties: {
priority: 100 + (i * 10)
direction: 'Inbound'
access: 'Allow'
protocol: 'Tcp'
sourceAddressPrefix: rule.source
destinationPortRange: string(rule.port)
sourcePortRange: '*'
destinationAddressPrefix: '*'
}
}
]User-Defined Types and Functions
// User-defined type
@export()
type environmentConfig = {
name: 'dev' | 'staging' | 'production'
skuTier: 'Basic' | 'Standard' | 'Premium'
instanceCount: int
enableHA: bool
}
// Using the type
param config environmentConfig = {
name: 'production'
skuTier: 'Premium'
instanceCount: 3
enableHA: true
}
// User-defined function
@export()
func getSubnetId(vnetId string, subnetName string) string =>
'${vnetId}/subnets/${subnetName}'
// Discriminated union type
@export()
type storageConfig = {
kind: 'blob'
containerName: string
} | {
kind: 'table'
tableName: string
}Module Composition
// main.bicep - orchestrator
targetScope = 'subscription'
param location string = 'eastus2'
param environment string
// Resource group
resource rg 'Microsoft.Resources/resourceGroups@2024-03-01' = {
name: 'rg-myapp-${environment}'
location: location
}
// Network module
module network 'modules/network.bicep' = {
name: 'network-deployment'
scope: rg
params: {
location: location
environment: environment
vnetAddressPrefix: '10.0.0.0/16'
}
}
// AKS module (depends on network)
module aks 'modules/aks.bicep' = {
name: 'aks-deployment'
scope: rg
params: {
location: location
environment: environment
subnetId: network.outputs.aksSubnetId
logAnalyticsWorkspaceId: monitoring.outputs.workspaceId
}
}
// Monitoring module
module monitoring 'modules/monitoring.bicep' = {
name: 'monitoring-deployment'
scope: rg
params: {
location: location
environment: environment
}
}Scope Targeting
// Subscription-level deployment
targetScope = 'subscription'
resource rg 'Microsoft.Resources/resourceGroups@2024-03-01' = {
name: 'rg-platform'
location: 'eastus2'
}
// Management group deployment
targetScope = 'managementGroup'
resource policy 'Microsoft.Authorization/policyDefinitions@2023-04-01' = {
name: 'require-tags'
properties: {
policyType: 'Custom'
mode: 'All'
policyRule: {
if: {
field: 'tags.Environment'
exists: 'false'
}
then: {
effect: 'deny'
}
}
}
}What-If Deployment Preview
# Preview changes before deploying
az deployment group what-if \
--resource-group rg-myapp-production \
--template-file main.bicep \
--parameters @parameters/production.bicepparam
# Subscription-level what-if
az deployment sub what-if \
--location eastus2 \
--template-file main.bicep \
--parameters environment=productionAKS (Azure Kubernetes Service)
Cluster Creation with Bicep
// modules/aks.bicep
param location string
param environment string
param subnetId string
param logAnalyticsWorkspaceId string
var clusterName = 'aks-myapp-${environment}'
// Managed Identity for AKS
resource aksIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: '${clusterName}-identity'
location: location
}
// AKS Cluster
resource aksCluster 'Microsoft.ContainerService/managedClusters@2024-02-01' = {
name: clusterName
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${aksIdentity.id}': {}
}
}
sku: {
name: 'Base'
tier: environment == 'production' ? 'Standard' : 'Free'
}
properties: {
dnsPrefix: clusterName
kubernetesVersion: '1.30'
networkProfile: {
networkPlugin: 'azure'
networkPluginMode: 'overlay'
networkPolicy: 'calico'
serviceCidr: '10.1.0.0/16'
dnsServiceIP: '10.1.0.10'
loadBalancerSku: 'standard'
}
// System node pool (required)
agentPoolProfiles: [
{
name: 'system'
count: environment == 'production' ? 3 : 1
vmSize: 'Standard_D4s_v5'
mode: 'System'
osType: 'Linux'
osSKU: 'AzureLinux'
vnetSubnetID: subnetId
enableAutoScaling: true
minCount: environment == 'production' ? 3 : 1
maxCount: environment == 'production' ? 5 : 3
availabilityZones: environment == 'production' ? ['1', '2', '3'] : []
nodeTaints: ['CriticalAddonsOnly=true:NoSchedule']
}
]
// Azure AD integration
aadProfile: {
managed: true
enableAzureRBAC: true
}
// Monitoring
addonProfiles: {
omsagent: {
enabled: true
config: {
logAnalyticsWorkspaceResourceID: logAnalyticsWorkspaceId
}
}
azureKeyvaultSecretsProvider: {
enabled: true
config: {
enableSecretRotation: 'true'
rotationPollInterval: '120s'
}
}
}
// Security
enableRBAC: true
disableLocalAccounts: true
securityProfile: {
defender: {
securityMonitoring: {
enabled: environment == 'production'
}
}
workloadIdentity: {
enabled: true
}
}
oidcIssuerProfile: {
enabled: true
}
autoUpgradeProfile: {
upgradeChannel: 'stable'
nodeOSUpgradeChannel: 'NodeImage'
}
}
}
// User node pool (workloads)
resource userPool 'Microsoft.ContainerService/managedClusters/agentPools@2024-02-01' = {
parent: aksCluster
name: 'workload'
properties: {
mode: 'User'
vmSize: 'Standard_D8s_v5'
osType: 'Linux'
osSKU: 'AzureLinux'
vnetSubnetID: subnetId
enableAutoScaling: true
minCount: 2
maxCount: 20
availabilityZones: environment == 'production' ? ['1', '2', '3'] : []
nodeLabels: {
'workload-type': 'general'
}
}
}
// Spot instance pool (cost optimization)
resource spotPool 'Microsoft.ContainerService/managedClusters/agentPools@2024-02-01' = if (environment != 'production') {
parent: aksCluster
name: 'spot'
properties: {
mode: 'User'
vmSize: 'Standard_D4s_v5'
osType: 'Linux'
scaleSetPriority: 'Spot'
spotMaxPrice: -1 // Pay up to on-demand price
scaleSetEvictionPolicy: 'Delete'
enableAutoScaling: true
minCount: 0
maxCount: 10
nodeTaints: ['kubernetes.azure.com/scalesetpriority=spot:NoSchedule']
nodeLabels: {
'kubernetes.azure.com/scalesetpriority': 'spot'
}
}
}
output clusterName string = aksCluster.name
output clusterFqdn string = aksCluster.properties.fqdn
output oidcIssuerUrl string = aksCluster.properties.oidcIssuerProfile.issuerURLWorkload Identity Setup
// Create a user-assigned managed identity for the workload
resource appIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: 'id-myapp-${environment}'
location: location
}
// Federated credential for Kubernetes service account
resource federatedCredential 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2023-01-31' = {
parent: appIdentity
name: 'myapp-federated'
properties: {
issuer: aksCluster.properties.oidcIssuerProfile.issuerURL
subject: 'system:serviceaccount:production:myapp-sa'
audiences: ['api://AzureADTokenExchange']
}
}# Kubernetes ServiceAccount with Workload Identity annotation
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: production
annotations:
azure.workload.identity/client-id: "<managed-identity-client-id>"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
template:
metadata:
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: myapp-sa
containers:
- name: myapp
image: myacr.azurecr.io/myapp:latestNetworking: Azure CNI Overlay
// modules/network.bicep
param location string
param environment string
param vnetAddressPrefix string
resource vnet 'Microsoft.Network/virtualNetworks@2023-11-01' = {
name: 'vnet-myapp-${environment}'
location: location
properties: {
addressSpace: {
addressPrefixes: [vnetAddressPrefix]
}
subnets: [
{
name: 'snet-aks'
properties: {
addressPrefix: cidrSubnet(vnetAddressPrefix, 20, 0) // /20 for AKS nodes
privateEndpointNetworkPolicies: 'Disabled'
}
}
{
name: 'snet-appgw'
properties: {
addressPrefix: cidrSubnet(vnetAddressPrefix, 24, 16) // /24 for App Gateway
}
}
{
name: 'snet-private-endpoints'
properties: {
addressPrefix: cidrSubnet(vnetAddressPrefix, 24, 17)
privateEndpointNetworkPolicies: 'Enabled'
}
}
]
}
}
output vnetId string = vnet.id
output aksSubnetId string = vnet.properties.subnets[0].id
output appGwSubnetId string = vnet.properties.subnets[1].idIngress: Application Gateway Ingress Controller (AGIC)
resource appGateway 'Microsoft.Network/applicationGateways@2023-11-01' = {
name: 'agw-myapp-${environment}'
location: location
properties: {
sku: {
name: 'WAF_v2'
tier: 'WAF_v2'
}
autoscaleConfiguration: {
minCapacity: 1
maxCapacity: environment == 'production' ? 10 : 2
}
webApplicationFirewallConfiguration: {
enabled: true
firewallMode: 'Prevention'
ruleSetType: 'OWASP'
ruleSetVersion: '3.2'
}
gatewayIPConfigurations: [
{
name: 'appGatewayIpConfig'
properties: {
subnet: { id: appGwSubnetId }
}
}
]
// Frontend, backend, routing configured by AGIC controller
}
}Monitoring: Container Insights + Prometheus
// modules/monitoring.bicep
resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
name: 'log-myapp-${environment}'
location: location
properties: {
sku: { name: 'PerGB2018' }
retentionInDays: environment == 'production' ? 90 : 30
}
}
resource prometheusWorkspace 'Microsoft.Monitor/accounts@2023-04-03' = {
name: 'prometheus-myapp-${environment}'
location: location
}
resource grafanaDashboard 'Microsoft.Dashboard/grafana@2023-09-01' = {
name: 'grafana-myapp-${environment}'
location: location
sku: { name: 'Standard' }
identity: { type: 'SystemAssigned' }
properties: {
grafanaIntegrations: {
azureMonitorWorkspaceIntegrations: [
{ azureMonitorWorkspaceResourceId: prometheusWorkspace.id }
]
}
}
}
output workspaceId string = logAnalytics.idAKS vs Container Apps Decision Framework
┌──────────────────────┬──────────────────────┬──────────────────────┐
│ Criteria │ AKS │ Container Apps │
├──────────────────────┼──────────────────────┼──────────────────────┤
│ K8s expertise needed │ Yes │ No │
│ Custom controllers │ Full control │ Not supported │
│ GPU workloads │ Supported │ Limited │
│ Scale to zero │ With KEDA │ Built-in │
│ Service mesh │ Istio/Linkerd/custom │ Built-in Dapr │
│ Pricing model │ Pay for nodes │ Pay per request │
│ Microservices <10 │ Overkill │ Ideal │
│ Microservices 10+ │ Ideal │ Gets complex │
│ Compliance (PCI/SOC) │ Full control │ Shared responsibility│
│ Stateful workloads │ Full support │ Limited │
│ Startup time │ Days to configure │ Hours │
└──────────────────────┴──────────────────────┴──────────────────────┘
RULE OF THUMB:
- < 10 microservices, no K8s expertise → Container Apps
- Complex orchestration, custom operators → AKS
- Event-driven, scale-to-zero critical → Container Apps
- Strict compliance, full network control → AKSAzure Container Apps
// Container Apps Environment
resource containerAppEnv 'Microsoft.App/managedEnvironments@2024-03-01' = {
name: 'cae-myapp-${environment}'
location: location
properties: {
appLogsConfiguration: {
destination: 'log-analytics'
logAnalyticsConfiguration: {
customerId: logAnalytics.properties.customerId
sharedKey: logAnalytics.listKeys().primarySharedKey
}
}
zoneRedundant: environment == 'production'
workloadProfiles: [
{ name: 'Consumption', workloadProfileType: 'Consumption' }
{
name: 'Dedicated-D4'
workloadProfileType: 'D4'
minimumCount: 1
maximumCount: 5
}
]
}
}
// Container App with Dapr and scaling rules
resource apiApp 'Microsoft.App/containerApps@2024-03-01' = {
name: 'ca-api-${environment}'
location: location
properties: {
managedEnvironmentId: containerAppEnv.id
configuration: {
ingress: {
external: true
targetPort: 8080
transport: 'http2'
corsPolicy: {
allowedOrigins: ['https://myapp.com']
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE']
}
}
dapr: {
enabled: true
appId: 'api'
appPort: 8080
appProtocol: 'http'
}
secrets: [
{
name: 'db-connection'
keyVaultUrl: 'https://kv-myapp.vault.azure.net/secrets/db-connection'
identity: appIdentity.id
}
]
}
template: {
containers: [
{
name: 'api'
image: 'myacr.azurecr.io/api:latest'
resources: {
cpu: json('0.5')
memory: '1Gi'
}
env: [
{ name: 'DB_CONNECTION', secretRef: 'db-connection' }
]
}
]
scale: {
minReplicas: environment == 'production' ? 2 : 0
maxReplicas: 20
rules: [
{
name: 'http-scaling'
http: { metadata: { concurrentRequests: '50' } }
}
{
name: 'queue-scaling'
custom: {
type: 'azure-servicebus'
metadata: {
queueName: 'orders'
messageCount: '10'
}
auth: [
{ secretRef: 'sb-connection', triggerParameter: 'connection' }
]
}
}
]
}
}
}
}Bicep Deployment Patterns
Parameter Files (.bicepparam)
// parameters/production.bicepparam
using '../main.bicep'
param environment = 'production'
param location = 'eastus2'
param deployRedis = true
param appServices = [
{ name: 'api', sku: 'P2v3' }
{ name: 'web', sku: 'P1v3' }
{ name: 'worker', sku: 'P1v3' }
]CI/CD with GitHub Actions
name: Deploy Infrastructure
on:
push:
branches: [main]
paths: ['infra/**']
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: What-If Preview
uses: azure/arm-deploy@v2
with:
scope: subscription
region: eastus2
template: infra/main.bicep
parameters: infra/parameters/production.bicepparam
additionalArguments: --what-if
- name: Deploy
uses: azure/arm-deploy@v2
with:
scope: subscription
region: eastus2
template: infra/main.bicep
parameters: infra/parameters/production.bicepparamCommon Azure Resources Reference
SQL Database
resource sqlServer 'Microsoft.Sql/servers@2023-08-01-preview' = {
name: 'sql-myapp-${environment}'
location: location
properties: {
administratorLogin: 'sqladmin'
administratorLoginPassword: sqlAdminPassword
minimalTlsVersion: '1.2'
publicNetworkAccess: 'Disabled'
}
}
resource sqlDb 'Microsoft.Sql/servers/databases@2023-08-01-preview' = {
parent: sqlServer
name: 'db-myapp'
location: location
sku: {
name: environment == 'production' ? 'P2' : 'S1'
tier: environment == 'production' ? 'Premium' : 'Standard'
}
properties: {
collation: 'SQL_Latin1_General_CP1_CI_AS'
maxSizeBytes: 268435456000 // 250 GB
zoneRedundant: environment == 'production'
}
}Cosmos DB
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2024-02-15-preview' = {
name: 'cosmos-myapp-${environment}'
location: location
kind: 'GlobalDocumentDB'
properties: {
consistencyPolicy: {
defaultConsistencyLevel: 'Session'
}
locations: [
{ locationName: location, failoverPriority: 0, isZoneRedundant: true }
]
databaseAccountOfferType: 'Standard'
enableAutomaticFailover: true
enableMultipleWriteLocations: false
publicNetworkAccess: 'Disabled'
networkAclBypass: 'AzureServices'
capacity: {
totalThroughputLimit: environment == 'production' ? 10000 : 1000
}
}
}