Subchapter 8.1
references/bicep-app-service.mdMarkdown5 KBView on GitHub
App Service-specific Bicep patterns. For shared patterns (skeleton, naming, tags, security defaults, data modules), see bicep-patterns.md.
Standard App Service module with managed identity and SCM/FTP auth disabled. Use this as the base for ALL App Service resources.
param location string
param tags object
param appServicePlanId string
param appServiceName string
resource appService 'Microsoft.Web/sites@2023-12-01' = {
name: appServiceName
location: location
tags: tags
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: appServicePlanId
httpsOnly: true
siteConfig: {
minTlsVersion: '1.2'
ftpsState: 'Disabled'
}
}
}
// SCM basic auth — enabled in IaC for deploy convenience. Deploy phase re-disables via REST API after code upload.
resource scmAuth 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2023-12-01' = {
parent: appService
name: 'scm'
properties: {
allow: true
}
}
// ⛔ MANDATORY — disable FTP basic auth
resource ftpAuth 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2023-12-01' = {
parent: appService
name: 'ftp'
properties: {
allow: false
}
}
output appServiceId string = appService.id
output principalId string = appService.identity.principalId⛔ Every App Service module MUST include: (1)
identity: { type: 'SystemAssigned' }, (2)scmbasicPublishingCredentialsPolicies withallow: true(IaC sets enabled for deploy convenience — deploy phase re-disables via REST API after code upload), (3)ftpbasicPublishingCredentialsPolicies withallow: false. Missing any of these → self-review L1FLAGGED.
When prepare-plan.json.deployStrategy exists with codeDeployPattern: "startup-install", apply these patterns to the App Service Bicep:
resource appService 'Microsoft.Web/sites@2023-12-01' = {
name: appServiceName
location: location
tags: tags
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
siteConfig: {
linuxFxVersion: 'NODE|${nodeVersion}' // ⛔ Use exact version tag (18-lts, 20-lts) — NEVER tilde (~18). Tilde works for WEBSITE_NODE_DEFAULT_VERSION but NOT linuxFxVersion.
ftpsState: 'Disabled'
minTlsVersion: '1.2'
// Startup command: Oryx build is primary, this is the safety-net fallback.
// Runs npm install only if node_modules doesn't exist (Oryx missed it).
// MUST be inline — never a .sh file (Windows CRLF → bash exit code 2).
appCommandLine: '${deployStrategy.startupCommand}'
appSettings: [
// Primary: tell Oryx to run npm install during zip deploy
{ name: 'SCM_DO_BUILD_DURING_DEPLOYMENT', value: 'true' }
{ name: 'ENABLE_ORYX_BUILD', value: 'true' }
// Extended timeout for native module compilation (max 1800, default 230)
{ name: 'WEBSITES_CONTAINER_START_TIME_LIMIT', value: '1800' }
// App-specific settings
{ name: 'NODE_ENV', value: 'production' }
]
}
}
}Rules:
appCommandLine only — never generate a .sh startup script file. Files created on Windows have CRLF line endings → bash exit code 2 on Linuxpackage.json.scripts.start or .main, never hardcode index.jsWEBSITES_CONTAINER_START_TIME_LIMIT = 1800 (the maximum). Native compilation takes 2-5 min; Python with scipy can take longerdeployStrategy is absent (no native modules), do NOT set appCommandLine — let Oryx use its default startupcd /home/site/wwwroot — Oryx extracts build output to a temp directory and sets the working directory automatically. Hardcoding cd /home/site/wwwroot causes MODULE_NOT_FOUND / Could not import because the app files aren’t thereSelf-review check (L2 Pattern): If hasNativeModules == true, verify Bicep has BOTH appCommandLine and WEBSITES_CONTAINER_START_TIME_LIMIT. If prereq-output.json.initCommands[] has required: true entries, verify appCommandLine includes them before the app start command. FLAGGED if either check fails.
⛔ F1/D1 does NOT support managed identity (causes OOM / deployment failure). When the plan SKU is F1 or D1:
- Omit
identity: { type: 'SystemAssigned' }from the App Service resource- Do NOT use
@Microsoft.KeyVault()in app settings — KV references require managed identity- Instead: Pass secrets as
@secure()params from the KV module’s@secure()output. The KV module generates the secret value and outputs it securely; main.bicep passes it to the App Service module as a@secure() param- Do NOT output
principalId— it doesn’t exist without identity
⛔
appService.identity.principalIdonly exists forSystemAssignedidentity. When usingUserAssigned, output the managed identity MODULE’sprincipalIdinstead —identity.principalIdis undefined and causesDeploymentOutputEvaluationFailed. F1/D1 App Service: no identity (OOM), so no principalId output.