Cost awareness
- Lesson 12
- 50 min
- Module 4: Cloud building blocks
Lesson 12 of 15 · Docker and Cloud Essentials for AI DevelopersCost awareness
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
Before Amira deploys anything, the help desk lead asks two questions: what will it cost, and how do we make sure that it stops costing? Answer both with numbers, and with a list of every resource and how to stop it.
Before you startWhat to know and to have ready
What you need to know
- The minimal design and its resources, from Regions and service types: Worked example: Amira’s minimal design.
- The identity and its role, from Identity and secrets: Worked example: the identity and its role.
- The optional Key Vault, from Identity and secrets: Optional stretch: the key in Key Vault.
What you need on your computer
- Your notes from the guided practice of Regions and service types and of Identity and secrets. The module practice turns them into a file.
- The project folder
~/projects/ticket-apiat the end of Module 3. The module practice addsdocs/cloud-design.mdto it. If the folder is missing, copy the snapshotend-of-m03, as in Use a snapshot. - Python 3, for one optional calculation.
- No Azure account. The Azure outputs are live outputs from the real run.
The prices come from the Azure Retail Prices API: region canadacentral, US dollars, pay-as-you-go. Prices change: check the current page before you deploy. The Azure steps were run with Azure CLI 2.88.0 and the containerapp extension 1.3.0b5, in region canadacentral.
What can charge
Section titled “What can charge”On your computer, a container that runs all month costs only electricity. In the cloud, every resource has one or more meters: things that Azure counts and charges for. They come in a few kinds:
| Kind of charge | Counted by | Example in the design |
|---|---|---|
| Existence | Time while the resource exists, used or not | The registry: per day |
| Compute | Time while a copy of the app runs, by its CPU and memory | The container app: per vCPU-second and per GiB-second |
| Requests | Number of requests | The container app: per million requests |
| Storage | GB kept, per month | The registry: images above the included storage |
| Data out (egress) | GB that leave Azure to the internet | Responses to clients |
| Logs | GB of logs collected | The Log Analytics workspace |
A replica is one running copy of the app’s container. The container app charges for compute only while at least one replica runs. Egress is data that leaves the provider’s network, for example the responses that go to your clients. Data that comes in is free, and the first 100 GB of egress each month is free. A small API like this one stays far below that.
These are the prices for the resources in the design:
| Meter | Price |
|---|---|
| Container Registry, Basic | US$0.1666 a day (about US$5 a month), while the registry exists, even if nothing uses it |
| Container Registry, data stored above the Basic included storage (10 GiB) | US$0.10 per GB a month |
| Container Apps, vCPU while active | US$0.000034 per vCPU-second |
| Container Apps, memory while active | US$0.000004 per GiB-second |
| Container Apps, vCPU while idle (only with a minimum of 1 or more replicas) | US$0.000004 per vCPU-second |
| Container Apps, requests | US$0.40 per million |
| Log Analytics, data collected | the first 5 GB a month for each billing account are free, then US$2.76 per GB |
| Key Vault (optional), operations | US$0.03 per 10,000 |
Container Apps also has a free grant. Each calendar month, each subscription gets 180,000 vCPU-seconds, 360,000 GiB-seconds and 2 million requests for free. A revision with zero replicas has no compute charge, and the platform’s health-probe requests are not charged.
Text description of the diagram
The resource group rg-ticket-api-practice holds six resources. The registry ticketapi4821 charges US$0.1666 a day for as long as it exists, even when nothing uses it; to stop the charge, delete it. The container app ticket-api charges for CPU and memory per second while a replica runs, and for requests, above the monthly free grant. At zero replicas there is no compute charge. Deleting the app stops everything. The Log Analytics workspace, which the environment made automatically, charges per GB of logs above 5 GB a month; delete it. The environment cae-ticket-api has no charge of its own in this design, and the identity id-ticket-api costs nothing; both go with the group. The optional Key Vault charges per 10,000 operations. After you delete it, it stays soft-deleted for 90 days. It does not charge then, but it keeps its name until you purge it. Outside the group, egress to the internet is free for the first 100 GB a month. A budget only sends an alert: it does not stop or delete anything, and cost data can arrive 8 to 24 hours late.
Worked example: a month of the ticket API
Section titled “Worked example: a month of the ticket API”The help desk lead asks: “If we leave it running, what does it cost a month?” Amira computes the worst simple case step by step. One replica runs all month, at the active rate, with 0.25 vCPU and 0.5 GiB.
| Step | vCPU | Memory |
|---|---|---|
| Seconds in 30 days | 30 x 24 x 3,600 = 2,592,000 s | 2,592,000 s |
| Use: size x seconds | 0.25 x 2,592,000 = 648,000 vCPU-s | 0.5 x 2,592,000 = 1,296,000 GiB-s |
| Minus the free grant | 648,000 - 180,000 = 468,000 vCPU-s | 1,296,000 - 360,000 = 936,000 GiB-s |
| Times the active price | 468,000 x 0.000034 = US$15.91 | 936,000 x 0.000004 = US$3.74 |
Compute: 15.91 + 3.74 = about US$19.66 a month. Add the registry, about US$5. Requests are free up to 2 million, and the logs of a small API stay under 5 GB. So “always on” costs about US$25 a month.
You can check the compute part with one line of Python:
python3 -c "print(round((0.25*2592000-180000)*0.000034 + (0.5*2592000-360000)*0.000004, 2))"The output:
19.66Now the other cases, with the same method:
| Setting | Compute a month | With the registry |
|---|---|---|
| Minimum 1 replica, busy all month (active rate) | US$19.66 | about US$25 |
| Minimum 1 replica, idle all month (idle rate: US$0.000004 for vCPU and memory) | US$5.62 | about US$11 |
| Minimum 0 replicas (scale to zero), no requests | US$0 | about US$5 |
| The practice: about 1 hour, then delete the group | US$0 (inside the free grant) | a few cents: at most one day of the registry, US$0.17 |
Look at the free grant another way: 180,000 vCPU-seconds is 200 hours of one 0.25 vCPU replica. The grant covers the practice many times over. It does not cover a forgotten app that runs all month.
Scale to zero, and the requests you did not send
Section titled “Scale to zero, and the requests you did not send”Scale to zero means that the platform stops every replica when no requests come, and starts one again when a request arrives. By default, Container Apps waits 300 seconds (5 minutes) without requests before it scales to zero. The first request after that waits while a replica starts. This wait is a cold start. In the real run, the first request after the deployment took 18 seconds, and the next requests took less than a second.
The real run also showed something that surprises many people. Minutes after the app got its public address, requests arrived that nobody on the team sent. Here is one, from the app’s log (live):
2026-10-07T16:17:06.5051432Z stdout F INFO: 100.100.0.60:49464 - "GET / HTTP/1.1" 404 Not FoundNobody on the team called /. Such requests come from programs that scan the internet for new addresses. They arrived at 16:17, 16:19 and 16:27. Each one woke the app, or restarted the 300-second wait before it scaled to zero. The app had a replica from 16:23 until at least 16:31.
Serverless is not free
Section titled “Serverless is not free”“Serverless” sounds as if there is nothing to pay and nothing to set. Both parts are wrong:
- It charges while a replica runs. Scale to zero helps only if the minimum is 0 and nothing sends requests. A minimum of 1 replica, to avoid cold starts, means a charge every second of the month.
- It needs configuration. You choose the size of each replica, the minimum and the maximum number of replicas, the ingress, the probes and the settings (Module 5). The defaults are not chosen for your budget. For example, the default maximum is 10 replicas. The course sets 2, so that a flood of requests cannot start 10.
- Other resources charge anyway. The registry charges every day, with zero replicas or not.
Budgets alert. They do not stop anything.
Section titled “Budgets alert. They do not stop anything.”A budget is an amount of money that you set for a scope, such as a subscription or a resource group, for a month. When the costs reach a percentage of it, for example 80 %, Azure sends an email alert.
A budget does not stop, limit or delete anything. Microsoft’s documentation says it directly: when a budget threshold is passed, “resources aren’t affected, and your consumption isn’t stopped”. Azure also collects cost data with a delay: usually 8 to 24 hours, and it checks budgets once every 24 hours. So the alert can come a day after the money was spent. AWS Budgets and Google Cloud budgets are also alerts first: to stop something, you must add an action or automation of your own.
You can create a budget in the Azure portal: open the resource group, select Budgets, then Add, and give an amount and an email address. The course does not test this step, and a budget is optional. Make one if your subscription is used for more than this practice. Then remember the rule once more: the budget tells you. Only deleting the resources stops the cost.
Tags, and a trap
Section titled “Tags, and a trap”A tag is a label, a name=value pair, that you put on a resource group or a resource, for example purpose=practice. Cost reports can group costs by tag, and you can search for resources by tag.
The real run had the trap. The group was created with --tags purpose=practice course=C18. Later, with all the resources still in the group, Amira searched by the tag:
az resource list --tag purpose=practice -o tableThe output was empty. The resources were there. Resources do not inherit the tags of their resource group. The group had the tag, and its resources did not. If you want a tag on each resource, give it in each create command (--tags), or use an Azure Policy that copies the group’s tags. For a clean-up check, do not trust a tag search: check the group itself, with az group exists and az resource list -g.
A resource inventory
Section titled “A resource inventory”An inventory is a list of every resource in a deployment, with what it can cost and how to stop it. It includes the resources that Azure made for you. You write it before the deployment, from the design. After the deployment, you check it against the real list. At the clean-up, you tick each line. Module 5 uses it.
The column that matters most is “Can it charge, and how do I stop it?”. Three resources need care:
- The Log Analytics workspace. Nobody asked for it.
az containerapp env createprintedGenerating a Log Analytics workspace with name "workspace-..."and made it. If you delete only the app and the environment, the workspace stays. - The registry. It charges every day, even with no app.
- The Key Vault (optional). After you delete it, it stays soft-deleted for 90 days. It does not charge, but its name stays taken.
az keyvault list-deletedshows it, andaz keyvault purge --name kv-ticket-api-4821removes it.
Some things are not in az resource list at all: the role assignment, and a soft-deleted vault. Write them in the inventory anyway.
Guided practice: price your design
Section titled “Guided practice: price your design”- Take the resource list of your design from the guided practice of Regions and service types.
- For each resource, write the kind of charge from the first table: existence, compute, requests, storage, egress, logs, or none.
- Compute the practice cost: 1 hour of one replica, then the group is deleted. Is the compute inside the free grant?
- Compute the month with a minimum of 1 replica, busy all month. Use the table of the worked example.
- Write down which resource still charges if you delete only the container app.
Show the expected answers
- Registry: existence (per day), and storage only above 10 GiB. Container app: compute and requests. Workspace: logs. Environment, identity, role assignment: none. Key Vault: requests to the vault (operations).
- 1 hour: 0.25 x 3,600 = 900 vCPU-s and 0.5 x 3,600 = 1,800 GiB-s. That is far inside the free grant, so the compute costs US$0. The practice costs at most one day of the registry, US$0.17.
- A month at the active rate with a minimum of 1 replica: about US$19.66 of compute, plus about US$5 for the registry.
- If you delete only the app, the registry still charges every day, and the workspace can still charge for logs. Delete the whole group.
Your turn: the lead wants no cold start
Section titled “Your turn: the lead wants no cold start”The help desk lead says: “18 seconds for the first request is too slow. Keep one replica running.” Change the design for this one condition. What changes in the cost, and what do you tell the lead?
Show one answer
Set the minimum to 1 replica. The app then never scales to zero, so it charges every second of the month. If it is idle most of the time, it bills at the idle rate: about US$5.62 a month for compute. When requests keep it busy, it bills at the active rate, up to about US$19.66. Add the registry, about US$5. Tell the lead the range: US$11 to US$25 a month, at the prices in this lesson. Suggest a budget alert on the group. Also say that a budget only warns: if the cost must stop, someone must lower the minimum or delete the app.
Common mistake
Scale to zero is set, but the app does not scale to zero
Symptom: The app has a minimum of 0 replicas. Nobody on the team uses it. But the replica count stays at 1, and the compute charge grows. In the real run, the app had a replica from 16:23 to at least 16:31, with no requests from the team.
Cause: Requests that you did not send. A public address gets requests from programs that scan the internet, such as GET / from 100.100.0.60 in the log. Each request restarts the 300-second wait before the app scales to zero. “Serverless” did its job: it ran the app because requests came.
Fix:
- Look at the app’s log for requests that your team did not send, for example paths that your API does not have.
- Keep the maximum number of replicas low (2 in the course), so that a burst of such requests cannot start many replicas.
- For a practice deployment, delete the resource group as soon as you have your evidence. Then nothing can wake it.
- For a real service, these requests are normal. Put them in the estimate, and make sure that every endpoint that costs money needs the API key.
For a real AI service, this matters more: a model endpoint that anyone can call can run up a large bill. Protect it with a key, limit the replicas, and watch the costs.
Module practice
Section titled “Module practice”Guided practice → independent task. The worked examples of this module made Amira’s design step by step. Now write your own design for the practice deployment, as a file in the project. Module 5 builds from it.
- In
~/projects/ticket-api, create the folderdocsand the filedocs/cloud-design.md. - Write a section What the service needs: each need, its building block and its Azure resource, from your notes of Lesson 1.
- Write the region and the resource group, with the tags.
- Write Identity and access, from your access plan of Lesson 2. Say that the registry’s admin user stays off.
- Write Settings and secrets: each setting, its value, whether it is secret, and where it lives. Do not write the key itself.
- Write the compute settings and a cost estimate: the practice, and one replica running all month.
- Write the resource inventory, with a column “Can it charge, and how do I stop it?”. Include the resources that you did not create yourself, and the ones that
az resource listdoes not show. - Write the decisions: what you left out, and why.
Check your file against these questions: Can someone who did not read this module delete everything with it? Does it name every resource that can charge? Is there any secret value in it?
Show the model answer: docs/cloud-design.md
# Cloud design: ticket-api practice deployment
A minimal design to run `ticket-api:1.1.0` on Azure Container Apps for one practice session, then delete everything. Written before the deployment (Module 4). Module 5 builds it, and `DEPLOY.md` records what was made and removed.
Prices from the Azure Retail Prices API, canadacentral, USD, pay-as-you-go. Check the current prices before you deploy.
## What the service needs
| Need | Building block | Azure resource ||---|---|---|| Run the container from the image | Serverless containers | Container app `ticket-api` in the environment `cae-ticket-api` || A place for the image | Container registry | `ticketapi4821` (Basic) || A public HTTPS address | HTTPS ingress | The app's ingress: external, target port 8000 || Settings | The app's configuration | `REQUIRE_API_KEY=true`, `SHOW_DOCS=false` || The API key | A secret | Container Apps secret `api-key`, used as `API_KEY=secretref:api-key` || Permission to pull the image | A managed identity | `id-ticket-api` (user-assigned), role AcrPull on the registry || Logs | A log store | Log Analytics workspace (made automatically by the environment) || History records | Managed PostgreSQL | None: left out on purpose (see Decisions) |
## Region and resource group
- Region: `canadacentral` (the course's region; use the nearest region that has Container Apps). Every resource is in the same region.- Resource group: `rg-ticket-api-practice`, tags `purpose=practice` and `course=C18`. Every resource goes in this group, so one delete removes them all.
## Identity and access
| Identity | Role | Scope | Why ||---|---|---|---|| Me (the person who deploys) | Owner or Contributor | My subscription, or only the practice group | Create and delete the practice resources || `id-ticket-api` | AcrPull | The registry `ticketapi4821` only | Pull the image, nothing more (least privilege) |
The registry's admin user stays **off**. No registry password exists in this design.
## Settings and secrets
| Name | Value | Secret? | Where ||---|---|---|---|| `API_KEY` | A new random key, made for this deployment | Yes | Container Apps secret `api-key`, referenced with `secretref:` || `REQUIRE_API_KEY` | `true` | No | Environment variable: without a key, the app does not start || `SHOW_DOCS` | `false` | No | Environment variable || `DATABASE_URL` | not set | (would be a secret) | Not used: history is off |
The key is never in the image, in Git, in a plain environment variable or in this file. Optional stretch: keep the key in a Key Vault (`kv-ticket-api-4821`) and reference it with `keyvaultref:` and `identityref:`; the identity then also gets Key Vault Secrets User on that vault only.
## Compute settings
0.25 vCPU and 0.5 GiB for each replica, minimum 0 replicas (scale to zero), maximum 2 replicas. The maximum is a cost limit as well as a scale limit.
## Cost estimate
| Case | Estimate ||---|---|| The practice: about 1 hour, then delete the group | A few cents: at most one day of the registry (US$0.17). The compute, the requests and the logs stay inside the monthly free amounts. || One replica running all month at the active rate | 648,000 vCPU-s - 180,000 free = 468,000 x US$0.000034 = US$15.91; 1,296,000 GiB-s - 360,000 free = 936,000 x US$0.000004 = US$3.74; total about **US$19.66** a month, plus the registry, about US$5. || The same with scale to zero and no traffic | US$0 for compute, plus the registry, about US$5 a month. |
Serverless is not free: it charges while a replica runs, and requests that I did not send can start a replica.
## Resource inventory
| Resource | Type | Made by | Can it charge? | How do I stop it? ||---|---|---|---|---|| `rg-ticket-api-practice` | Resource group | Me | No (it is a folder) | `az group delete --name rg-ticket-api-practice --yes`; this deletes everything in it || `ticketapi4821` | Container registry, Basic | Me | **Yes**: US$0.1666 a day while it exists, even if nothing uses it; storage above the 10 GiB included | Delete the registry (or the group) || `id-ticket-api` | User-assigned managed identity | Me | No | Delete it (or the group) || AcrPull for `id-ticket-api` on the registry | Role assignment | Me | No. Not listed by `az resource list` | Nothing to stop: Azure removes it with the registry and the identity (checked with `az role assignment list --all`). It is listed here so that I know it exists || `cae-ticket-api` | Container Apps environment (Consumption) | Me | No charge of its own in this design | Delete it (or the group) || `workspace-rgticketapipractice...` | Log Analytics workspace | **The environment, automatically** | **Yes**: per GB of logs above 5 GB a month (US$2.76 per GB) | Delete it (or the group). I did not ask for it, so I must look for it || `ticket-api` | Container app | Me | **Yes**: vCPU-seconds and GiB-seconds while a replica runs, and requests, above the monthly free grant | Scale to zero stops the compute charge; delete it (or the group) to stop everything || Data out to the internet | Egress | Responses to clients | Only above 100 GB a month | Delete the app || Optional: `kv-ticket-api-4821` | Key Vault | Me | **Yes**, a little: US$0.03 per 10,000 operations | Delete it (or the group), then `az keyvault purge --name kv-ticket-api-4821`. A deleted vault is soft-deleted for 90 days: no charge, but its name stays taken || Optional: a budget on the group | Cost Management budget | Me | No. It only sends an alert; it does not stop anything | Delete it after the practice || On my computer: the pushed image tags and the registry login | Local images, Docker credentials | Me | No | `docker image rm ticketapi4821.azurecr.io/ticket-api:1.1.0`, `docker logout ticketapi4821.azurecr.io` |
Checks after the clean-up: `az group exists --name rg-ticket-api-practice` returns `false`, and `az keyvault list-deleted` does not list the vault. Note: `az resource list --tag purpose=practice` is **not** a check, because the resources do not inherit the group's tag: it returned nothing even while the group still held its resources.
## Decisions
- **No database.** A managed PostgreSQL server charges for every hour that it exists. Without `DATABASE_URL`, the API classifies normally, and `/v1/history` returns `503 history_unavailable`. A real service adds Azure Database for PostgreSQL flexible server, in the same region, with `DATABASE_URL` as a secret.- **No custom domain, no private network, no availability zones.** The platform's address and HTTPS are enough for a practice service.- **No data that matters.** The practice uses a new key and test tickets only, and the service is deleted the same day.Your file can use other words and another order. It is complete when its inventory has every resource, and a way to stop each one. Check for the workspace that the environment makes, the role assignment and the soft-deleted vault. It must contain no secret value.
Module check
Section titled “Module check”Check your understanding
Retrieval: what is a budget in Azure, and what does it do when the costs pass it?
Show the answer
A budget is an amount of money that you set for a scope and a period. The scope can be a subscription or a resource group, and the period is usually a month. When the actual or the forecast costs reach a percentage that you choose, Azure sends an alert, for example an email. It does not stop, limit or delete anything: the resources keep running and keep charging. Cost data can also arrive 8 to 24 hours late. Only deleting or changing the resources stops the cost.
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: Amira deletes the container app and the environment, but not the resource group. Two weeks later, what is still on the bill, and why?
Show the answer
The registry: it charges US$0.1666 a day while it exists, so about US$2.33 for 14 days, even with no app. The Log Analytics workspace also still exists, because the environment made it as a separate resource and deleting the environment does not delete it. It charges only for logs above 5 GB a month, so almost nothing now, but it is still a resource that she must remember. The identity costs nothing but is still there. Deleting the whole group would have removed all of them at once.
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: list every resource in the practice design that could continue to make charges, or stay behind, after the practice, and say how you stop each one.
Show the answer
The container app ticket-api: compute while a replica runs, and requests; scale to zero stops the compute, deleting the app stops all. The registry ticketapi4821: every day while it exists; delete it. The Log Analytics workspace that az containerapp env create made automatically: per GB of logs above 5 GB a month; delete it. If you made it, the Key Vault: per 10,000 operations. Delete it, then purge it. A deleted vault stays soft-deleted for 90 days and keeps its name, but it no longer charges. Egress above 100 GB a month stops with the app. The environment, the identity and the role assignment do not charge. Deleting the resource group rg-ticket-api-practice removes all of them except the soft-deleted vault’s name. A budget, if made, charges nothing and stops nothing. Check with az group exists and az keyvault list-deleted, not with a tag search.
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
This module mapped the ticket API to cloud building blocks, before anything was created:
- Every cloud has the same building blocks: compute, storage, databases, networking and managed services. Choose the region for distance, data residency, price and the services in it.
- Serverless moves the servers to the provider. Your image, its packages, your settings, your identities and your clean-up stay yours.
- Put a practice deployment in one resource group, so that one delete removes it.
- Give the app a managed identity with the smallest role at the narrowest scope (AcrPull on one registry). Keep the key in a platform secret or in Key Vault, never in a plain setting.
- Each resource charges for something different: existence, compute, requests, storage, egress, logs. The practice costs a few cents. A forgotten app with one replica costs about US$25 a month.
- A budget alerts. It does not stop anything. A resource inventory says how to stop each charge, and it includes the workspace that you did not ask for.
Reset: this module creates nothing in the cloud and changes no code. Your project has one new file, docs/cloud-design.md. To start again, delete it, or copy the snapshot end-of-m04. If you created any Azure resource on your own while you read this module, delete its resource group now.
Next, you build the design: you push the image to a registry and deploy it, live on Azure or simulated on your computer.
Next: Registry and deployment