# Azure Deploy

> Deploy Azure infrastructure, BFF API, Static Web Apps, and configure App Service settings

- Skill: `majiayu000/azure-deploy-3` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add majiayu000/azure-deploy-3`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/azure-deploy-3/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/majiayu000/azure-deploy-3

---


# 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

```powershell
# 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

```powershell
# 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

```powershell
# 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

```powershell
# 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

```powershell
# 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:

```powershell
# 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:**
1. Builds the API in Release mode (unless `-SkipBuild`)
2. Creates deployment zip package
3. Deploys via Azure CLI
4. Waits for app restart
5. Verifies health check passes

#### Option A-alt: Manual Azure CLI

If you need to run steps manually:

```powershell
# 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:

```powershell
# 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:**

1. Go to **Azure Portal** → **App Services** → `spe-api-dev-67e2xz`
2. Click **Advanced Tools** → **Go** (opens Kudu)
3. Click **Tools** → **Zip Push Deploy**
4. Drag and drop `publish.zip` onto the page
5. 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

```powershell
# 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:

```powershell
# 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:**
1. Builds webpack production bundle (unless `-SkipBuild`)
2. Gets fresh deployment token from Azure
3. Deploys using SWA CLI with spinner workaround
4. Verifies deployment and shows manifest version

### Manual Deployment (Alternative)

If you need to run steps manually:

```powershell
# 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:

1. Download manifest: `https://icy-desert-0bfdbb61e.6.azurestaticapps.net/outlook/manifest.xml`
2. Upload to M365 Admin Center → Integrated Apps → Upload custom app
3. 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

```powershell
# 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

```powershell
# 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

```powershell
# 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

```powershell
# 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

```powershell
# 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**:
1. Check **Deployment Center** → **Logs** in Azure Portal
2. If no new entry with current timestamp, deployment didn't register

**Solution** (in order of preference):
1. **Restart the App Service** - sometimes forces reload of new deployment
2. **Try alternative CLI command**: `az webapp deployment source config-zip`
3. **Use Kudu Zip Push Deploy** as manual fallback (see Option C above)
4. 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

```powershell
# 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

```powershell
# 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

```powershell
# 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-deploy` skill instead of this one
- **Check health first** - Before troubleshooting, verify `/healthz` endpoint
- **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 `/healthz` and 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 `/healthz` response body for DI scope or startup errors

### Office Add-ins Specific

- **Use the script** - Always use `.\scripts\Deploy-OfficeAddins.ps1` for SWA deployments
- **SWA CLI spinner issue** - The script handles this; don't run `npx swa deploy` directly from bash
- **Fresh tokens** - The script gets a fresh token each time; manual deploys should do the same
- **CDN caching** - After deploy, use `?v=timestamp` parameter 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

