---
name: apiops-deployment
description: Guides deployment of Azure API Management infrastructure using Infrastructure as Code (Bicep/Terraform), CI/CD pipelines (GitHub Actions/Azure DevOps), and APIOps workflows. Use when deploying APIM, creating pipelines, or implementing dev→test→prod promotion strategies.
license: MIT
metadata:
  author: Thomas Thornton
  version: "1.0.0"
  last-updated: "2026-01-29"
  azure-services: "api-management, devops, github-actions"
  iac-tools: "bicep, terraform"
---

# APIOps Deployment Skill

Provides Infrastructure as Code (Bicep/Terraform) templates and CI/CD pipeline patterns for deploying Azure API Management following APIOps principles and phased deployment strategies.

## When to Use This Skill

Activate this skill when users need:

- **Infrastructure deployment**: Generate Bicep/Terraform for APIM, Front Door, VNet, supporting services
- **CI/CD pipelines**: Create GitHub Actions or Azure DevOps workflows for APIOps
- **Environment promotion**: Implement dev → test → prod deployment workflows
- **Configuration management**: Environment-specific parameters (dev/test/prod)
- **Disaster recovery**: Backup/restore procedures, blue-green deployments
- **Phased rollout**: Follow deployment planning guide with 7 phases over 17 weeks

---

## Phased Deployment Strategy

**See DEPLOYMENT_PLANNING_GUIDE.md for complete 17-week, 7-phase plan**

### Phase Summary

| Phase | Duration | Focus | Key Deliverables |
|-------|----------|-------|------------------|
| **1** | Week 1-2 | Core Infrastructure | VNet, APIM(dev), Front Door, Key Vault, monitoring |
| **2** | Week 3-4 | Authentication & IAM | Entra ID, Entra External ID, OAuth policies|
| **3** | Week 5-8 | API Onboarding (Dev) | 5 pilot APIs, policies, developer portal |
| **4** | Week 9-10 | Production Infrastructure | APIM(prod) Premium 3u, zone-redundant |
| **5** | Week 11-12 | Production APIs | Migrate 5 pilot APIs, performance testing |
| **6** | Week 13-15 | Governance & Scaling | API Center, additional APIs, APIOps automation |
| **7** | Week 16-17 | Operations Handoff | Runbooks, training, monitoring dashboards |

---

## Important: MCP Tools (ALWAYS Use Before Code Generation)

### 1. Get Azure Verified Modules (AVM)

**BEFORE writing any Bicep**, check for Azure Verified Modules:

```
Tool: azure_bicep-get_azure_verified_module
ResourceType: "Microsoft.ApiManagement/service"
```

**Why**: AVM modules follow Microsoft best practices, reduce code duplication, tested at scale

### 2. Call Deployment Best Practices FIRST

```
Tool: mcp_azure_mcp_get_azure_bestpractices
Intent: "Azure API Management deployment best practices Bicep"
```

### 3. Search Deployment Documentation

```
Tool: mcp_azure_mcp_documentation search
Query: "APIM VNet Internal Bicep deployment"
```

---

## Infrastructure as Code Templates

**See [references/IaC_TEMPLATES.md](references/IaC_TEMPLATES.md) for complete Bicep/Terraform templates**

### Quick Bicep Example: Production APIM

```bicep
param location string = 'uksouth'
param apimName string = 'apim-api-marketplace-prod-uks'
param publisherEmail string = 'admin@example.com'
param publisherName string = 'API Marketplace Team'
param vnetName string = 'vnet-apim-prod-uks'
param subnetName string = 'snet-apim'

resource vnet 'Microsoft.Network/virtualNetworks@2023-04-01' existing = {
  name: vnetName
}

resource apim 'Microsoft.ApiManagement/service@2023-05-01-preview' = {
  name: apimName
  location: location
  sku: {
    name: 'Premium'
    capacity: 3 // Zone-redundant: 3 units across 3 availability zones
  }
  properties: {
    publisherEmail: publisherEmail
    publisherName: publisherName
    virtualNetworkType: 'Internal' // VNet Internal mode
    virtualNetworkConfiguration: {
      subnetResourceId: '${vnet.id}/subnets/${subnetName}'
    }
    customProperties: {
      'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10': 'False'
      'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11': 'False'
      'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Ssl30': 'False'
      'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10': 'False'
      'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11': 'False'
      'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30': 'False'
      'Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TripleDes168': 'False'
    }
    disableGateway: false
  }
  identity: {
    type: 'SystemAssigned' // Managed Identity for Key Vault access
  }
  zones: [
    '1'
    '2'
    '3'
  ] // Zone redundancy for 99.99% SLA
}

output apimId string = apim.id
output managedIdentityPrincipalId string = apim.identity.principalId
```

**Key Configuration**:
- VNet Internal mode (`virtualNetworkType: 'Internal'`)
- Premium SKU with 3 units (zone-redundant)
- TLS 1.2+ only (disable weak protocols)
- Managed Identity (no service accounts)
- Zones `[1, 2, 3]` for 99.99% SLA

---

## APIOps CI/CD Pipeline Pattern

### GitHub Actions Workflow (Recommended)

```yaml
name: APIOps - Deploy APIM Infrastructure

on:
  workflow_dispatch:
  push:
    branches: [main]
    paths:
      - 'infra/**'
      - '.github/workflows/deploy-infra.yml'

env:
  AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
  AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
  
jobs:
  # ===== Dev Environment =====
  deploy-dev:
    runs-on: ubuntu-latest
    environment: development
    steps:
      - uses: actions/checkout@v4
      
      - name: Azure Login
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ env.AZURE_TENANT_ID }}
          subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
      
      - name: Deploy Bicep
        uses: azure/arm-deploy@v2
        with:
          scope: resourcegroup
          resourceGroupName: rg-apim-dev-uks
          template: ./infra/main.bicep
          parameters: ./infra/params/dev.bicepparam
          failOnStdErr: false
      
      - name: Smoke Test
        run: |
          # Verify APIM health endpoint
          curl -f https://management.azure.com/subscriptions/${{ env.AZURE_SUBSCRIPTION_ID }}/resourceGroups/rg-apim-dev-uks/providers/Microsoft.ApiManagement/service/apim-api-marketplace-dev-uks?api-version=2023-05-01-preview \
            -H "Authorization: Bearer $(az account get-access-token --query accessToken -o tsv)"
  
  # ===== Test Environment (After Dev) =====
  deploy-test:
    needs: deploy-dev
    runs-on: ubuntu-latest
    environment: test
    steps:
      # Same steps as dev, use test.bicepparam
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ env.AZURE_TENANT_ID }}
          subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
      - uses: azure/arm-deploy@v2
        with:
          scope: resourcegroup
          resourceGroupName: rg-apim-test-uks
          template: ./infra/main.bicep
          parameters: ./infra/params/test.bicepparam
          failOnStdErr: false
  
  # ===== Production (Manual Approval Required) =====
  deploy-prod:
    needs: deploy-test
    runs-on: ubuntu-latest
    environment:
      name: production
      # GitHub environment protection rule: Require manual approval
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ env.AZURE_TENANT_ID }}
          subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
      
      - name: Deploy Production APIM
        uses: azure/arm-deploy@v2
        with:
          scope: resourcegroup
          resourceGroupName: rg-apim-prod-uks
          template: ./infra/main.bicep
          parameters: ./infra/params/prod.bicepparam
          failOnStdErr: false
      
      - name: Production Smoke Test
        run: |
          # Verify Front Door → APIM connectivity
          curl -f https://api.yourdomain.com/health
      
      - name: Tag Release
        run: |
          git tag -a "apim-prod-$(date +%Y%m%d-%H%M%S)" -m "Production deployment"
          git push origin --tags
```

**Key Features**:
- Dev → Test → Prod progression (serial jobs with `needs`)
- Manual approval for production (`environment: production` with protection rules)
- Smoke tests after each deployment
- Git tags for production releases (audit trail)
- Federated credential authentication (no secrets in code)

---

## Environment-Specific Parameters

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

param location = 'uksouth'
param environment = 'dev'
param apimSku = 'Developer' // £45/month
param apimCapacity = 1
param virtualNetworkType = 'Internal'
param enableFrontDoor = false // Dev: Direct APIM access
param tags = {
  Environment: 'Development'
  ManagedBy: 'IaC'
  CostCenter: 'Engineering'
}
```

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

param location = 'uksouth'
param environment = 'prod'
param apimSku = 'Premium' // £1,944/month (3 units)
param apimCapacity = 3 // Zone-redundant
param virtualNetworkType = 'Internal'
param enableFrontDoor = true // Prod: Front Door + Private Link
param enableApiCenter = true // £135/month
param tags = {
  Environment: 'Production'
  ManagedBy: 'IaC'
  CostCenter: 'Operations'
  Criticality: 'High'
}
```

**Pattern**: Single `main.bicep` template, environment-specific `.bicepparam` files for configuration

---

## Backup & Restore Procedures

### APIM Backup (Automated)

```bash
#!/bin/bash
# backup-apim.sh - Schedule daily via Azure Automation or GitHub Actions

APIM_NAME="apim-api-marketplace-prod-uks"
RESOURCE_GROUP="rg-apim-prod-uks"
STORAGE_ACCOUNT="stapimproduksbkp"
CONTAINER="apim-backups"
BACKUP_NAME="apim-backup-$(date +%Y%m%d-%H%M%S)"

# Trigger APIM backup to Storage Account
az apim backup create \
  --name "$APIM_NAME" \
  --resource-group "$RESOURCE_GROUP" \
  --storage-account-name "$STORAGE_ACCOUNT" \
  --storage-account-container "$CONTAINER" \
  --backup-name "$BACKUP_NAME"

echo "Backup created: $BACKUP_NAME"
```

### APIM Restore (Disaster Recovery)

```bash
#!/bin/bash
# restore-apim.sh - Run during DR scenario

APIM_NAME="apim-api-marketplace-prod-uks"
RESOURCE_GROUP="rg-apim-prod-uks"
STORAGE_ACCOUNT="stapimproduksbkp"
CONTAINER="apim-backups"
BACKUP_NAME="apim-backup-20260128-120000" # Latest successful backup

# Restore APIM from backup
az apim backup restore \
  --name "$APIM_NAME" \
  --resource-group "$RESOURCE_GROUP" \
  --storage-account-name "$STORAGE_ACCOUNT" \
  --storage-account-container "$CONTAINER" \
  --backup-name "$BACKUP_NAME"

echo "APIM restored from backup: $BACKUP_NAME"
```

**Backup Retention**:
- Daily backups for 30 days (Azure Storage lifecycle policy)
- Weekly backups for 12 weeks
- Monthly backups for 12 months
- Geo-redundant storage (GRS) to UK West

---

## Deployment Validation Checklist

Before production deployment, verify:

- **Infrastructure Code**: Bicep/Terraform linted and validated (`az bicep build`, `terraform validate`)
- **Network Configuration**: VNet Internal mode, Private Link configured
- **Security**: TLS 1.2+, weak protocols disabled, Key Vault integration
- **Authentication**: OAuth policies deployed, Entra ID/External ID configured
- **Rate Limiting**: All APIs have rate-limit-by-key policies
- **Monitoring**: Application Insights, diagnostic logs, alerts configured
- **Backups**: Automated daily backups to GRS storage
- **Smoke Tests**: Health endpoints responding, Front Door → APIM connectivity verified
- **Manual Approval**: Production deployment requires human approval (GitHub environment protection)
- **Rollback Plan**: Previous Bicep/Terraform state in Git, APIM backup available

---

## Common Deployment Issues & Solutions

### Issue: APIM VNet Integration Fails

**Error**: `The subnet is not valid for API Management instance`

**Solution**:
1. Ensure subnet size ≥ /27 (32 IPs minimum)
2. Subnet must not have any other resources
3. Delegate subnet to `Microsoft.ApiManagement/service`:
   ```bicep
   delegations: [
     {
       name: 'delegation'
       properties: {
         serviceName: 'Microsoft.ApiManagement/service'
       }
     }
   ]
   ```

---

### Issue: Private Link Connection Pending

**Error**: Front Door → APIM Private Link status `Pending Approval`

**Solution**:
1. Approve Private Endpoint connection in APIM:
   ```bash
   az network private-endpoint-connection approve \
     --resource-name apim-api-marketplace-prod-uks \
     --resource-group rg-apim-prod-uks \
     --name <connection-name> \
     --type Microsoft.ApiManagement/service
   ```
2. Verify status:
   ```bash
   az network private-endpoint-connection list \
     --name apim-api-marketplace-prod-uks \
     --resource-group rg-apim-prod-uks \
     --type Microsoft.ApiManagement/service
   ```

---

### Issue: Deployment Takes 45+ Minutes

**Cause**: APIM Premium with zone redundancy takes 30-60 minutes to deploy

**Solution**: Expected behavior. Use incremental deployments:
- Day 1: Deploy APIM infrastructure (wait 45 min)
- Day 2+: Deploy API configurations (fast, minutes)

**Optimization**: Use `--what-if` flag to preview changes without deploying

---

## Related Skills

- **azure-apim-architecture** - Understand architecture before deploying
- **apim-policy-authoring** - Deploy policies as part of APIOps workflow
- **api-security-review** - Validate security before production deployment

---

## Microsoft Documentation

- [APIOps Guide](https://learn.microsoft.com/azure/architecture/example-scenario/devops/automated-api-deployments-apiops)
- [APIM Bicep Reference](https://learn.microsoft.com/azure/templates/microsoft.apimanagement/service)
- [APIM Backup/Restore](https://learn.microsoft.com/azure/api-management/api-management-howto-disaster-recovery-backup-restore)
- [Azure Verified Modules](https://aka.ms/avm)
- [GitHub Actions for Azure](https://learn.microsoft.com/azure/developer/github/github-actions)

---

**Skill Version**: 1.0  
**Last Updated**: 29 January 2026  
**Primary Knowledge**: DEPLOYMENT_PLANNING_GUIDE.md, [references/IaC_TEMPLATES.md](references/IaC_TEMPLATES.md)