Tutorials › Deployment

Metals Simple Deploy · Deployment

Getting This Project into Azure

Deploying a containerized application is four steps, and they're the same four no matter who runs them: build an image, push it to a registry, point the hosting service at the new tag, and restart so it pulls. This project can do those four steps three different ways — by hand from your terminal, automatically from GitHub Actions, or declaratively through Terraform. This section explains the steps themselves, then the automation around them.

Codey the Sr Developer, standing with a pointer, ready to walk through the guide
Registry → Azure Container Registry Hosting → 2 container Web Apps Build tool → az acr build CI/CD → GitHub Actions Auth → OIDC, no passwords

The four steps, every time start here

Before containers, deploying this API meant uploading a zip of Python source and letting the server install dependencies and restart itself. Now the deployable artifact is a finished image, which makes the process shorter and far more predictable — but adds a registry in the middle.

Codey typing on a laptop
build, push, point, restart
  1. Build the image from the Dockerfile. This project builds in Azure with az acr build rather than locally — the source is uploaded, built on a managed agent, and the result lands in the registry in one step. No local Docker required.
  2. Push it to the Azure Container Registry as metals-api:<tag> or metals-ui:<tag>. With az acr build this is included — build and push are the same command.
  3. Point the Web App at that image, with az webapp config container set. Only strictly necessary when the tag changes, but running it every time keeps the deployment idempotent.
  4. Restart the Web App so it actually pulls. This step is the one people forget — see below.
Where the images come frompull, not push

Nothing pushes an image to the Web App. The Web App pulls it from the registry, authenticating with its own system-assigned managed identity, which has been granted the AcrPull role. There are no registry usernames or passwords anywhere in this project — the registry's admin account is explicitly disabled.

That identity and role assignment are created for you by both the manual script and Terraform.

Why a restart is required the :latest catch

This project tags its images latest. Pushing a new image under a tag that the Web App is already configured to use changes nothing from Azure's point of view — the configured value is identical, so there's nothing to react to, and the old container keeps running the old image indefinitely.

Codey thinking with a rubber duck
same tag, new contents
So every deployment path restarts explicitlyall three of them

az webapp restart forces the platform to start a fresh container, which re-pulls the tag and therefore picks up the new image. Both az_deploy and the GitHub Actions workflows do this as a deliberate final step, with a comment saying why.

The alternative is to tag every build uniquely — with a commit SHA, for example — so the configured value genuinely changes and no restart is needed. That's a common production pattern; this project favors the simpler moving tag, and pays for it with one extra command.

Then wait for healthydon't declare victory early

A restart returns immediately, long before the new container is serving traffic. Every deployment path here finishes by polling /health on both apps until each answers 200, so a failed rollout surfaces as a failed deployment instead of a silent one.

Infrastructure vs. application two different clocks

It's worth being precise about which kind of change you're making, because they move at different speeds and carry very different risk.

InfrastructureApplication
What changesThe registry, Web Apps, database server, identities, roles.The contents of the two container images.
How oftenRarely — once to create it, then occasionally.Constantly, with every code change.
Done byaz_create_resources or Terraform.az_deploy or the deploy workflows.
If it goes wrongPotentially destructive — a deleted database loses data.Recoverable — push a corrected image and restart.
Automation policyAlways manual. Even with CI/CD set up, apply requires someone to click Run workflow.Automatic on merge to main, after tests pass.

That last row is the project's central deployment decision, and it's covered in detail on the workflows page: shipping a tested container image is routine enough to automate, while changing infrastructure is not.

Three ways to do it pick a path

All three produce the same running application. They differ in how much is automated, how much is previewed before it happens, and how much is remembered between runs.

Codey holding up a sticky note
same destination, three roads

These aren't mutually exclusive, and this project uses a mix: Terraform owns the infrastructure, GitHub Actions owns routine image deployments, and the manual scripts stay around for learning and for when something needs doing by hand.

Pick a page to learn go deeper

Codey giving a thumbs up

Haven't read the Docker tutorial yet? Start there — everything on this page assumes you know what an image is.