Awesome-omni-skill azure-data-api-builder
Deploy Data API Builder (DAB) to Azure Container Apps with Azure SQL, Azure Container Registry (ACR), and Azure Developer CLI (azd). Produces Bicep templates, Dockerfile, and azure.yaml. Use when asked to deploy DAB to Azure, create Bicep for DAB, or set up cloud API hosting.
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/devops/azure-data-api-builder" ~/.claude/skills/diegosouzapw-awesome-omni-skill-azure-data-api-builder && rm -rf "$T"
skills/devops/azure-data-api-builder/SKILL.mdData API Builder — Azure Deployment
This skill covers deploying Data API Builder (DAB) to Azure Container Apps with Azure SQL, using Azure Developer CLI (azd) for orchestration.
For local development, see the
aspire-data-api-builder or docker-data-api-builder skill.
Core Mental Model
- Always build a custom Docker image with
baked in — never use volume mounts in Azure.dab-config.json - azd orchestrates the full deployment: Bicep provisions resources, a post-provision hook deploys content.
- ACR hosts all custom images (DAB, web, Inspector).
- Connection strings use secrets — never plain-text env vars for credentials.
is required in all Azure SQL connection strings.TrustServerCertificate=true
Anti-Patterns (NEVER DO)
- ❌ Azure Files share mount for
dab-config.json - ❌ Azure Storage Account for config files
- ❌ Volume mounts in Azure Container Apps for DAB config
- ❌ Any external file storage for DAB config in cloud
- ✅ Custom Docker image with config embedded via
COPY
Why custom image? Config is versioned with the image — immutable, reproducible, no extra infrastructure, faster startup, fewer failure modes.
Architecture
azd up ├── Bicep provisions: │ Azure SQL Server + Database │ Azure Container Registry (ACR) │ Container Apps Environment │ DAB Container App (port 5000) │ SQL Commander Container App (port 8080) │ Web Container App (port 80) │ MCP Inspector Container App (port 80) │ └── post-provision.ps1: 1. Add client IP to SQL firewall 2. Build + deploy dacpac schema 3. Build custom DAB image (with CORS) → push to ACR 4. Update DAB container app 5. Build web image → push to ACR 6. Update web container app 7. Build Inspector image → push to ACR 8. Update Inspector container app
File Structure
project/ ├── azure.yaml # azd entry point ├── azure/ │ ├── main.bicep # Subscription-scoped entry │ ├── resources.bicep # All Azure resources │ └── post-provision.ps1 # Content deployment hook ├── api/ │ ├── dab-config.json # DAB configuration │ └── Dockerfile # Custom DAB image └── database/ ├── database.sqlproj ├── Tables/*.sql └── Scripts/PostDeployment.sql
Templates
Dockerfile (DAB Custom Image)
FROM mcr.microsoft.com/azure-databases/data-api-builder:1.7.83-rc COPY dab-config.json /App/dab-config.json
Two lines. Config baked in. No volume mounts.
azure.yaml
name: my-project metadata: template: my-project infra: provider: bicep path: azure module: main hooks: postprovision: shell: pwsh continueOnError: false interactive: true run: ./azure/post-provision.ps1
main.bicep (Subscription Scope)
targetScope = 'subscription' @minLength(1) @maxLength(64) param environmentName string param location string @secure() param sqlAdminPassword string param sqlAdminUser string = 'sqladmin' var tags = { 'azd-env-name': environmentName } param resourceToken string = '' var effectiveToken = empty(resourceToken) ? uniqueString(subscription().id, environmentName, location) : resourceToken resource rg 'Microsoft.Resources/resourceGroups@2024-03-01' = { name: 'rg-${environmentName}-${effectiveToken}' location: location tags: tags } module resources 'resources.bicep' = { scope: rg name: 'resources' params: { location: location tags: tags resourceToken: effectiveToken sqlAdminUser: sqlAdminUser sqlAdminPassword: sqlAdminPassword } } // Outputs become env vars for post-provision hook output AZURE_RESOURCE_GROUP string = rg.name output AZURE_SQL_SERVER_NAME string = resources.outputs.sqlServerName output AZURE_SQL_SERVER_FQDN string = resources.outputs.sqlServerFqdn output AZURE_SQL_DATABASE string = 'sql-db' output AZURE_SQL_ADMIN_USER string = sqlAdminUser output AZURE_ACR_NAME string = resources.outputs.acrName output AZURE_CONTAINER_APP_API_NAME string = resources.outputs.dabAppName output AZURE_CONTAINER_APP_API_FQDN string = resources.outputs.dabFqdn
resources.bicep — DAB Container App
The DAB container app definition within
resources.bicep:
param location string param tags object param resourceToken string param sqlAdminUser string @secure() param sqlAdminPassword string // ── SQL Server + Database ── resource sqlServer 'Microsoft.Sql/servers@2023-08-01-preview' = { name: 'sql-server-${resourceToken}' location: location tags: tags properties: { administratorLogin: sqlAdminUser administratorLoginPassword: sqlAdminPassword } } resource sqlFirewallAzure 'Microsoft.Sql/servers/firewallRules@2023-08-01-preview' = { parent: sqlServer name: 'AllowAzureServices' properties: { startIpAddress: '0.0.0.0' endIpAddress: '0.0.0.0' } } resource sqlDb 'Microsoft.Sql/servers/databases@2023-08-01-preview' = { parent: sqlServer name: 'sql-db' location: location tags: tags sku: { name: 'Basic', tier: 'Basic' } } // ── Container Registry ── resource acr 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' = { name: 'acr${resourceToken}' location: location tags: tags sku: { name: 'Basic' } properties: { adminUserEnabled: true } } // ── Container Apps Environment ── resource cae 'Microsoft.App/managedEnvironments@2024-03-01' = { name: 'environment-${resourceToken}' location: location tags: tags properties: {} } // ── DAB Container App ── var sqlConnString = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Database=sql-db;User Id=${sqlAdminUser};Password=${sqlAdminPassword};Encrypt=true;TrustServerCertificate=true' resource dabApp 'Microsoft.App/containerApps@2024-03-01' = { name: 'data-api-${resourceToken}' location: location tags: tags properties: { managedEnvironmentId: cae.id configuration: { ingress: { external: true targetPort: 5000 } registries: [ { server: acr.properties.loginServer username: acr.listCredentials().username passwordSecretRef: 'acr-password' } ] secrets: [ { name: 'db-conn', value: sqlConnString } { name: 'acr-password', value: acr.listCredentials().passwords[0].value } ] } template: { containers: [ { name: 'dab-api' image: 'mcr.microsoft.com/azure-databases/data-api-builder:1.7.83-rc' resources: { cpu: json('0.5'), memory: '1Gi' } env: [ { name: 'MSSQL_CONNECTION_STRING', secretRef: 'db-conn' } ] } ] scale: { minReplicas: 1, maxReplicas: 1 } } } } // ── Outputs ── output sqlServerName string = sqlServer.name output sqlServerFqdn string = sqlServer.properties.fullyQualifiedDomainName output acrName string = acr.name output dabAppName string = dabApp.name output dabFqdn string = dabApp.properties.configuration.ingress.fqdn
Note: Bicep provisions with a placeholder image. The post-provision hook replaces it with the custom ACR image.
Connection Strings
Azure SQL (SQL Auth)
Server=tcp:<server>.database.windows.net,1433;Database=sql-db;User Id=sqladmin;Password=<password>;Encrypt=true;TrustServerCertificate=true
Azure SQL (Managed Identity)
Server=tcp:<server>.database.windows.net,1433;Database=sql-db;Authentication=Active Directory Default;Encrypt=true;TrustServerCertificate=true
CRITICAL:
is required — DAB and SQL Commander will not connect without it.TrustServerCertificate=true
CORS Configuration
DAB's
dab-config.json needs the web app's Azure origin in its CORS config. Use a placeholder that gets replaced during image build:
dab-config.json (with placeholder)
"cors": { "origins": ["http://localhost:5173", "__WEB_URL_AZURE__"], "allow-credentials": false }
Replacement in post-provision.ps1
$webOrigin = "https://$webFqdn" $dabConfig = Get-Content -Path "api/dab-config.json" -Raw $dabConfig = $dabConfig.Replace("__WEB_URL_AZURE__", $webOrigin) $dabConfig | Out-File -FilePath "api/dab-config.json" -Encoding utf8 -Force
This replacement happens before
, so the correct origin is baked into the image.az acr build
Post-Provision Hook Pattern
The
post-provision.ps1 script runs after Bicep provisions all resources. It handles content that can't be done declaratively:
$ErrorActionPreference = "Stop" # Variables from Bicep outputs (set by azd as env vars) $resourceGroup = $env:AZURE_RESOURCE_GROUP $sqlServerName = $env:AZURE_SQL_SERVER_NAME $sqlServerFqdn = $env:AZURE_SQL_SERVER_FQDN $sqlDb = $env:AZURE_SQL_DATABASE $sqlAdminUser = $env:AZURE_SQL_ADMIN_USER $sqlAdminPassword = $env:AZURE_SQL_ADMIN_PASSWORD $acrName = $env:AZURE_ACR_NAME $dabAppName = $env:AZURE_CONTAINER_APP_API_NAME $dabFqdn = $env:AZURE_CONTAINER_APP_API_FQDN $sqlConn = "Server=tcp:$sqlServerFqdn,1433;Database=$sqlDb;User Id=$sqlAdminUser;Password=$sqlAdminPassword;Encrypt=true;TrustServerCertificate=true" # ── 1. Open SQL firewall for local machine ── $myIp = (Invoke-WebRequest -Uri "https://api.ipify.org" -UseBasicParsing).Content az sql server firewall-rule create ` --resource-group $resourceGroup ` --server $sqlServerName ` --name "azd-deploy-client" ` --start-ip-address $myIp ` --end-ip-address $myIp 2>$null | Out-Null # ── 2. Deploy database schema (dacpac) ── dotnet build database/database.sqlproj -c Release sqlpackage /Action:Publish ` /SourceFile:database/bin/Release/database.dacpac ` /TargetConnectionString:"$sqlConn" ` /p:BlockOnPossibleDataLoss=false # ── 3. Build custom DAB image with CORS → push to ACR ── $apiDeployDir = "api-deploy-temp" Copy-Item -Path "api" -Destination $apiDeployDir -Recurse -Force $webOrigin = "https://$env:AZURE_WEB_APP_FQDN" $dabConfig = Get-Content -Path "$apiDeployDir/dab-config.json" -Raw $dabConfig = $dabConfig.Replace("__WEB_URL_AZURE__", $webOrigin) $dabConfig | Out-File -FilePath "$apiDeployDir/dab-config.json" -Encoding utf8 -Force az acr build --registry $acrName --image dab-api:latest --file "$apiDeployDir/Dockerfile" $apiDeployDir/ Remove-Item $apiDeployDir -Recurse -Force # ── 4. Update DAB container app with custom image ── az containerapp update ` --name $dabAppName ` --resource-group $resourceGroup ` --image "$acrName.azurecr.io/dab-api:latest"
Steps 5-8 handle web app, Inspector — see
andazure-mcp-inspectorskills.azure-sql-commander
Deployment Commands
Full deployment (recommended)
azd up
This runs Bicep provisioning + post-provision hook in one command.
Re-deploy DAB only (after config change)
# Replace CORS placeholder $dabConfig = Get-Content -Path "api/dab-config.json" -Raw $dabConfig = $dabConfig.Replace("__WEB_URL_AZURE__", $webOrigin) $dabConfig | Out-File -FilePath "api-deploy-temp/dab-config.json" -Encoding utf8 # Build and push az acr build --registry <acr-name> --image dab-api:latest --file api/Dockerfile api/ # Update container az containerapp update --name <app-name> --resource-group <rg> --image <acr>.azurecr.io/dab-api:latest
Re-deploy schema only
dotnet build database/database.sqlproj -c Release sqlpackage /Action:Publish ` /SourceFile:database/bin/Release/database.dacpac ` /TargetConnectionString:"Server=tcp:<fqdn>,1433;Database=sql-db;User Id=sqladmin;Password=<pw>;Encrypt=true;TrustServerCertificate=true" ` /p:BlockOnPossibleDataLoss=false
Verification
After deployment, verify DAB is healthy:
$r = Invoke-WebRequest -Uri "https://<dab-fqdn>/health" -UseBasicParsing $r.StatusCode # Should be 200
Also check:
- REST:
https://<dab-fqdn>/api/<entity> - GraphQL:
https://<dab-fqdn>/graphql - MCP:
https://<dab-fqdn>/mcp - Swagger:
https://<dab-fqdn>/swagger
Common Issues
DAB container fails to start
- Check connection string secret is correct in Container Apps
- Verify Azure SQL firewall allows Azure services (
rule)0.0.0.0 - Check
is in the connection stringTrustServerCertificate=true
Schema not found (404 on entities)
- Database schema must be deployed before the DAB image is built
- Post-provision runs dacpac first, then builds the DAB image — this order matters
CORS errors from web app
- The
placeholder must be replaced before__WEB_URL_AZURE__az acr build - Verify the replacement happened: check post-provision output for "CORS origin set to..."
- After adding a new web origin, rebuild and push the DAB image
ACR build fails
- Verify ACR admin is enabled:
az acr show --name <acr> --query adminUserEnabled - Check Dockerfile path is correct relative to build context
Container app shows old image
- Azure caches images. After pushing, update the container:
az containerapp update --name <app> --resource-group <rg> --image <acr>.azurecr.io/dab-api:latest
Key Differences: Local vs Azure
| Aspect | Local (Docker/Aspire) | Azure |
|---|---|---|
| Config mount | Bind mount | Baked into image |
| Connection | (service name) | |
| CORS | or | Specific origin |
| Secrets | file | Container Apps secrets |
| Schema deploy | to | to Azure SQL FQDN |
| Image source | Docker Hub / MCR direct | ACR (custom image) |
Related Skills
— Local dev with .NET Aspireaspire-data-api-builder
— Local dev with Docker Composedocker-data-api-builder
— DAB config file referencedata-api-builder-config
— DAB CLI commandsdata-api-builder-cli
— Deploy SQL Commander to Azureazure-sql-commander
— Deploy MCP Inspector to Azureazure-mcp-inspector