# 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>$null` for `2>/dev/null`, and `$LASTEXITCODE` checks 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 ```bash 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`](../scripts/generate-app-name.sh) โ†’ `APP_NAME=$(./scripts/generate-app-name.sh [folder])` - PowerShell: [`scripts/generate-app-name.ps1`](../scripts/generate-app-name.ps1) โ†’ `$appName = & .\scripts\generate-app-name.ps1 [-FolderName ]` Example: folder `my-flask-app/` โ†’ `my-flask-app-a3f9c1d2`. ### 1c. Derived names (use only when user did not specify) | Resource | Default | |---|---| | Resource group | `-rg` | | App Service Plan | `-plan` | ### 1d. Region 1. If user specified a region, use it. 2. 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: ```bash REGION=$(az config get defaults.location -o tsv 2>/dev/null) || REGION="" ``` ```powershell $region = az config get defaults.location -o tsv 2>$null; if ($LASTEXITCODE -ne 0) { $region = "" } ``` 3. Else default to `eastus2`. 4. Only call `ask_user` if `az group create` later 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](transient-retry.md). Configuration errors (`AuthorizationFailed`, `SkuNotAvailable`, `QuotaExceeded`, etc.) must **not** be retried โ€” surface them. ## 2. Resource Group ```bash az group show -n --only-show-errors 2>/dev/null || \ az group create -n -l ``` ```powershell az group show -n --only-show-errors 2>$null if ($LASTEXITCODE -ne 0) { az group create -n -l } ``` ## 3. App Service Plan โ€” **Linux, P0v3 by default** > โš ๏ธ **MANDATORY**: Use `--is-linux` and `--sku P0v3`. Do not change OS or SKU unless the user explicitly requests it. ```bash az appservice plan show -n -g --only-show-errors 2>/dev/null || \ az appservice plan create -n -g --is-linux --sku P0v3 -l ``` ```powershell az appservice plan show -n -g --only-show-errors 2>$null if ($LASTEXITCODE -ne 0) { az appservice plan create -n -g --is-linux --sku P0v3 -l } ``` ## 4. Web App โ€” Python 3.14 runtime (Linux) > โš ๏ธ **Shell safety**: Always use the **colon** form `PYTHON:3.14` โ€” never the pipe form `PYTHON|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 by `az webapp create --runtime` and is shell-safe everywhere. ```bash az webapp show -n -g --only-show-errors 2>/dev/null || \ az webapp create -n -g -p --runtime "PYTHON:3.14" ``` ```powershell az webapp show -n -g --only-show-errors 2>$null if ($LASTEXITCODE -ne 0) { az webapp create -n -g -p --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: ```bash 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 ```bash az webapp show -n -g --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: .azurewebsites.net`. ## Notes - โ›” Never use `az webapp up` โ€” deprecated. See [deploy-azcli.md](deploy-azcli.md). - If the user requests a different SKU (e.g. `B1` for dev/test), respect it but warn that **P0v3** is the documented default. - If a Windows plan is requested, hand off to `azure-prepare`.