Azure Deployment¶
Enterprise deployment using Azure Container Apps, PostgreSQL Flexible Server, and Container Instances
Architecture Overview¶
┌─────────────────────────────────────────────────────────────┐
│ Azure Front Door │
│ (CDN + WAF + HTTPS) │
└──────────────────┬──────────────────────────────────────────┘
│
┌─────────────┴────────────┐
│ │
▼ ▼
┌─────────────┐ ┌──────────────────┐
│ Blob Storage│ │ Container Apps │
│ (Frontend) │ │ (Backend) │
└─────────────┘ └────────┬─────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────┐ ┌──────────────┐
│ PostgreSQL │ │ Qdrant │ │ Cosmos DB │
│ Flexible │ │(Container│ │ (Cache/NoSQL)│
│ Server │ │Instance) │ │ │
└─────────────┘ └──────────┘ └──────────────┘
Prerequisites¶
1. Azure Account Setup¶
- Azure subscription (free tier available)
- Azure CLI installed:
# Install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Login
az login
# Set subscription
az account set --subscription "YOUR_SUBSCRIPTION_ID"
2. Resource Group Creation¶
3. Required Permissions¶
Your Azure account needs:
- Contributor role on subscription
- Permissions to create:
- Container Apps, Container Instances
- PostgreSQL Flexible Server
- Storage Account, Front Door, Cosmos DB
- Virtual Network, Log Analytics
4. LLM API Key¶
You need an API key for:
- Azure OpenAI (recommended for enterprise)
- OpenAI API
- Other supported providers
Deployment Steps¶
1. Clone Repository¶
2. Configure Variables¶
Edit main.bicep parameters:
param location string = 'australiaeast'
param projectName string = 'greengovrag'
param environment string = 'prod'
param containerImage string = 'ghcr.io/sdp5/green-gov-rag:latest'
3. Set Secrets in Key Vault¶
# Create Key Vault
az keyvault create \
--name greengovrag-kv \
--resource-group greengovrag-rg \
--location australiaeast
# Store OpenAI API key
az keyvault secret set \
--vault-name greengovrag-kv \
--name openai-api-key \
--value "sk-your-key-here"
# Store database password
DB_PASSWORD=$(openssl rand -base64 32)
az keyvault secret set \
--vault-name greengovrag-kv \
--name db-password \
--value "$DB_PASSWORD"
# Optional: Azure OpenAI
az keyvault secret set \
--vault-name greengovrag-kv \
--name azure-openai-key \
--value "your-azure-openai-key"
4. Deploy Infrastructure¶
# Validate template
az deployment group validate \
--resource-group greengovrag-rg \
--template-file main.bicep \
--parameters main.parameters.json
# Deploy
az deployment group create \
--resource-group greengovrag-rg \
--template-file main.bicep \
--parameters main.parameters.json
Deployment takes ~15-20 minutes.
5. Configure Container Apps Environment¶
# Get Container Apps environment name
ENVIRONMENT_NAME=$(az containerapp env list \
--resource-group greengovrag-rg \
--query '[0].name' -o tsv)
# Update backend container
az containerapp update \
--name greengovrag-backend \
--resource-group greengovrag-rg \
--set-env-vars \
LLM_PROVIDER=azure \
LLM_MODEL=gpt-5-mini \
VECTOR_STORE_TYPE=qdrant \
DATABASE_URL=secretref:db-connection-string \
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com \
AZURE_OPENAI_API_KEY=secretref:azure-openai-key
6. Get Deployment Outputs¶
# Get backend URL
az containerapp show \
--name greengovrag-backend \
--resource-group greengovrag-rg \
--query properties.configuration.ingress.fqdn -o tsv
# Get frontend URL
az storage account show \
--name greengovragfrontend \
--resource-group greengovrag-rg \
--query primaryEndpoints.web -o tsv
7. Verify Deployment¶
# Health check
BACKEND_URL=$(az containerapp show \
--name greengovrag-backend \
--resource-group greengovrag-rg \
--query properties.configuration.ingress.fqdn -o tsv)
curl https://$BACKEND_URL/api/health
8. Configure Front Door¶
# Create Front Door profile
az afd profile create \
--profile-name greengovrag-fd \
--resource-group greengovrag-rg \
--sku Standard_AzureFrontDoor
# Add origin for backend
az afd origin create \
--resource-group greengovrag-rg \
--host-name $BACKEND_URL \
--profile-name greengovrag-fd \
--origin-group-name backend-origins \
--origin-name backend \
--priority 1 \
--weight 1000 \
--enabled-state Enabled \
--http-port 80 \
--https-port 443
9. Run ETL Pipeline¶
Using Azure Container Instances:
az container create \
--resource-group greengovrag-rg \
--name greengovrag-etl \
--image ghcr.io/sdp5/green-gov-rag:latest \
--command-line "greengovrag-cli etl run-pipeline" \
--environment-variables \
LLM_PROVIDER=azure \
DATABASE_URL=<connection-string> \
--secure-environment-variables \
OPENAI_API_KEY=<key> \
--restart-policy Never
Check logs:
Configuration¶
Environment Variables¶
Set in Container App:
az containerapp update \
--name greengovrag-backend \
--resource-group greengovrag-rg \
--set-env-vars \
LLM_PROVIDER=azure \
LLM_MODEL=gpt-5-mini \
VECTOR_STORE_TYPE=qdrant \
CLOUD_PROVIDER=azure \
LOG_LEVEL=INFO \
--secrets \
db-password=<value> \
openai-api-key=<value>
Azure OpenAI Configuration¶
Create Azure OpenAI resource:
az cognitiveservices account create \
--name greengovrag-openai \
--resource-group greengovrag-rg \
--kind OpenAI \
--sku S0 \
--location australiaeast
# Deploy model
az cognitiveservices account deployment create \
--name greengovrag-openai \
--resource-group greengovrag-rg \
--deployment-name gpt-5-mini \
--model-name gpt-5-mini \
--model-version "0301" \
--model-format OpenAI \
--sku-capacity 10 \
--sku-name Standard
Configure backend:
OPENAI_ENDPOINT=$(az cognitiveservices account show \
--name greengovrag-openai \
--resource-group greengovrag-rg \
--query properties.endpoint -o tsv)
OPENAI_KEY=$(az cognitiveservices account keys list \
--name greengovrag-openai \
--resource-group greengovrag-rg \
--query key1 -o tsv)
az containerapp update \
--name greengovrag-backend \
--resource-group greengovrag-rg \
--set-env-vars \
LLM_PROVIDER=azure \
AZURE_OPENAI_ENDPOINT=$OPENAI_ENDPOINT \
--replace-secrets \
azure-openai-key=$OPENAI_KEY
Scaling Configuration¶
Container Apps auto-scaling:
az containerapp update \
--name greengovrag-backend \
--resource-group greengovrag-rg \
--min-replicas 1 \
--max-replicas 10 \
--scale-rule-name http-scaling \
--scale-rule-type http \
--scale-rule-http-concurrency 100
Or edit main.bicep:
resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
properties: {
configuration: {
scaling: {
minReplicas: 1
maxReplicas: 10
rules: [
{
name: 'http-rule'
http: {
metadata: {
concurrentRequests: '100'
}
}
}
]
}
}
}
}
Database Scaling¶
Upgrade PostgreSQL tier:
az postgres flexible-server update \
--name greengovrag-db \
--resource-group greengovrag-rg \
--sku-name Standard_B2s # Upgrade from B1ms
CI/CD Pipeline¶
GitHub Actions Setup¶
1. Create Service Principal¶
az ad sp create-for-rbac \
--name greengovrag-github-actions \
--role Contributor \
--scopes /subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/greengovrag-rg \
--sdk-auth
Copy the output JSON to GitHub Secrets as AZURE_CREDENTIALS.
2. Configure GitHub Secrets¶
In repository Settings → Secrets and variables → Actions:
AZURE_CREDENTIALS=<json-from-above>
AZURE_SUBSCRIPTION_ID=<your-subscription-id>
AZURE_RESOURCE_GROUP=greengovrag-rg
CONTAINER_REGISTRY=greengovrag.azurecr.io
OPENAI_API_KEY=sk-...
3. Workflow Files¶
.github/workflows/deploy-azure.yml:
name: Deploy to Azure
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Build and push Docker image
run: |
az acr build \
--registry ${{ secrets.CONTAINER_REGISTRY }} \
--image greengovrag:${{ github.sha }} \
--file backend/Dockerfile .
- name: Deploy to Container Apps
run: |
az containerapp update \
--name greengovrag-backend \
--resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \
--image ${{ secrets.CONTAINER_REGISTRY }}/greengovrag:${{ github.sha }}
Monitoring¶
Application Insights¶
Create Application Insights:
az monitor app-insights component create \
--app greengovrag-insights \
--location australiaeast \
--resource-group greengovrag-rg \
--application-type web
# Get instrumentation key
INSTRUMENTATION_KEY=$(az monitor app-insights component show \
--app greengovrag-insights \
--resource-group greengovrag-rg \
--query instrumentationKey -o tsv)
# Configure backend
az containerapp update \
--name greengovrag-backend \
--resource-group greengovrag-rg \
--set-env-vars \
APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=$INSTRUMENTATION_KEY
View Logs¶
# Container Apps logs
az containerapp logs show \
--name greengovrag-backend \
--resource-group greengovrag-rg \
--follow
# Query Log Analytics
az monitor log-analytics query \
--workspace greengovrag-logs \
--analytics-query "ContainerAppConsoleLogs_CL | where ContainerAppName_s == 'greengovrag-backend' | order by TimeGenerated desc | take 100"
Metrics¶
View in Azure Portal: Monitor → Metrics → Select resource
Key metrics:
- Container Apps: CPU/Memory usage, HTTP requests, Response time
- PostgreSQL: CPU/Memory, Connections, Storage
- Cosmos DB: Request units, Storage, Latency
Alerts¶
Create CPU alert:
az monitor metrics alert create \
--name HighCPUAlert \
--resource-group greengovrag-rg \
--scopes /subscriptions/.../resourceGroups/greengovrag-rg/providers/Microsoft.App/containerApps/greengovrag-backend \
--condition "avg Percentage CPU > 80" \
--window-size 5m \
--evaluation-frequency 1m \
--action email=contact@sundeep.id.au
Backup and Recovery¶
PostgreSQL Automated Backups¶
# Backups are automatic (7-day retention)
# List backups
az postgres flexible-server backup list \
--resource-group greengovrag-rg \
--server-name greengovrag-db
# Restore from backup
az postgres flexible-server restore \
--resource-group greengovrag-rg \
--name greengovrag-db-restored \
--source-server greengovrag-db \
--restore-time "2025-11-15T02:00:00Z"
Qdrant Backups¶
Manual snapshot:
# Connect to container instance
az container exec \
--resource-group greengovrag-rg \
--name greengovrag-qdrant \
--exec-command "/bin/sh"
# Inside container
curl -X POST 'http://localhost:6333/collections/greengovrag/snapshots'
Blob Storage Backups¶
Enable soft delete:
az storage account blob-service-properties update \
--account-name greengovragfrontend \
--resource-group greengovrag-rg \
--enable-delete-retention true \
--delete-retention-days 7
Troubleshooting¶
Issue: Container App Not Starting¶
Check logs:
az containerapp logs show \
--name greengovrag-backend \
--resource-group greengovrag-rg \
--tail 100
Common causes:
- Missing environment variables
- Database connection string incorrect
- Container image not found
Issue: Database Connection Timeout¶
Check firewall rules:
# Allow Azure services
az postgres flexible-server firewall-rule create \
--resource-group greengovrag-rg \
--name greengovrag-db \
--rule-name AllowAzureServices \
--start-ip-address 0.0.0.0 \
--end-ip-address 0.0.0.0
Issue: High Costs¶
Optimization steps:
- Use Burstable tier for PostgreSQL (B1ms instead of GP_Standard_D2s_v3)
- Set Container Apps min replicas to 0 (scale to zero)
- Use Serverless Cosmos DB instead of provisioned throughput
- Enable blob storage lifecycle policies
- Use Azure Front Door Standard instead of Premium
Teardown¶
Delete Resource Group¶
Note: Some resources may have soft-delete:
- Key Vault (can be purged after 90 days or immediately with
--purge) - Blob Storage (soft-deleted blobs retained for 7 days)
Force delete Key Vault:
Next Steps¶
- Production Checklist - Pre-launch verification
- Monitoring Guide - Detailed monitoring setup
- Cloud Comparison - AWS vs Azure comparison
Last Updated: 2025-11-22