Azure infrastructure for the classified website spans several services that work together: the App Service hosts the ASP.NET Core API, Azure SQL holds the data, Redis caches view counts and session data, Blob Storage stores listing photos, CDN delivers photos globally, and Azure SignalR Service enables real-time notifications across multiple API instances. Provisioning with Bicep (Azure’s Infrastructure as Code language) means the entire environment can be recreated in minutes from a single command — essential for disaster recovery and creating staging environments.
Azure Resource Provisioning
// ── infra/main.bicep — classified website infrastructure ──────────────────
// Deploy with: az deployment group create
// --resource-group classifiedapp-rg
// --template-file infra/main.bicep
// --parameters @infra/params.prod.json
// Resource group parameters
param location string = resourceGroup().location
param environment string = 'production'
param appName string = 'classifiedapp'
// ── Azure SQL Database ────────────────────────────────────────────────────
resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {
name: '${appName}-sql-${environment}'
location: location
properties: {
administratorLogin: 'sqladmin'
// Password from Key Vault reference — never in Bicep!
administratorLoginPassword: '@Microsoft.KeyVault(VaultName=kv-classified;SecretName=sql-password)'
version: '12.0'
minimalTlsVersion: '1.2'
publicNetworkAccess: 'Enabled' // or 'Disabled' for VNet-only
}
}
resource sqlDb 'Microsoft.Sql/servers/databases@2022-05-01-preview' = {
parent: sqlServer
name: '${appName}-db'
location: location
sku: { name: 'GP_Gen5_2', tier: 'GeneralPurpose', capacity: 2 }
properties: {
collation: 'SQL_Latin1_General_CP1_CI_AS'
maxSizeBytes: 32 * 1024 * 1024 * 1024 // 32 GB
zoneRedundant: false // true for high-availability (extra cost)
backupStorageRedundancy: 'Local'
}
}
// ── Azure App Service ─────────────────────────────────────────────────────
resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
name: '${appName}-plan'
location: location
sku: { name: 'B2', tier: 'Basic', capacity: 2 }
kind: 'linux'
properties: { reserved: true } // Linux plan
}
resource apiApp 'Microsoft.Web/sites@2022-03-01' = {
name: '${appName}-api'
location: location
identity: { type: 'SystemAssigned' } // Managed Identity
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
linuxFxVersion: 'DOTNETCORE|8.0'
alwaysOn: true
http20Enabled: true
appSettings: [
{ name: 'ASPNETCORE_ENVIRONMENT', value: 'Production' }
{ name: 'Azure__SignalRConnectionString',
value: '@Microsoft.KeyVault(...)' }
{ name: 'Stripe__SecretKey',
value: '@Microsoft.KeyVault(...)' }
]
}
httpsOnly: true
}
}
// ── Auto-scaling ──────────────────────────────────────────────────────────
resource autoScale 'Microsoft.Insights/autoscalesettings@2022-10-01' = {
name: '${appName}-autoscale'
location: location
properties: {
targetResourceUri: appServicePlan.id
enabled: true
profiles: [{
name: 'Default'
capacity: { minimum: '2', maximum: '5', default: '2' }
rules: [
{
// Scale out when CPU > 70% for 5 minutes
metricTrigger: {
metricName: 'CpuPercentage'
threshold: 70
operator: 'GreaterThan'
timeWindow: 'PT5M'
}
scaleAction: { direction: 'Increase', value: '1', cooldown: 'PT10M' }
}
{
// Scale in when CPU < 30% for 10 minutes
metricTrigger: {
metricName: 'CpuPercentage'
threshold: 30
operator: 'LessThan'
timeWindow: 'PT10M'
}
scaleAction: { direction: 'Decrease', value: '1', cooldown: 'PT10M' }
}
]
}]
}
}
identity: { type: 'SystemAssigned' }) eliminates stored credentials entirely for Azure service-to-service authentication. The App Service authenticates to Azure SQL using its identity, not a username/password. Grant access with: CREATE USER [classifiedapp-api] FROM EXTERNAL PROVIDER; ALTER ROLE db_datareader ADD MEMBER [classifiedapp-api]; ALTER ROLE db_datawriter ADD MEMBER [classifiedapp-api]; — the minimum necessary permissions. No connection string with passwords means no passwords to rotate, leak, or expire.@Microsoft.KeyVault(VaultName=...; SecretName=...)) in App Service configuration to read secrets directly from Key Vault at runtime. The App Service's Managed Identity must have Key Vault Secrets User role on the Key Vault. This way, secrets never pass through GitHub — they are fetched directly by Azure at deployment time.Infrastructure at a Glance
| Service | SKU | Purpose | Monthly Cost (approx) |
|---|---|---|---|
| Azure App Service | B2 (2 instances) | ASP.NET Core API | ~$70 |
| Azure SQL | GP_Gen5_2 | Classified data | ~$185 |
| Azure Redis | C1 Standard | View counts, sessions | ~$55 |
| Azure SignalR | Standard S1 | Real-time notifications | ~$50 |
| Blob + CDN | LRS + Standard | Listing photos | ~$20 |
| Static Web Apps | Standard | Angular frontend | ~$9 |
| Total | ~$389/mo |
Common Mistakes
Mistake 1 — Connection strings with passwords in App Service config (credential exposure)
❌ Wrong — Server=...;Password=MyPassword123 in App Service environment variable; password visible in Azure Portal.
✅ Correct — Managed Identity for SQL/Storage; Key Vault references for 3rd-party secrets (Stripe, SendGrid).
Mistake 2 — Single App Service instance with no autoscaling (single point of failure)
❌ Wrong — one instance; any restart (deployment, crash) causes downtime.
✅ Correct — minimum 2 instances always running; autoscale to 5 on high load; deployments use slot swap (zero downtime).