description: Deploy Azure infrastructure, BFF API, Static Web Apps, and configure App Service settings tags: [deploy, azure, infrastructure, bicep, api, app-service, static-web-app, office-addins] techStack: [azure, bicep, dotnet, app-service, swa] appliesTo: ["infrastructure/**", "deploy to azure", "deploy api", "azure deployment", "deploy office addins", "deploy static web app"] alwaysApply: false
Azure Deploy
Category: Operations Last Updated: January 2026
Quick Reference
| Deployment Type | Tool | Skill/Guide |
|---|---|---|
| Azure Infrastructure | Azure CLI + Bicep | This skill |
| BFF API | Azure CLI / GitHub Actions | This skill |
| Office Add-ins / SWA | SWA CLI + PowerShell | This skill |
| Dataverse/PCF | PAC CLI | Use dataverse-deploy skill |
| Solution Import | PAC CLI | Use dataverse-deploy skill |
For Dataverse deployments (PCF, solutions, web resources): Use the dataverse-deploy skill instead.
Prerequisites
Required Tools
# Verify installations
az --version # Azure CLI 2.50+
az bicep version # Bicep CLI 0.39+
dotnet --version # .NET SDK 8.0+
Required Access
| Resource | Access Level |
|---|---|
| Azure Subscription | Contributor role |
| Azure AD | Application Administrator (for app registrations) |
Authentication
# Login to Azure
az login
# Set subscription
az account set --subscription "Spaarke SPE Subscription 1"
# Verify
az account show --query "{Name:name, Id:id}" -o table
Environment Reference
Subscriptions & Resource Groups
| Environment | Subscription | Resource Group |
|---|---|---|
| Dev | Spaarke SPE Subscription 1 | spe-infrastructure-westus2 |
| Prod | (TBD) | rg-spaarke-{customer}-prod |
Azure Resource Endpoints (Dev)
| Service | Endpoint |
|---|---|
| BFF API | https://spe-api-dev-67e2xz.azurewebsites.net |
| Azure OpenAI | https://spaarke-openai-dev.openai.azure.com/ |
| AI Search | https://spaarke-search-dev.search.windows.net/ |
| Document Intelligence | https://westus2.api.cognitive.microsoft.com/ |
| Key Vault | https://sprkspaarkedev-aif-kv.vault.azure.net/ |
| AI Foundry Hub | sprkspaarkedev-aif-hub |
| AI Foundry Project | sprkspaarkedev-aif-proj |
Key Vault Secrets
| Secret Name | Purpose |
|---|---|
openai-api-key | Azure OpenAI API key |
search-admin-key | AI Search admin key |
docintel-api-key | Document Intelligence key |
redis-connection | Redis connection string |
appinsights-key | Application Insights key |
Full environment reference: See docs/guides/AZURE-DEPLOYMENT-GUIDE.md → Environment Configuration
Deployment Type 1: Azure Infrastructure (Bicep)
When to Use
- Creating new Azure resources
- Deploying AI Foundry stack
- Setting up customer environments
Bicep Stack Locations
| Stack | Path | Purpose |
|---|---|---|
| AI Foundry | infrastructure/bicep/stacks/ai-foundry-stack.bicep | AI Hub, Project, Storage, KV |
| Model 1 Shared | infrastructure/bicep/stacks/model1-shared.bicep | Shared infrastructure |
| Model 2 Full | infrastructure/bicep/stacks/model2-full.bicep | Full customer deployment |
Deploy Infrastructure
# Navigate to infrastructure directory
cd infrastructure/bicep
# Deploy AI Foundry stack
az deployment group create `
--resource-group spe-infrastructure-westus2 `
--template-file stacks/ai-foundry-stack.bicep `
--parameters customerId=spaarke environment=dev location=westus2
Verify Deployment
# List deployed resources
az resource list `
--resource-group spe-infrastructure-westus2 `
--output table
Expected Resources (AI Foundry Stack)
| Resource Type | Name Pattern | Purpose |
|---|---|---|
| Storage Account | sprk{customer}{env}aifsa | AI Foundry storage |
| Key Vault | sprk{customer}{env}-aif-kv | Secrets |
| Log Analytics | sprk{customer}{env}-aif-logs | Monitoring |
| App Insights | sprk{customer}{env}-aif-insights | APM |
| ML Workspace (Hub) | sprk{customer}{env}-aif-hub | AI Foundry Hub |
| ML Workspace (Project) | sprk{customer}{env}-aif-proj | AI Foundry Project |
Deployment Type 2: BFF API (App Service)
When to Use
- Deploying API code changes
- Updating App Service configuration
- Publishing new API version
Build API
# Build release
cd src/server/api/Sprk.Bff.Api
dotnet publish -c Release -o ./publish
# Or use publish profile
dotnet publish -c Release /p:PublishProfile=Azure
Deploy to App Service
Option A: Deployment Script (Recommended for Dev)
Use the deployment script - handles build, package, deploy, and verification:
# Full build and deploy (~1 min)
.\scripts\Deploy-BffApi.ps1
# Deploy existing build (faster, ~30 sec)
.\scripts\Deploy-BffApi.ps1 -SkipBuild
What the script does:
- Builds the API in Release mode (unless
-SkipBuild) - Creates deployment zip package
- Deploys via Azure CLI
- Waits for app restart
- Verifies health check passes
Option A-alt: Manual Azure CLI
If you need to run steps manually:
# Build and package
cd src/server/api/Sprk.Bff.Api
dotnet publish -c Release -o ./publish
Compress-Archive -Path './publish/*' -DestinationPath './publish.zip' -Force
# Deploy
az webapp deploy `
--resource-group spe-infrastructure-westus2 `
--name spe-api-dev-67e2xz `
--src-path ./publish.zip `
--type zip
# Verify deployment took effect
curl https://spe-api-dev-67e2xz.azurewebsites.net/healthz
Option B: GitHub Actions (Production Releases)
Use for production deployments - push to master triggers automated deployment:
# Merge to master triggers deploy-staging.yml
git push origin master
# Monitor deployment
gh run list --workflow=deploy-staging.yml --limit 3
gh run watch
See .github/workflows/deploy-staging.yml for configuration.
Option C: Kudu Zip Push Deploy (Troubleshooting Fallback)
Use when CLI deploy reports success but app still runs old code:
- Go to Azure Portal → App Services →
spe-api-dev-67e2xz - Click Advanced Tools → Go (opens Kudu)
- Click Tools → Zip Push Deploy
- Drag and drop
publish.ziponto the page - Verify new entry appears in Deployment Center logs
Note: This is a rare fallback. If CLI deploys consistently fail to update the app, check Deployment Center logs first.
Configure App Settings
# Set individual setting
az webapp config appsettings set `
--resource-group spe-infrastructure-westus2 `
--name spe-api-dev-67e2xz `
--settings Ai__Enabled=true
# Set multiple settings from file
az webapp config appsettings set `
--resource-group spe-infrastructure-westus2 `
--name spe-api-dev-67e2xz `
--settings @appsettings.json
Required App Settings
| Setting | Example Value |
|---|---|
Ai__Enabled | true |
Ai__OpenAiEndpoint | https://spaarke-openai-dev.openai.azure.com/ |
Ai__OpenAiKey | @Microsoft.KeyVault(...) |
Ai__SummarizeModel | gpt-4o-mini |
DocumentIntelligence__Enabled | true |
DocumentIntelligence__AiSearchEndpoint | https://spaarke-search-dev.search.windows.net |
Full settings reference: See docs/guides/AZURE-DEPLOYMENT-GUIDE.md → BFF API App Settings
Deployment Type 3: Office Add-ins (Static Web App)
When to Use
- Deploying Office Add-in changes (Outlook, Word)
- Updating taskpane UI or manifest
- Dev iteration on add-in features
Resource Reference
| Resource | Value |
|---|---|
| Static Web App Name | spaarke-office-addins |
| Resource Group | spe-infrastructure-westus2 |
| URL | https://icy-desert-0bfdbb61e.6.azurestaticapps.net |
| Source Directory | src/client/office-addins |
| Dist Directory | src/client/office-addins/dist |
Dev Deployment (Recommended for Iteration)
Use the deployment script - handles all the complexity:
# Full build and deploy
.\scripts\Deploy-OfficeAddins.ps1
# Skip build (use existing dist)
.\scripts\Deploy-OfficeAddins.ps1 -SkipBuild
# Deploy to preview environment
.\scripts\Deploy-OfficeAddins.ps1 -Environment preview
# Verbose output
.\scripts\Deploy-OfficeAddins.ps1 -Verbose
What the script does:
- Builds webpack production bundle (unless
-SkipBuild) - Gets fresh deployment token from Azure
- Deploys using SWA CLI with spinner workaround
- Verifies deployment and shows manifest version
Manual Deployment (Alternative)
If you need to run steps manually:
# Navigate to office-addins directory
cd src/client/office-addins
# 1. Build production bundle
npx webpack --mode production
# 2. Get fresh deployment token (tokens can expire/rotate)
$token = az staticwebapp secrets list `
--name spaarke-office-addins `
--resource-group spe-infrastructure-westus2 `
--query properties.apiKey -o tsv
# 3. Deploy with output to log file (avoids spinner hanging issue)
Start-Process -FilePath 'powershell.exe' `
-ArgumentList "-NoProfile","-Command","npx swa deploy ./dist --deployment-token $token --env production *> deploy.log" `
-NoNewWindow -Wait
# 4. Check deployment result
Get-Content deploy.log
# 5. Verify deployment (use cache-busting param)
curl "https://icy-desert-0bfdbb61e.6.azurestaticapps.net/outlook/manifest.xml?v=$(Get-Date -Format 'HHmmss')"
Why Direct Deploy Instead of GitHub Actions
| Aspect | Direct Deploy | GitHub Actions |
|---|---|---|
| Speed | ~30 seconds | 2-5 minutes (CI + deploy) |
| Requires PR | No | Yes (merge to master) |
| Best for | Dev iteration, debugging | Production releases |
| Token | Fresh each time | Stored in secrets |
Use Direct Deploy when:
- Testing UI changes
- Debugging add-in issues
- Rapid iteration cycles
- Any work that doesn't need PR review
Use GitHub Actions when:
- Ready for production release
- Changes have been code-reviewed
- Want deployment audit trail
Troubleshooting SWA Deployment
| Issue | Cause | Solution |
|---|---|---|
| Spinner hangs forever | SWA CLI spinner blocks output | Use Start-Process with log redirect |
| Old token rejected | Tokens rotate periodically | Get fresh token via az staticwebapp secrets list |
| Deployment succeeds but old content | CDN caching | Add ?v=timestamp to verify, wait 1-2 min |
| 404 on deployed files | Wrong dist path | Verify ./dist contains expected files |
Manifest Upload After Deploy
After deploying code changes, if manifest changed:
- Download manifest:
https://icy-desert-0bfdbb61e.6.azurestaticapps.net/outlook/manifest.xml - Upload to M365 Admin Center → Integrated Apps → Upload custom app
- Wait 5-15 minutes for propagation to Outlook clients
Note: Manifest version must be incremented (e.g., 1.0.1.0 → 1.0.2.0) for M365 to accept updates.
Deployment Type 4: Key Vault Secrets
Store Secrets
# Store OpenAI API key
az keyvault secret set `
--vault-name sprkspaarkedev-aif-kv `
--name openai-api-key `
--value "YOUR-API-KEY"
# Store from Azure resource (auto-retrieve)
az keyvault secret set `
--vault-name sprkspaarkedev-aif-kv `
--name openai-api-key `
--value "$(az cognitiveservices account keys list --name spaarke-openai-dev --resource-group spe-infrastructure-westus2 --query key1 -o tsv)"
Retrieve Secrets
# Get secret value
az keyvault secret show `
--vault-name sprkspaarkedev-aif-kv `
--name openai-api-key `
--query value -o tsv
Verification Procedures
API Health Check
# Check API is running
curl https://spe-api-dev-67e2xz.azurewebsites.net/healthz
# Expected: "Healthy"
curl https://spe-api-dev-67e2xz.azurewebsites.net/ping
# Expected: "pong"
Azure Resource Verification
# List resources in resource group
az resource list --resource-group spe-infrastructure-westus2 -o table
# Check specific resource
az webapp show --name spe-api-dev-67e2xz --resource-group spe-infrastructure-westus2 --query state
AI Services Verification
# Check OpenAI deployments
az cognitiveservices account deployment list `
--name spaarke-openai-dev `
--resource-group spe-infrastructure-westus2 `
-o table
# Check AI Search index
az search index list `
--service-name spaarke-search-dev `
--resource-group spe-infrastructure-westus2 `
-o table
Error Handling
| Error | Cause | Solution |
|---|---|---|
AuthorizationFailed | Insufficient permissions | Verify Contributor role on subscription |
ResourceNotFound | Wrong resource group or name | Check resource names in Environment Reference |
DeploymentFailed | Bicep template error | Check az deployment group show --name {deployment} for details |
KeyVault access denied | Missing Key Vault policy | Add access policy for your identity |
App Service 503 | API not started | Check Application Insights logs |
App Service 500 after deploy | DI scope mismatch or startup error | Check /healthz response body for error details |
| CLI deploy succeeds but old code runs | Deployment didn't register | Check Deployment Center logs; use Kudu as fallback |
Deployment Not Taking Effect (Rare)
Symptom: az webapp deploy reports success, but the API still runs old code.
Diagnosis:
- Check Deployment Center → Logs in Azure Portal
- If no new entry with current timestamp, deployment didn't register
Solution (in order of preference):
- Restart the App Service - sometimes forces reload of new deployment
- Try alternative CLI command:
az webapp deployment source config-zip - Use Kudu Zip Push Deploy as manual fallback (see Option C above)
- After any method, verify with
curl https://{app}.azurewebsites.net/healthz
Root Cause: This is rare and may indicate Azure-side caching or deployment slot issues. For consistent deployments, use GitHub Actions (Option A).
Check Deployment Logs
# API logs (streaming)
az webapp log tail --name spe-api-dev-67e2xz --resource-group spe-infrastructure-westus2
# Deployment logs
az webapp log deployment show --name spe-api-dev-67e2xz --resource-group spe-infrastructure-westus2
Naming Conventions
| Resource Type | Pattern | Example |
|---|---|---|
| Resource Group | rg-spaarke-{customer}-{env} | rg-spaarke-contoso-prod |
| App Service | sprk-{app}-{env} | sprk-bff-dev |
| Key Vault | sprk{customer}{env}-{purpose}-kv | sprkspaarkedev-aif-kv |
| Storage Account | sprk{customer}{env}sa | sprkspaarkedevsa |
| Azure OpenAI | spaarke-openai-{env} | spaarke-openai-dev |
| AI Search | spaarke-search-{env} | spaarke-search-dev |
Full naming conventions: See docs/architecture/AZURE-RESOURCE-NAMING-CONVENTION.md
Related Skills
| Skill | Use For |
|---|---|
dataverse-deploy | Dataverse solutions, PCF controls, web resources |
ribbon-edit | Ribbon customizations (uses solution export/import) |
ci-cd | CI/CD pipeline status and automated deployment workflows |
CI/CD Integration
Automated Deployments via GitHub Actions
This skill documents manual Azure deployments. For automated deployments, see the GitHub Actions workflows:
| Workflow | Trigger | What It Deploys |
|---|---|---|
deploy-staging.yml | Auto (after CI passes on master) | API to staging App Service |
deploy-to-azure.yml | Manual trigger | Infrastructure (Bicep) + API to production |
When to Use Manual vs Automated
| Scenario | Use |
|---|---|
| Regular code deployments | Automated (deploy-staging.yml after merge to master) |
| Production deployment | Automated (deploy-to-azure.yml manual trigger) |
| Emergency hotfix | Manual deployment (this skill) |
| Infrastructure changes only | Manual deployment (this skill) |
| Debugging deployment issues | Manual deployment (this skill) |
Trigger Automated Deployment
# Trigger production deployment manually
gh workflow run deploy-to-azure.yml
# Monitor deployment progress
gh run watch
# View deployment status
gh run list --workflow=deploy-to-azure.yml --limit 5
Check Deployment Status
# View recent deployments
gh run list --workflow=deploy-to-azure.yml
# View specific run details
gh run view {run-id}
# Download deployment artifacts
gh run download {run-id}
Related Documentation
| Document | Purpose |
|---|---|
docs/guides/AZURE-DEPLOYMENT-GUIDE.md | Comprehensive deployment reference |
docs/guides/CUSTOMER-DEPLOYMENT-GUIDE.md | Customer-facing setup guide |
docs/architecture/AZURE-RESOURCE-NAMING-CONVENTION.md | Naming standards |
Tips for AI
- Avoid discovery queries - Use endpoint values from Environment Reference above
- Operational commands OK - Deployments, secret management, configuration are permitted
- For Dataverse - Always use
dataverse-deployskill instead of this one - Check health first - Before troubleshooting, verify
/healthzendpoint - Key Vault references - Use
@Microsoft.KeyVault(SecretUri=...)syntax in App Settings - Dev deploys - Use deployment scripts for quick iteration; GitHub Actions for production releases
- Verify deployments - After manual deploy, check
/healthzand Deployment Center logs - If CLI deploy fails silently - Try restart first, then Kudu as last resort
Deployment Scripts (Preferred for Dev)
| Component | Script | Usage |
|---|---|---|
| BFF API | .\scripts\Deploy-BffApi.ps1 | Full build+deploy in ~1 min |
| Office Add-ins | .\scripts\Deploy-OfficeAddins.ps1 | Full build+deploy in ~30 sec |
Both scripts support -SkipBuild flag to deploy existing builds faster.
- 500 errors after deploy - Check
/healthzresponse body for DI scope or startup errors
Office Add-ins Specific
- Use the script - Always use
.\scripts\Deploy-OfficeAddins.ps1for SWA deployments - SWA CLI spinner issue - The script handles this; don't run
npx swa deploydirectly from bash - Fresh tokens - The script gets a fresh token each time; manual deploys should do the same
- CDN caching - After deploy, use
?v=timestampparameter to verify new content - Manifest changes - After deploying manifest changes, remind user to upload to M365 Admin Center
- Version bumping - Manifest version must be incremented for M365 to accept updates