Cloud for AI engineers
Access policies and least privilege, storage, compute choices, managed model endpoints, secrets and budgets.
Create a free account Try part of it here
Why it matters
Most AI products run on one of the big clouds, and the first week of an AI engineering job often starts with a cloud account: credentials to request, a bucket of documents to index, a model endpoint to call, a budget to stay inside. None of it is hard once you know the handful of ideas every provider shares. This lesson builds those ideas with small Python models of them, so they make sense whichever cloud your team uses.
What you'll be able to do
- Read a cloud access policy and grant a service only what it needs
- Choose between virtual machines, containers and serverless for an AI workload
- Call models through a cloud provider's managed endpoint, and say what that changes
- Keep secrets out of code and images, and set budgets that warn before a bill surprises you
What it covers
- What the cloud rents you
- Identity and access
- Decide a request
- Storage: buckets and objects
- Compute: machines, containers, functions
- Managed model endpoints
- Secrets without long-lived keys
- Costs and budgets
- When do the alerts fire?
- A checklist for your first cloud project
- Do it for real: a small cloud deployment with a budget
Show the answer
Read access to that one bucket
Give a service only what its job needs. If the app is ever tricked or its credentials leak, the damage stays inside that one bucket.
What the cloud rents you
A cloud provider rents you computers, storage and ready-made services by the hour, the gigabyte or the request, instead of you buying hardware. The three biggest are Amazon Web Services, Google Cloud and Microsoft Azure. Their names differ, but the building blocks match:
- identity and access: who may do what (IAM);
- storage: files kept as objects in buckets;
- compute: virtual machines, containers or functions that run your code;
- managed services: databases, queues and model endpoints someone else runs for you;
- billing: a meter on all of the above, with budgets and alerts.
Everything lives in a region, a group of data centers in one part of the world. Pick one near your users and allowed by your data rules, because moving data between regions costs time and money.
Identity and access
Every request to a cloud is made by a principal: a person, or a service account that a program uses. A policy lists statements that allow or deny actions (like storage:read) on resources (like one bucket). Three rules decide almost every case:
- Nothing is allowed unless a statement allows it.
- An explicit deny beats any allow.
- Patterns with
*match many actions or resources, so read them carefully.
from fnmatch import fnmatch policy = [ {"effect": "Allow", "action": "storage:read", "resource": "bucket/docs/*"}, {"effect": "Deny", "action": "storage:*", "resource": "bucket/docs/private/*"}, ] def matches(stmt, action, resource): return fnmatch(action, stmt["action"]) and fnmatch(resource, stmt["resource"]) hits = [s["effect"] for s in policy if matches(s, "storage:read", "bucket/docs/faq.md")] print(hits)
The principle behind it all is least privilege: give each principal the smallest set of actions on the smallest set of resources that lets it do its job.
from fnmatch import fnmatch policy = [ {"effect": "Allow", "action": "storage:read", "resource": "bucket/docs/*"}, {"effect": "Deny", "action": "storage:*", "resource": "bucket/docs/private/*"}, ] hits = [s["effect"] for s in policy if fnmatch("storage:read", s["action"]) and fnmatch("bucket/docs/private/salaries.csv", s["resource"])] print(hits)
Show the answer
['Allow', 'Deny']
Both statements match: the allow covers everything under docs, and the deny covers the private folder. Since a deny beats an allow, this read is refused.
Decide a request
Write allowed(policy, action, resource): return True only if some statement with effect "Allow" matches the action and resource (with fnmatch) and no statement with effect "Deny" does.
from fnmatch import fnmatch POLICY = [ {"effect": "Allow", "action": "storage:read", "resource": "bucket/docs/*"}, {"effect": "Allow", "action": "model:invoke", "resource": "model/*"}, {"effect": "Deny", "action": "storage:*", "resource": "bucket/docs/private/*"}, ] def allowed(policy, action, resource): return any(s["effect"] == "Allow" for s in policy) print(allowed(POLICY, "storage:read", "bucket/docs/faq.md"), allowed(POLICY, "storage:read", "bucket/docs/private/pay.csv"))
Write a sentence or two, then compare it with a good answer in the lesson.
Storage: buckets and objects
Object storage keeps files as objects in buckets, each object found by a key like docs/returns/policy.md. The slashes look like folders but are just part of the name, which is why one pattern can cover a whole "folder". It's cheap, durable and where most AI data lives: documents to index, training sets, logs.
Two habits matter most. Keep buckets private, and when someone outside needs one file, hand them a presigned URL: a link signed by your credentials that works for one object and expires, say, in fifteen minutes. And add lifecycle rules that move old data to colder, cheaper storage or delete it after a set time, so last year's logs don't quietly cost you every month.
Show the answer
A presigned URL for that file that expires soon
A presigned URL grants access to one object for a short time, without changing the bucket or sharing any credentials.
Compute: machines, containers, functions
You can run code three main ways. A virtual machine is a whole computer you manage, billed while it's on. A container service runs your Docker image and handles machines for you; many restart and scale it automatically. Serverless functions run a small piece of code per request, cost nothing while idle, and suit short, bursty work, but they start slowly after a quiet spell and usually have a time limit.
For AI work there's one more question: does it need a GPU? Calling a hosted model doesn't, since the provider's GPUs do the work. Serving your own open model, or fine-tuning one, does, and GPU machines are the most expensive thing in most cloud bills.
Match the shape of the work to the way it runs: always on, packaged and scaled, or only when called.
Managed model endpoints
Each big cloud also sells access to frontier models through its own account: Amazon Bedrock, Google Cloud's Vertex AI and Microsoft's Azure AI Foundry, for example, offer Claude among others. The models are the same, but the plumbing changes. You authenticate with the cloud's IAM instead of a separate API key, usage lands on the same bill as everything else, and you can keep traffic and data inside a chosen region, which some companies' data rules require.
Two things catch people out. Each region has quotas, a cap on tokens or requests per minute, which you may need to ask to raise before launch. And model names and available versions differ slightly per provider, so keep the model id in configuration rather than scattered through the code.
Show the answer
The environment at run time, from the platform's secret store
Images and repositories get copied and shared; secrets belong in the platform's secret manager, injected as environment variables when the container starts.
Write a sentence or two, then compare it with a good answer in the lesson.
Secrets without long-lived keys
A secret manager stores credentials encrypted, controls who may read each one, logs every read and can rotate them on a schedule. Your service reads a secret at start-up, so changing it never means rebuilding the image.
Better still, avoid long-lived keys altogether. Clouds can give a running service its own identity, often called workload identity, and hand it short-lived credentials automatically. There's then no key to leak at all, and access is still checked by the same IAM policies.
Costs and budgets
Cloud bills grow quietly: a forgotten GPU machine, logs nobody deletes, or data flowing out to the internet, which providers charge for as egress. Model calls add a fourth meter, priced per token. Estimate before you build:
hours = 24 * 30 container = 2 * hours * 0.05 # two small containers at $0.05 an hour storage = 200 * 0.02 # 200 GB at $0.02 per GB-month egress = 50 * 0.09 # 50 GB out at $0.09 per GB tokens = 30_000_000 / 1_000_000 * 4 # 30 million input tokens at $4 per million print(round(container + storage + egress + tokens, 2))
Then set a budget with alerts, say at 50%, 90% and 100% of what you expect, sent to people who will act on them. Budgets warn rather than stop spending, so the provider's own spending caps on model usage are worth setting too.
spend = [30, 45, 60, 80, 95, 120] # spend so far, by day budget = 100 crossed = [day for day, total in enumerate(spend, start=1) if total >= 0.9 * budget] print(crossed[0] if crossed else None)
Show the answer
5
The 90% alert fires the first day spend reaches $90, which is day 5 at $95. Alerts on spend so far give you a day to act before the budget is gone.
When do the alerts fire?
Write alerts(spend_by_day, budget, thresholds=(0.5, 0.9, 1.0)): spend_by_day holds each day's spend (not the total). Return a list of (threshold, day) pairs: for each threshold, the first day (counting from 1) on which the running total reaches threshold × budget. Leave out thresholds never reached.
def alerts(spend_by_day, budget, thresholds=(0.5, 0.9, 1.0)): return [] print(alerts([10, 12, 15, 14, 20, 18, 25], 100))
Write a sentence or two, then compare it with a good answer in the lesson.
A checklist for your first cloud project
- One project or account per environment (development, production), so a test can't touch real data.
- Service accounts with least privilege, and no personal logins inside apps.
- Secrets in the secret manager, ideally replaced by workload identity.
- Private buckets, presigned URLs for sharing, lifecycle rules for old data.
- A region chosen for your users and your data rules, with model quotas checked before launch.
- A budget with alerts at 50%, 90% and 100%, and spending caps on model usage.
- Logs and metrics turned on from day one, so the first incident isn't also the first time you look.
Show the answer
Separate accounts for staging and production
If staging can only reach staging resources, a bug there can't touch production. Separate accounts or projects make that the default.
Do it for real: a small cloud deployment with a budget
Deploy one of your services (the capstone, or any FastAPI app) on a real cloud account, set up the way a careful team would: its own service account with least privilege, secrets in the secret manager, and a budget with alerts. Many providers have free tiers or starting credits, so this can cost nothing.
Paste your service account's policy (or a written summary of its permissions) and your budget settings into the starter, which checks them with this lesson's rules. Then ask the tutor to review the deployment against the rubric.
It must
- Create a separate project or account for this deployment, not your personal default
- Run the service with its own service account whose permissions list only the actions it uses
- Store any model or database credentials in the secret manager (or use workload identity)
- Create a budget with alerts at 50%, 90% and 100%, sent to an address you read
- Write down how you'd shut everything down, and do it once the review is done
Go further
- Use a managed model endpoint through the cloud account instead of a separate API key
- Add a lifecycle rule that deletes logs after 30 days
POLICY = [
{"effect": "Allow", "action": "storage:read", "resource": "bucket/docs/*"},
{"effect": "Allow", "action": "secrets:read", "resource": "secret/model-key"},
{"effect": "Allow", "action": "model:invoke", "resource": "model/claude-opus-5-5"},
]
BUDGET = {"amount": 50, "alerts": [0.5, 0.9, 1.0], "notify": "ops@yourteam.example"}
def review(policy, budget):
notes = []
for s in policy:
if s["effect"] == "Allow" and ("*" in s["action"] or s["resource"] in ("*", "bucket/*")):
notes.append(f"Broad permission: {s['action']} on {s['resource']}. Can it be narrower?")
if not budget.get("amount"):
notes.append("Set a budget amount")
if sorted(budget.get("alerts", [])) != [0.5, 0.9, 1.0]:
notes.append("Use alerts at 50%, 90% and 100%")
if "@" not in budget.get("notify", ""):
notes.append("Send the alerts to an address someone reads")
return notes
for note in review(POLICY, BUDGET) or ["Nothing to flag. Ask the tutor to review the deployment."]:
print("-", note)
Write a sentence or two, then compare it with a good answer in the lesson.
Practice what you just read
- 2 code examples you can edit and run
- a practice exercise that checks your code
- a 6-question quiz
- 5 review cards for spaced repetition
Try a quiz question
A policy allows storage:read on bucket/docs/* and denies storage:* on bucket/docs/private/*. Can the service read bucket/docs/private/a.txt?