This guide explains how to deploy Template Doctor to Azure using Azure Developer CLI (azd).
The deployment provisions:
- Azure Cosmos DB (MongoDB API, Serverless) - Database for storing analysis results
- Azure Container Registry - Hosts Docker images
- Azure Container Apps Environment - Managed Kubernetes-like environment
- Azure Container App - Runs the Template Doctor combined container
- Log Analytics Workspace - Monitoring and diagnostics
-
Install Azure Developer CLI (azd):
# macOS/Linux curl -fsSL https://aka.ms/install-azd.sh | bash # Windows (PowerShell) powershell -ex AllSigned -c "Invoke-RestMethod 'https://aka.ms/install-azd.sh' | Invoke-Expression"
-
Install Azure CLI:
# macOS brew install azure-cli # Windows winget install Microsoft.AzureCLI # Linux curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
-
Install Docker:
- Docker Desktop: https://www.docker.com/products/docker-desktop
-
GitHub Configuration:
A. OAuth App (for user login):
- Create OAuth app at https://github.com/settings/developers
- Set Authorization callback URL to:
https://<your-app-url>/callback.html - Note down Client ID and Client Secret
B. Personal Access Token (for repository operations):
- Create token at https://github.com/settings/tokens/new
- Required scopes:
- ✅
repo- Full control of private repositories (includes cloning, creating PRs) - ✅
workflow- Update GitHub Action workflows - ✅
read:org- Read org membership (for SAML/SSO handling)
- ✅
- Note down the token (you won't see it again!)
azd auth loginEdit .env in the repository root and add your GitHub credentials:
# Edit .env file
GITHUB_CLIENT_ID=Iv1.xxxxxxxxxxxxxxxx
GITHUB_CLIENT_SECRET=your-oauth-client-secret-here
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxWhat each token is used for:
- GITHUB_CLIENT_ID/SECRET: OAuth login for users to authenticate
- GITHUB_TOKEN: Backend operations:
- 🔍 Cloning repositories for analysis
- 💾 Creating PRs to save results
- 🚀 Triggering workflow runs
- 📊 Accessing repository metadata
- 🔐 Handling SAML/SSO repositories
.env BEFORE running azd provision. The values will be automatically loaded by azd.
Alternative: You can also set them via azd env set after initialization:
azd env set GITHUB_CLIENT_ID "your-value"
azd env set GITHUB_CLIENT_SECRET "your-value"
azd env set GITHUB_TOKEN "your-value"# Initialize azd environment (creates .azure/<env>/ directory)
azd init
# Or use a specific environment name
azd env new production
# Provision infrastructure (reads secrets from .env automatically)
azd provision# Build Docker image and deploy to Container Apps
azd deployThis will:
- Build the Docker image from
Dockerfile.combined - Push image to Azure Container Registry
- Deploy to Azure Container Apps
- Update environment with connection strings
# Show service endpoints
azd show
# Or get URL directly
azd env get-values | grep SERVICE_WEB_URIVisit the URL to access Template Doctor!
After azd provision, these are automatically set in .azure/<environment>/.env:
AZURE_LOCATION=eastus2
AZURE_SUBSCRIPTION_ID=<your-sub-id>
AZURE_RESOURCE_GROUP=rg-production
AZURE_CONTAINER_REGISTRY_ENDPOINT=cr<unique>.azurecr.io
SERVICE_WEB_URI=https://<app-name>.azurecontainerapps.io
MONGODB_URI=mongodb://<cosmos-account>.mongo.cosmos.azure.com:10255/...
COSMOS_ENDPOINT=https://<cosmos-account>.documents.azure.com:443/You must manually set (before deploying):
# GitHub OAuth (for user authentication)
GITHUB_CLIENT_ID=<from-github-oauth-app>
GITHUB_CLIENT_SECRET=<from-github-oauth-app>
# GitHub Personal Access Token (for repository operations)
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx📖 Detailed token setup guide: See GITHUB_TOKEN_SETUP.md for comprehensive instructions on creating and configuring GitHub credentials.
After deployment, update your GitHub OAuth app:
- Go to https://github.com/settings/developers
- Select your OAuth app
- Update Authorization callback URL to:
Get FQDN from:
https://<your-app-fqdn>/callback.htmlazd env get-values | grep SERVICE_WEB_URI
# Stream logs from Container App
az containerapp logs show \
--name ca-web-<unique-id> \
--resource-group rg-<environment> \
--follow# Set new environment variable
azd env set MY_VAR "my-value"
# Re-deploy to apply changes
azd deploy# Update main.bicep to change minReplicas/maxReplicas
# Then re-provision
azd provision# Get connection string
azd env get-values | grep MONGODB_URI
# Or via Azure CLI
az cosmosdb keys list \
--name cosmos-<unique-id> \
--resource-group rg-<environment> \
--type connection-strings-
Get connection string:
azd env get-values | grep MONGODB_URI -
Open MongoDB Compass and paste the connection string
-
Database:
template-doctor -
Collections:
repos,analysis
Logs are sent to Log Analytics workspace automatically.
Query logs:
az monitor log-analytics query \
--workspace <workspace-id> \
--analytics-query "ContainerAppConsoleLogs_CL | where TimeGenerated > ago(1h) | project TimeGenerated, Log_s | order by TimeGenerated desc"View in Azure Portal:
- Navigate to Cosmos DB account
- Monitoring → Metrics
- Key metrics:
- Total Request Units
- Total Requests
- Throttled Requests (429s)
Set up alerts:
az monitor metrics alert create \
--name "High RU Usage" \
--resource-group rg-<environment> \
--scopes /subscriptions/<sub-id>/resourceGroups/rg-<environment>/providers/Microsoft.DocumentDB/databaseAccounts/cosmos-<unique-id> \
--condition "avg TotalRequestUnits > 1000" \
--description "Alert when RU usage is high"- No minimum cost - Pay only for RUs consumed
- Best for: Dev/test, variable workloads
- Limits: 1M RU/s max, 50 GB storage
Typical costs (approximate):
- 1M read operations: ~$0.40
- 1M write operations: ~$2.00
- Storage: ~$0.25/GB/month
Free tier includes:
- 180,000 vCPU-seconds
- 360,000 GiB-seconds
- 2 million requests
Current configuration:
- CPU: 0.5 cores
- Memory: 1 GiB
- Min replicas: 1
- Max replicas: 3
For low-medium traffic (~10K analyses/month):
| Resource | Cost |
|---|---|
| Cosmos DB (Serverless) | $5-20 |
| Container Apps | $0-10 (within free tier) |
| Container Registry | $5 (Basic tier) |
| Log Analytics | $0-5 (first 5GB free) |
| Total | $10-40/month |
The first deployment uses a placeholder image. If the container fails to start:
-
Check Container App logs:
az containerapp logs show --name ca-web-<unique-id> --resource-group rg-<environment> --follow
-
Verify image was pushed:
az acr repository list --name cr<unique-id>
-
Re-deploy:
azd deploy
Update GitHub OAuth app callback URL to match your deployed app:
https://<app-fqdn>/callback.html
-
Verify Cosmos DB connection string:
azd env get-values | grep MONGODB_URI -
Check Container App environment variables:
az containerapp show --name ca-web-<unique-id> --resource-group rg-<environment> --query properties.template.containers[0].env
-
Test connection from local MongoDB Compass
If you see 429 errors or unexpected costs:
-
Check RU consumption:
- Azure Portal → Cosmos DB → Metrics → Total Request Units
-
Add indexes:
db.repos.createIndex({ 'latestAnalysis.scanDate': -1 }); db.analysis.createIndex({ repoUrl: 1, scanDate: -1 });
-
Optimize queries in application code
Create .github/workflows/azure-deploy.yml:
name: Deploy to Azure
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install azd
uses: Azure/setup-azd@v1.0.0
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy with azd
run: |
azd env refresh -e production --no-prompt
azd env set GITHUB_CLIENT_ID "${{ secrets.GH_CLIENT_ID }}"
azd env set GITHUB_CLIENT_SECRET "${{ secrets.GH_CLIENT_SECRET }}"
azd deploy --no-prompt
env:
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_LOCATION: eastus2Pipeline is available at .azdo/pipelines/azure-dev.yml
Setup:
- Create variable group
template-doctor-secrets - Add variables:
AZURE_SUBSCRIPTION_IDGITHUB_CLIENT_IDGITHUB_CLIENT_SECRET
- Create service connection to Azure
- Run pipeline
# Delete entire environment (resource group + all resources)
azd down
# Or delete specific environment
azd down --purge --force# Remove just the container app
az containerapp delete --name ca-web-<unique-id> --resource-group rg-<environment>-
Add custom domain to Container App:
az containerapp hostname add \ --name ca-web-<unique-id> \ --resource-group rg-<environment> \ --hostname app.yourdomain.com
-
Add DNS CNAME record:
app.yourdomain.com -> <app-fqdn> -
Update GitHub OAuth callback URL
Current setup uses connection string. To use Managed Identity:
- Uncomment
principalIdininfra/main.bicep - Update
database.tsto useDefaultAzureCredential - Re-provision:
azd provision
See database.bicep for role assignment logic.
# Create environments
azd env new dev
azd env new staging
azd env new production
# Switch between environments
azd env select dev
azd deploy
azd env select production
azd deploy- Set up monitoring alerts
- Configure auto-scaling rules
- Add custom domain
- Set up CI/CD pipeline
- Configure backup retention
- Review security recommendations
- Azure Developer CLI Documentation
- Azure Container Apps Documentation
- Cosmos DB for MongoDB API
- DATA_LAYER.md - Database setup and migration