6.1 KiB
Create RG + App Service Plan + Web App (Linux, P0v3)
Creates resources only when missing — every step is show || create (idempotent against existing user-supplied names).
💡 Shell note: Bash blocks below use
\line continuation,||,2>/dev/null,$(…). PowerShell equivalents are shown alongside where the bash form doesn't round-trip — substitute`for\,2>$nullfor2>/dev/null, and$LASTEXITCODEchecks for||.
1. Resolve Azure context — minimize prompts
Ask the user at most ONE question (the app name). Derive everything else. If the user's request already names an RG / Plan / region / subscription (e.g. "deploy to my-team-rg in westus3"), use those values verbatim and skip the corresponding derivation — the show || create flow below works against existing resources.
1a. Subscription
az account show --query id -o tsv
If unset, prompt the user to az login. Only call ask_user if multiple subscriptions are configured and no default is set.
1b. App name
Ask the user once:
"What name would you like for your App Service? (Press Enter to auto-generate one.)"
If empty / "any" / "you choose", call the generator script — it implements the slug rules (lowercase, hyphen-collapse, ≤ 40 chars, ^[a-z][a-z0-9-]{1,38}[a-z0-9]$) and an 8-hex-char GUID suffix:
- Bash / zsh:
scripts/generate-app-name.sh→APP_NAME=$(./scripts/generate-app-name.sh [folder]) - PowerShell:
scripts/generate-app-name.ps1→$appName = & .\scripts\generate-app-name.ps1 [-FolderName <folder>]
Example: folder my-flask-app/ → my-flask-app-a3f9c1d2.
1c. Derived names (use only when user did not specify)
| Resource | Default |
|---|---|
| Resource group | <app-name>-rg |
| App Service Plan | <app-name>-plan |
1d. Region
- If user specified a region, use it.
- Else read the CLI default. Suppress the "Configuration is not set" stderr line only when paired with an exit-code check — never blindly drop stderr, since it would also swallow auth/transport errors:
REGION=$(az config get defaults.location -o tsv 2>/dev/null) || REGION=""$region = az config get defaults.location -o tsv 2>$null; if ($LASTEXITCODE -ne 0) { $region = "" } - Else default to
eastus2. - Only call
ask_userifaz group createlater fails with a region/quota/availability error.
1e. Show the defaults summary BEFORE creating
Print one concise block so the user can interrupt to override.
Example (illustrative — substitute the actual derived values, do not print verbatim):
Using these defaults for your Python App Service deployment:
• App name : flask-app-demo-27may
• Resource group : flask-app-demo-27may-rg (auto-derived)
• App Service Plan: flask-app-demo-27may-plan (auto-derived)
• Region : eastus2 (CLI default)
• Plan SKU : P0v3 Linux
• Runtime : PYTHON:3.14
Proceeding with create. Reply "stop" within the next message to change any value.
Do not call ask_user for confirmation here — just print and proceed.
1f. Transient error handling
On connection-level or 429/5xx errors from any az ... create in §§2–4, see transient-retry.md. Configuration errors (AuthorizationFailed, SkuNotAvailable, QuotaExceeded, etc.) must not be retried — surface them.
2. Resource Group
az group show -n <rg> --only-show-errors 2>/dev/null || \
az group create -n <rg> -l <region>
az group show -n <rg> --only-show-errors 2>$null
if ($LASTEXITCODE -ne 0) { az group create -n <rg> -l <region> }
3. App Service Plan — Linux, P0v3 by default
⚠️ MANDATORY: Use
--is-linuxand--sku P0v3. Do not change OS or SKU unless the user explicitly requests it.
az appservice plan show -n <plan> -g <rg> --only-show-errors 2>/dev/null || \
az appservice plan create -n <plan> -g <rg> --is-linux --sku P0v3 -l <region>
az appservice plan show -n <plan> -g <rg> --only-show-errors 2>$null
if ($LASTEXITCODE -ne 0) {
az appservice plan create -n <plan> -g <rg> --is-linux --sku P0v3 -l <region>
}
4. Web App — Python 3.14 runtime (Linux)
⚠️ Shell safety: Always use the colon form
PYTHON:3.14— never the pipe formPYTHON|3.14. The pipe character is a shell operator in PowerShell, Bash, and cmd, and breaks the command even when quoted in some contexts. The colon form is fully supported byaz webapp create --runtimeand is shell-safe everywhere.
az webapp show -n <app> -g <rg> --only-show-errors 2>/dev/null || \
az webapp create -n <app> -g <rg> -p <plan> --runtime "PYTHON:3.14"
az webapp show -n <app> -g <rg> --only-show-errors 2>$null
if ($LASTEXITCODE -ne 0) {
az webapp create -n <app> -g <rg> -p <plan> --runtime "PYTHON:3.14"
}
The 8-hex-char GUID suffix from §1b is sufficient for global hostname uniqueness; the optional --domain-name-scope TenantReuse flag (Azure CLI ≥ 2.76, July 2025) is intentionally omitted to stay compatible with older CLIs.
Discover available runtimes
If PYTHON:3.14 is unavailable in the region:
az webapp list-runtimes --os linux --query "[?contains(@, 'PYTHON')]" -o tsv
The output uses the pipe form (e.g., PYTHON|3.14) — convert to colon form before passing to --runtime. Prefer 3.14; fall back to 3.13, then 3.12.
5. Verify
az webapp show -n <app> -g <rg> --query "{name:name, state:state, host:defaultHostName, linuxFx:siteConfig.linuxFxVersion}" -o table
Expected: state: Running, linuxFx: PYTHON|3.14 (Azure stores it in pipe form internally — normal), host: <app>.azurewebsites.net.
Notes
- ⛔ Never use
az webapp up— deprecated. See deploy-azcli.md. - If the user requests a different SKU (e.g.
B1for dev/test), respect it but warn that P0v3 is the documented default. - If a Windows plan is requested, hand off to
azure-prepare.