Verify and remove
- Lesson 15
- 50 min
- Module 5: Deploy and clean up
Lesson 15 of 15 · Docker and Cloud Essentials for AI DevelopersVerify and remove
1. Understand containers
2. Build the application image
3. Networking, configuration and persistence
4. Cloud building blocks
5. Deploy and clean up
What you will be able to do
“Succeeded” is not proof. Send the requests that show the deployment works and fails safely, and read what it logged. Then remove all of it, with a list that proves that you forgot nothing.
Before you startWhat to know and to have ready
What you need to know
- A smoke check, from Verify and clean up: A smoke check in Build and Deploy Your First API.
- The request ID in each log line, from Configuration and logging: A request ID for every request in the API course.
- Scale to zero, from Cost awareness: Scale to zero, and the requests you did not send.
- The inventory and the “can it charge?” column, from Cost awareness: A resource inventory.
- Revisions and the system logs, from Operational configuration: Worked example: apply the file (live).
What you need on your computer
- Live route: the container app with the probes from
deploy/containerapp.yaml, from Operational configuration: Worked example: apply the file (live). You need its address and its key, from Registry and deployment: Step 6: create the container app. - Simulated route: the local simulation on
127.0.0.1:8080, from Registry and deployment: The simulated route: the same steps on your computer. The local registry comes from Registry, repository and tag. The key is insecrets/api_key.txt. docs/cloud-design.mdwith your inventory, from Cost awareness: Module practice. This lesson writesDEPLOY.mdnext to it.- If the live deployment is gone or broken, make it again with Registry and deployment: The live route: create the deployment. For the project files, use the snapshot
end-of-m05, as in Use a snapshot.
Tested with Docker Desktop 4.81.0 (Docker Engine 29.6.1) on macOS, Apple silicon. The Azure steps were run with Azure CLI 2.88.0 and the containerapp extension 1.3.0b5, in region canadacentral. The Windows and Linux steps are not tested.
Smoke checks: what this deployment must prove
Section titled “Smoke checks: what this deployment must prove”A smoke check is a short set of requests that you send right after a deployment. pytest already tested the code. The smoke check tests this deployment: this image, these settings, this secret, this address. Each check proves one thing:
| Request | Expected | It proves |
|---|---|---|
GET /health |
200 {"status":"ok"} |
The process runs and the ingress reaches it. |
GET /ready |
200 {"status":"ready"} |
The classifier can work. |
POST /v1/classify with the key |
200 and a category |
The secret reached the app. |
POST /v1/classify without the key |
401 |
The API is not open. |
GET /docs |
404 |
SHOW_DOCS=false reached the app. |
http://…/health (live only) |
301 to https:// |
No plain HTTP. |
GET /v1/history with the key |
503 history_unavailable |
Expected: there is no database in this deployment. |
Two checks expect a failure. A deployment that answers 200 without a key is broken, even if every other check passes.
Worked example: the live checks
Section titled “Worked example: the live checks”Amira sets the address once. The first request comes after a quiet time, so she measures it:
URL=https://ticket-api.nicewater-a307f95a.canadacentral.azurecontainerapps.iotime curl -s $URL/health$URL = "https://ticket-api.nicewater-a307f95a.canadacentral.azurecontainerapps.io"Measure-Command { curl.exe -s "$URL/health" }The output (live, shortened):
{"status":"ok"}real 18.4s18 s for one small request. The app had zero replicas, so the platform had to start one first: a cold start. The next requests took less than a second. Wake the app with /health before a timed check, so that a cold start does not look like a slow API.
Then the other checks. $KEY is the key from Registry and deployment: Step 6: create the container app, still in the terminal:
curl -s $URL/readycurl -s -X POST $URL/v1/classify -H 'Content-Type: application/json' -H "X-API-Key: $KEY" -d '{"subject":"Charged twice for March"}'curl -s -o /dev/null -w "%{http_code}\n" -X POST $URL/v1/classify -H 'Content-Type: application/json' -d '{"subject":"x"}'curl -s -o /dev/null -w "%{http_code}\n" $URL/docscurl -s $URL/v1/history -H "X-API-Key: $KEY"curl.exe -s "$URL/ready"curl.exe -s -X POST "$URL/v1/classify" -H "Content-Type: application/json" -H "X-API-Key: $KEY" -d '{\"subject\":\"Charged twice for March\"}'curl.exe -s -o NUL -w "%{http_code}\n" -X POST "$URL/v1/classify" -H "Content-Type: application/json" -d '{\"subject\":\"x\"}'curl.exe -s -o NUL -w "%{http_code}\n" "$URL/docs"curl.exe -s "$URL/v1/history" -H "X-API-Key: $KEY"The output (live):
{"status":"ready"}{"category":"billing","priority":1,"confidence":0.7,"model_version":"keywords-1.0"}401404{"error":{"code":"history_unavailable","message":"The history is not available. Try again later.","request_id":"a6c8601cf7fb","fields":[]}}Every answer is the expected one. The http:// check gave 301, as in Operational configuration: Ingress. The history gives 503, because the cloud design has no database: a managed PostgreSQL server costs money every hour, and the practice does not need it. The API says so in its standard error shape, with a request ID. DATABASE_URL is an optional setting: with it, the same image would keep a history.
The Postman collection from the API course works too: set the Deployed environment’s baseUrl to the address, as in Verify and clean up: Worked example: the Deployed environment.
Worked example: the simulated checks
Section titled “Worked example: the simulated checks”The same checks, on http://127.0.0.1:8080. The key comes from the secret file, so it is not typed:
URL=http://127.0.0.1:8080KEY=$(cat secrets/api_key.txt)curl -s $URL/healthcurl -s $URL/readycurl -s -X POST $URL/v1/classify -H 'Content-Type: application/json' -H "X-API-Key: $KEY" -d @requests/billing.jsoncurl -s -o /dev/null -w "%{http_code}\n" -X POST $URL/v1/classify -H 'Content-Type: application/json' -d '{"subject":"x"}'curl -s -o /dev/null -w "%{http_code}\n" $URL/docscurl -s -o /dev/null -w "%{http_code}\n" $URL/v1/history -H "X-API-Key: $KEY"$URL = "http://127.0.0.1:8080"$KEY = Get-Content secrets/api_key.txt -Rawcurl.exe -s "$URL/health"curl.exe -s "$URL/ready"curl.exe -s -X POST "$URL/v1/classify" -H "Content-Type: application/json" -H "X-API-Key: $KEY" -d "@requests/billing.json"curl.exe -s -o NUL -w "%{http_code}\n" -X POST "$URL/v1/classify" -H "Content-Type: application/json" -d '{\"subject\":\"x\"}'curl.exe -s -o NUL -w "%{http_code}\n" "$URL/docs"curl.exe -s -o NUL -w "%{http_code}\n" "$URL/v1/history" -H "X-API-Key: $KEY"The output (simulated):
{"status":"ok"}{"status":"ready"}{"category":"billing","priority":1,"confidence":0.9,"model_version":"keywords-1.0"}401404503There is no cold start in the simulation (the container always runs) and no 301 (there is no HTTPS). Write that in the evidence: these two checks are live only.
Read the logs
Section titled “Read the logs”Live: the app’s own lines. The app writes the same log lines as on your computer. Azure collects them:
az containerapp logs show -g $RG -n ticket-api --tail 12 --format textThe output (live, shortened):
2026-10-07T16:17:09.83371 Connecting to the container 'ticket-api'...2026-10-07T16:17:09.93183 Successfully Connected to container: 'ticket-api' [Revision: 'ticket-api--0000001', Replica: 'ticket-api--0000001-84c5f7d4bb-9zszh']2026-10-07T16:16:54.6157582Z stderr F INFO ticket_api: GET /health 200 1ms id=caf6c4a82e302026-10-07T16:16:54.6158236Z stdout F INFO: 127.0.0.1:52216 - "GET /health HTTP/1.1" 200 OK2026-10-07T16:17:04.6139872Z stderr F INFO ticket_api: GET /ready 200 2ms id=81dbb27c35d72026-10-07T16:17:04.6140104Z stdout F INFO: 127.0.0.1:59104 - "GET /ready HTTP/1.1" 200 OK2026-10-07T16:17:05.7192419Z stderr F INFO ticket_api: POST /v1/classify 200 2ms id=95f853debb5b2026-10-07T16:17:05.7193523Z stdout F INFO: 100.100.0.60:49458 - "POST /v1/classify HTTP/1.1" 200 OK2026-10-07T16:17:06.5050443Z stderr F INFO ticket_api: GET / 404 0ms id=71d34007b1a12026-10-07T16:17:06.5051432Z stdout F INFO: 100.100.0.60:49464 - "GET / HTTP/1.1" 404 Not FoundRead the client addresses. 127.0.0.1 is the probes: they run inside the replica. 100.100.0.60 is the platform’s ingress: every request from outside comes through it. stderr F and stdout F name the stream, and F means a full line.
Look at the last request: GET / with 404. Nobody on the team sent it. It came a few minutes after the app got its public address, and more came at 16:19 and 16:27. These are requests that you did not send, for example from programs that scan the internet for new addresses. Each one wakes the app, and starts the 300-second wait before it scales to zero again. A public address is public from the first minute. That is why the first revision already required the key.
Live: the system logs. The platform’s own events (image pulls, starts, probe failures) are separate. You used them in Operational configuration: az containerapp logs show … --type system, without --format text.
Live: Log Analytics. The workspace keeps the lines, and you can search them with a query. They arrived about 2 minutes later:
WS=$(az monitor log-analytics workspace list -g $RG --query "[0].customerId" -o tsv)az monitor log-analytics query -w $WS --analytics-query "ContainerAppConsoleLogs_CL | where Log_s has 'classify' | project TimeGenerated, RevisionName_s, Log_s | take 5" -o table$WS = az monitor log-analytics workspace list -g $RG --query "[0].customerId" -o tsvaz monitor log-analytics query -w $WS --analytics-query "ContainerAppConsoleLogs_CL | where Log_s has 'classify' | project TimeGenerated, RevisionName_s, Log_s | take 5" -o tableThe output (live, shortened):
Log_s RevisionName_s TimeGenerated------------------------------------------------------------------ ------------------- ---------------------------INFO ticket_api: POST /v1/classify 200 2ms id=95f853debb5b ticket-api--0000001 2026-10-07T16:17:07.067693ZINFO: 100.100.0.60:49458 - "POST /v1/classify HTTP/1.1" 200 OK ticket-api--0000001 2026-10-07T16:17:07.067693Z...Each line has its revision. After a deployment, this tells you which version answered a request. To follow up one error, search for its request ID: where Log_s has 'a6c8601cf7fb'.
Simulated: Compose logs. A client sends a request without a key, and gets a request ID in the error:
curl -s -X POST http://127.0.0.1:8080/v1/classify -H 'Content-Type: application/json' -d '{"subject":"Parcel lost"}'docker compose -f deploy/local-cloud.yaml logs api | grep 0008b89dda79The output (simulated):
{"error":{"code":"unauthorized","message":"A valid X-API-Key header is required.","request_id":"0008b89dda79","fields":[]}}api-1 | INFO ticket_api: POST /v1/classify 401 7ms id=0008b89dda79In PowerShell, use Select-String 0008b89dda79 instead of grep. The simulation’s requests come from an address like 172.21.0.1 (the Compose network’s gateway), and its health checks from 127.0.0.1, like Azure’s probes.
Clean up from an explicit inventory
Section titled “Clean up from an explicit inventory”A practice deployment is finished only when it is gone. The rule: list first, delete second, check third. A list made before the deletion is the only way to know later that nothing was forgotten.
Text description of the diagram
First list every resource in the group. Then delete the group, without waiting for the command, and ask Azure later whether the group still exists. When it is gone, look for what a group deletion leaves: a soft-deleted Key Vault, which you purge. Check that no group and no role assignment is left. Then remove the copies on your computer, and record every result.
Step 1: the inventory
Section titled “Step 1: the inventory”az resource list --resource-group $RG -o tableThe output (live, with the optional Key Vault from Module 4):
Name ResourceGroup Location Type Status--------------------------------- ---------------------- ------------- ------------------------------------------------ ---------ticketapi4821 rg-ticket-api-practice canadacentral Microsoft.ContainerRegistry/registries Succeededid-ticket-api rg-ticket-api-practice canadacentral Microsoft.ManagedIdentity/userAssignedIdentities Succeededworkspace-rgticketapipracticeXXXX rg-ticket-api-practice canadacentral Microsoft.OperationalInsights/workspaces Succeededcae-ticket-api rg-ticket-api-practice canadacentral Microsoft.App/managedEnvironments Succeededticket-api rg-ticket-api-practice canadacentral Microsoft.App/containerApps Succeededkv-ticket-api-4821 rg-ticket-api-practice canadacentral Microsoft.KeyVault/vaults SucceededCopy this table into DEPLOY.md before you delete anything. Compare it with the design in docs/cloud-design.md: the workspace is the resource that nobody asked for. The role assignments are not in this list; they go with the resources that they are on.
Step 2: delete the group, and check later
Section titled “Step 2: delete the group, and check later”az group delete --name $RG --yes --no-waitThe command prints nothing and returns at once. Without --no-wait, it waits until the deletion is finished: in the test, that was about 28 minutes, from 16:36 to 17:04. The Container Apps environment was the last resource to go. While it runs, az group show -n $RG --query properties.provisioningState -o tsv says Deleting. Later, ask:
az group exists --name $RGtrue means that the deletion is not finished: ask again in a few minutes. The output at the end (live):
falseStep 3: what a group deletion leaves behind
Section titled “Step 3: what a group deletion leaves behind”If you made the Key Vault, it is not completely gone:
az keyvault list-deleted --query "[].{name:name, deleted:properties.deletionDate, purge:properties.scheduledPurgeDate}" -o tableThe output (live):
Name Deleted Purge------------------ ------------------------- -------------------------kv-ticket-api-4821 2026-10-07T16:36:40+00:00 2027-01-05T16:36:40+00:00The vault is soft-deleted: Azure keeps it, with its secrets, for 90 days, so that a mistake can be undone. It costs nothing. But the secret still exists, and nobody can make a new vault with that name. Purge it:
az keyvault purge --name kv-ticket-api-4821az keyvault list-deleted --query "length(@)"The purge prints nothing (exit code 0). The output of the count (live):
0Step 4: check that nothing is left
Section titled “Step 4: check that nothing is left”az group list --query "[?contains(name,'ticket-api')].name" -o tsvaz role assignment list --all --query "[?contains(scope,'rg-ticket-api-practice')].{role:roleDefinitionName,scope:scope}" -o tableBoth printed nothing (live). No group, and the role assignments were removed with the registry and the vault.
Step 5: the copies on your computer
Section titled “Step 5: the copies on your computer”docker image rm $ACR.azurecr.io/ticket-api:1.1.0 $ACR.azurecr.io/ticket-api:1.1.0-arm64docker logout $ACR.azurecr.ioThe output (live):
Untagged: ticketapi4821.azurecr.io/ticket-api:1.1.0Untagged: ticketapi4821.azurecr.io/ticket-api:1.1.0-arm64...Removing login credentials for ticketapi4821.azurecr.ioLeave out the 1.1.0-arm64 tag if you did not make it, and add any other tag that you pushed. docker logout deletes the registry token from Docker’s credential store. Keep ticket-api:1.1.0: it is your own image, and the final assignment uses it. In the test, the resources existed for about 54 minutes. The expected cost: one day of the Basic registry (US$0.17) and a few cents or less of compute, inside the free grant.
The simulated clean-up
Section titled “The simulated clean-up”The same rule: list, delete, check.
docker ps -a --filter name=ticket-api --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"docker volume ls --filter name=ticket-api-registryThe output (simulated):
NAMES IMAGE STATUSticket-api-registry registry:3.1.2 Up 16 secondsticket-api-cloud-sim-api-1 ticket-api:1.1.0 Up 3 minutes (healthy)DRIVER VOLUME NAMElocal ticket-api-registry-dataIf your Module 3 database is running, its containers are in the list too. Leave them. Then delete only the simulation and the registry:
docker compose -f deploy/local-cloud.yaml downdocker rm -f ticket-api-registrydocker volume rm ticket-api-registry-datadocker image rm 127.0.0.1:5000/ticket-api:1.1.0 127.0.0.1:5000/ticket-api:1.1.0-arm64 registry:3.1.2Add any other tag that you pushed, such as 1.1.0-native.
The output (simulated, shortened):
Container ticket-api-cloud-sim-api-1 Removed Network ticket-api-cloud-sim_default Removedticket-api-registryticket-api-registry-dataUntagged: 127.0.0.1:5000/ticket-api:1.1.0...Untagged: registry:3.1.2The registry keeps its images in the volume ticket-api-registry-data: 71.24MB in the test, the compressed size of the amd64 image (docker system df -v shows it). Without -v ticket-api-registry-data:… in the docker run command, Docker makes a volume with a random name, and docker rm -f leaves it behind. A named volume makes the inventory clear. Check that the list commands from the start now print no simulation lines.
Guided practice: verify and remove
Section titled “Guided practice: verify and remove”- Live route: wake the app with
/health, and write down the time of the first request. - Run the smoke checks for your route. Compare each answer with the table in Smoke checks.
- Find your classify request in the logs. Live:
az containerapp logs show --format text. Simulated:docker compose … logs api | grep <request ID>. - Live route: look for a request that you did not send, such as
GET /. - Make the inventory:
az resource list -g $RG -o table, or thedocker ps -aanddocker volume lslists. Copy it intoDEPLOY.md. - Delete. Live:
az group delete --name $RG --yes --no-wait. Simulated: the four commands of the simulated clean-up. - Check. Live:
az group existsuntilfalse, thenaz keyvault list-deleted(purge if needed),az group listandaz role assignment list. Simulated: the two list commands again. - Remove the local copies and sign out of the registry (live).
- Record each check and its result in
DEPLOY.md.
Show the expected result
Every smoke check gives its expected status: 200, 200, 200, 401, 404, and (live) 301, with 503 history_unavailable for the history. az group exists says false, az keyvault list-deleted --query "length(@)" says 0, and the group and role lists are empty. Simulated: no ticket-api-cloud-sim or ticket-api-registry lines are left. DEPLOY.md holds the inventory from before the deletion and the result of each check, as in the Module practice answer.
Your turn: a smoke check that you can repeat
Section titled “Your turn: a smoke check that you can repeat”Write smoke-check.sh (or smoke-check.ps1), which runs the checks for /health, /ready, classify with and without the key, and /docs. It takes the address from URL and the key from TICKET_API_KEY, never from the file. It prints PASS or FAIL for each check, and ends with exit code 1 if any check failed. What must it do when TICKET_API_KEY is not set?
Show one answer for macOS and Linux
#!/bin/sh# Smoke checks for the deployed ticket API.# Set URL and TICKET_API_KEY first. The key never goes in this file.if [ -z "$URL" ] || [ -z "$TICKET_API_KEY" ]; then echo "Set URL and TICKET_API_KEY first." >&2 exit 2fifail=0check() { name=$1; want=$2; shift 2 got=$(curl -s -o /dev/null -w "%{http_code}" "$@") if [ "$got" = "$want" ]; then echo "PASS $name $got" else echo "FAIL $name: got $got, expected $want" fail=1 fi}check health 200 "$URL/health"check ready 200 "$URL/ready"check classify 200 -X POST "$URL/v1/classify" -H "Content-Type: application/json" \ -H "X-API-Key: $TICKET_API_KEY" -d '{"subject":"Charged twice for March"}'check no-key 401 -X POST "$URL/v1/classify" -H "Content-Type: application/json" -d '{"subject":"x"}'check docs-hidden 404 "$URL/docs"exit $failThe output (simulated), first with the right key, then with a wrong one:
$ TICKET_API_KEY=$(cat secrets/api_key.txt) URL=http://127.0.0.1:8080 sh smoke-check.shPASS health 200PASS ready 200PASS classify 200PASS no-key 401PASS docs-hidden 404$ TICKET_API_KEY=wrong URL=http://127.0.0.1:8080 sh smoke-check.shPASS health 200PASS ready 200FAIL classify: got 401, expected 200PASS no-key 401PASS docs-hidden 404The exit codes were 0 and 1. Without the key, the script stops at once with exit code 2 and a clear message: it does not send requests that must fail. The check function sends one request and compares only the status code. That is enough for a smoke check: pytest checked the bodies before the deployment. The same script works for both routes. Only URL changes.
Common mistake
The search by tag finds nothing, but the resources are there
Symptom (live): Amira tagged the group with purpose=practice. To find all practice resources, she searches by the tag:
az resource list --tag purpose=practice --query "[].{name:name, type:type}" -o tableThe command prints nothing. But az resource list -g rg-ticket-api-practice lists six resources. A clean-up based on the tag search would say “nothing to delete” while the registry charges every day.
Cause: tags on a resource group are not passed to the resources in it. The group has the tag. The registry, the app and the others do not. --tag searches the resources’ own tags.
Fix:
- Make the inventory from the resource group:
az resource list -g $RG -o table. A group is the boundary that you delete, so it is the boundary that you list. - If you need tags on resources, set them on each resource, for example with
--tagson eachcreatecommand. Azure Policy can also require or copy tags, which is outside this course. - After the clean-up, check with commands that cannot miss a resource:
az group existsandaz group list.
For a real AI service: GPU servers and model storage cost much more than this registry. A team that cleans up “everything with the tag experiment” can leave a GPU server running for weeks. An inventory that you can prove complete (one group per experiment, listed before deletion) protects the budget better than a search.
Check your understanding
Why does a smoke check include requests that must fail, such as classify without a key and /docs?
Show the answer
They prove that the safety settings reached the deployment. A wrong deployment can pass every “happy” check: if the secret or REQUIRE_API_KEY is missing, classify without a key could answer 200, and the API would be open to anyone on the internet. If SHOW_DOCS is wrong, /docs shows the whole API to strangers. Only a request that must fail can show that. A deployment that answers these with 200 is broken, even if everything else passes.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
In the live logs, some requests come from 127.0.0.1 and others from 100.100.0.60. Which is which, and why does a GET / that you never sent matter?
Show the answer
127.0.0.1 is the probes: the platform runs them inside the replica, so they come from the replica itself. 100.100.0.60 is the platform’s ingress, and every request from outside comes through it, yours too. A GET / that nobody on the team sent shows that strangers find and call a public address within minutes. Programs that scan the internet do this. It wakes the app (and restarts the 300-second wait before it scales to zero), and it would reach an API without a key. That is why the first revision already required the key.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Module practice
Section titled “Module practice”Guided practice → independent task. You saw each step with Amira in the three lessons. Now do the whole module on your own: a live deployment if you have an Azure subscription, or the local simulation with the same configuration concepts. The evidence is one file, DEPLOY.md, in the project. It has three parts:
- Deployment checklist: each step with the command that you ran and the result that you saw. The image name with its digest, and its platform.
- Resource inventory: every resource, made before the deletion, with what it can cost and how you delete it.
- Clean-up record: each deletion and each check after it, with its result.
Label every result live or simulated. Do not copy the lesson’s outputs: use yours.
Show a model answer (shortened)
# Deployment record: ticket-api 1.1.0
Route: live (Azure Container Apps, canadacentral).
## Checklist- [x] Image built for linux/amd64 and pushed: ticketapi4821.azurecr.io/ticket-api:1.1.0@sha256:cfd04194eb1b... imagetools inspect: linux/amd64 (+ attestation)- [x] App created: https://ticket-api.nicewater-a307f95a.canadacentral.azurecontainerapps.io- [x] deploy/containerapp.yaml applied: revision ticket-api--0000001, Healthy, traffic 100- [x] Smoke checks (live): /health 200 (18 s cold start), /ready 200, classify 200, no key 401, /docs 404, http 301, history 503 (no database, expected)
## Inventory (before deletion)| Resource | Type | Can it charge? | How to stop it ||---|---|---|---|| ticketapi4821 | Container registry, Basic | Yes, US$0.17 per day | Delete the group || ... | ... | ... | ... |
## Clean-up record (live)| Step | Command | Result ||---|---|---|| Delete the group | az group delete --name rg-ticket-api-practice --yes --no-wait | started 16:36 || Group gone | az group exists --name rg-ticket-api-practice | false (17:04) || ... | ... | ... |The full example, with the simulated variant, is the DEPLOY.md of the snapshot end-of-m05. A reviewer marks it demonstrated when three things are true. The smoke checks include the failure paths. The inventory lists the automatic workspace. Every clean-up line has a check with its result, not only the delete command.
Module check
Section titled “Module check”Check your understanding
Retrieval: name the three probe types of Azure Container Apps, the endpoint that the ticket API uses for each, and what the platform does when each one fails.
Show the answer
Startup on /health: the platform waits (up to 30 failures, 60 s), then restarts the copy; the other probes wait for it. Liveness on /health: after 3 failures, the platform restarts the copy. Readiness on /ready: after 3 failures, the platform sends the copy no requests, but does not restart it. A new revision gets the traffic only when it is ready.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
Predict: you run az group delete --name rg-ticket-api-practice --yes and the command ends without an error. Which costs and which risks can still be left, and how do you check?
Show the answer
If the command ended, the group’s resources are gone, so the registry, the app and the workspace no longer charge. But a soft-deleted Key Vault is still there for 90 days: it costs nothing, but its secrets exist and its name is taken. Check with az keyvault list-deleted and purge it. Resources in other groups were never in the inventory, so check az group list for a practice group that you forgot. On your computer, the registry token and the pushed tags are still there until docker logout and docker image rm. With --no-wait, the command ends at once, so check az group exists until it says false.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
Completion check: show your DEPLOY.md. Does it document working access, the resource inventory and verified clean-up steps?
Show the answer
A complete DEPLOY.md has: the address (live) or http://127.0.0.1:8080 (simulated), and the image with its digest and platform; the smoke checks with their results, including 401 without a key and 404 for /docs; the inventory made before the deletion, with the automatic Log Analytics workspace (live) or the registry volume (simulated), and what each item can cost; and for each deletion, a check with its result: az group exists → false, az keyvault list-deleted → 0, empty group and role lists, docker logout (live), or empty docker ps -a and docker volume ls lists (simulated). Every result is labelled live or simulated. If a check is missing, the clean-up is assumed, not verified.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Recap
- A smoke check proves this deployment, and it includes the requests that must fail: no key →
401,/docs→404. - Wake an app at zero replicas before a timed check: the first request was a cold start of 18 s.
- The app’s logs show your requests, the probes (
127.0.0.1) and requests that you did not send. Search by request ID. - Clean up from an explicit inventory: list the group, delete it with
--no-wait, checkaz group exists, purge a soft-deleted Key Vault, check the groups and roles, remove the local copies. Record each result inDEPLOY.md. - Tags on a group are not passed to its resources. Make the inventory from the group.
Reset. To start this module again: live, the resource group must be gone (az group exists → false); simulated, run the simulated clean-up above. Your project and ticket-api:1.1.0 stay. To reset the project files, copy the snapshot end-of-m04. To see what Docker still stores, run docker system df.
The course. You began with “it works on my computer”. Now you can package an API in an image, with its operating system, Python and packages. You can build it fast and without secrets, and run it with its settings and a database. You can map it to cloud building blocks with their costs, deploy it with checks and limits, and remove it with proof. The final assignment asks you to do it again, on your own, from a fresh environment.
Next: Final assignment