Purpose of this page
Deploy a new version of the API, the UI, or both to Azure by hand — and understand each of the four steps well enough to run them individually when something needs debugging.
Before you run it prerequisites
- Azure CLI installed, and az login already done — the script never logs you in.
- The resources from az_create_resources must already exist; this script only updates them.
- Run it from the repository root, not from inside utility_scripts/ — it locates the build contexts relative to the project.
- No Docker needed. The images are built by Azure Container Registry, not on your machine.
PowerShell, from the repository root.\utility_scripts\az_deploy.ps1 -UserName dzierzon
Bashbash utility_scripts/az_deploy.sh --user-name dzierzon
What the script does four steps, twice
The same four operations are applied to each of the two applications. Everything below is from az_deploy.ps1; the Bash version does the same work with --kebab-case options.
Push-Location $webApp.Context
try {
Invoke-Az acr build --registry $RegistryName --resource-group $resourceGroup `
--image "$($webApp.Image):$ImageTag" --file $webApp.DockerfilePath .
}
finally { Pop-Location }
One command per image. It packs the build context into an archive, uploads it, and asks the registry to run the Dockerfile on a managed build agent — the finished image is pushed into the registry without ever existing on your machine.
The Push-Location isn't decoration. az acr build validates --file against the current directory rather than the source argument, so the API (context: repo root, file: metals_api/Dockerfile) and the UI (context: metals_ui, file: Dockerfile) each have to run from their own context.
Invoke-Az webapp config container set --name $name --resource-group $resourceGroup `
--container-image-name $image `
--container-registry-url "https://$($registry.loginServer)" --output none
Sets which image the app should run. Strictly it's only needed when the tag changes, but running it every time makes the script idempotent — and it's what converts an app that was previously configured for something else.
No registry credentials appear here. The Web App authenticates to the registry with its own managed identity, configured back in az_create_resources.
Invoke-Az webapp restart --name $name --resource-group $resourceGroup --output none
Because this project reuses the latest tag, step 2 usually sets the image name to the value it already had — so Azure sees no change and keeps running the old container indefinitely. An explicit restart forces a fresh pull. Skip this and the deployment silently does nothing.
for ($attempt = 1; $attempt -le 10; $attempt++) {
try {
$response = Invoke-WebRequest -Uri "https://$hostName/health" -TimeoutSec 10 -UseBasicParsing
if ($response.StatusCode -eq 200) { $healthy = $true; break }
}
catch { Start-Sleep -Seconds 10 }
}
webapp restart returns immediately, long before the new container is serving traffic. The script polls each app's /health endpoint up to ten times, ten seconds apart, and warns clearly if one never comes up — so a broken rollout looks like a failure rather than a success.
The parameters and when you'd use them
Defaults are chosen so that the bare command does the common thing: build every image as latest and deploy them.
| PowerShell | Bash | Use |
|---|---|---|
-UserName | --user-name | Resource-name suffix; default dzierzon. Must match what you created resources with. |
-RegistryName | --registry-name | Override the generated registry name, if you had to pick a different one. |
-ImageTag | --image-tag | Tag to build and deploy; default latest. |
-SkipBuild | --skip-build | Skip building entirely and just repoint and restart at an already-pushed tag — useful for rollbacks and for retrying a failed deploy. |
-FollowLogs | --follow-logs | Stream the API's container logs after deploying, instead of returning to the prompt. |
Why az acr build instead of docker build + push
The obvious alternative is building locally and pushing. Letting the registry build instead removes several moving parts — and it's exactly what the GitHub Actions workflows do too, for the same reasons.
- Installing Docker on every machine that might deploy, including CI runners.
- A separate registry login. Your existing az login is the only authentication involved.
- Uploading image layers. Only the source is sent; the built layers never leave Azure.
- Architecture mismatches. The image is always built on the platform it will run on, so an ARM laptop can't accidentally produce an image the x86 Web App can't start.
You lose the local build cache — each az acr build starts fresh, so a small code change costs a full rebuild rather than a few cached seconds. For a deployment step that's an acceptable price; for the tight edit-and-test loop you'd still use Compose locally.
Common errors and how to fix them
| What you see | Likely cause | How to fix it |
|---|---|---|
| Unable to find 'Dockerfile' | az acr build resolved --file against your current directory, not the source argument. | Run the script from the repository root. This is exactly what the Push-Location in step 1 exists to handle. |
| AuthorizationFailed on listBuildSourceUploadUrl | The identity can reach the registry but lacks permission to start a build. | Building needs Contributor on the registry — see the roles table. Your own az login account usually already has it. |
| Deploy succeeds, but the site is unchanged | The new image was pushed under the same tag and nothing restarted. | Confirm step 3 ran. Rerun with -SkipBuild to repoint and restart without rebuilding. |
| did not report healthy ... within the timeout | The container started but is failing, or is slower than 100 seconds to come up. | Read the container logs: az webapp log tail --resource-group <rg> --name <app>. A missing app setting is the usual culprit. |
| ResourceNotFound for the registry | The resources don't exist yet, or -UserName doesn't match what they were created with. | Run az_create_resources first, using the same username. |
| The UI loads but every API call fails | The UI container can't reach the API — typically a DNS resolver or upstream setting. | Check the UI app's API_UPSTREAM and NGINX_RESOLVER settings; see the UI image page. |