# Azure Fundamentals

> Microsoft's cloud for people who know AWS or none: resource groups and subscriptions, the core compute/storage/database services, and Entra ID for identity.


---

# Azure Fundamentals

You open the Azure portal and the first thing it asks for is a "resource group." Then a "subscription." Then something called "Entra ID" wants to assign you a "role." None of these are the thing you actually wanted to build, and the docs assume you already know why they exist. That friction is the whole point of this guide: once the organizing model clicks, the rest of Azure stops feeling like a maze of dropdowns and starts feeling like a filing cabinet you can navigate in the dark.

We'll build the mental model first (the container hierarchy nobody explains up front), then walk the handful of services you'll actually touch, then look at identity and the gotchas that bite people in production.

## How to read this

Read the phases in order; each one assumes the last. If you already know AWS, phase 2 has a translation table that will save you the most time. If you're brand new to cloud, don't skip phase 1 - the hierarchy is the thing that makes everything else make sense. For the bigger "why clouds exist at all" picture, see /guides/cloud-platforms-explained.

## The phases

1. [The container hierarchy: how Azure is organized](01-the-container-hierarchy.md)
2. [The services you'll actually use](02-the-services-you-use.md)
3. [Identity, access, and production reality](03-identity-and-production.md)


---

# The container hierarchy: how Azure is organized

Here's the thing that trips up almost everyone on day one: in Azure, you can't create a single thing - not a server, not a database, not a storage bucket - until you've decided which *container* it lives in. The portal asks for a "resource group" before it asks for anything you care about, and it feels like bureaucracy blocking your path.

It isn't bureaucracy. It's the spine of the whole platform. Azure's billing, permissions, and lifecycle all hang off a four-level hierarchy. Learn the hierarchy and you've learned the part of Azure that everything else assumes you already understand.

## The four levels, top to bottom

Picture a set of nested boxes. From widest to narrowest:

```text
Management Group   →  optional grouping of subscriptions (org-wide policy)
  Subscription     →  the billing + quota boundary
    Resource Group →  a folder for things that share a lifecycle
      Resource     →  the actual VM, database, storage account, etc.
```

*What just happened:* every resource you ever create sits inside a resource group, which sits inside a subscription, which may sit inside a management group. You start reading from the bottom (the resource is what you want) but Azure makes you fill in the boxes from the top down.

Let's take them one at a time, because each box exists for a specific reason.

## Resource: the thing you actually wanted

A **resource** is any single managed service instance: one virtual machine, one storage account, one database, one web app. Each resource has a type (like `Microsoft.Compute/virtualMachines`) and lives in exactly one region (East US, West Europe, and so on). That's it - a resource is the leaf of the tree.

## Resource group: the folder with a lifecycle

A **resource group** is a folder, but a folder with opinions. The rule of thumb that actually matters: *put things in the same resource group when they live and die together.*

A web app, its database, and its storage account for one project? Same resource group. When the project is retired, you delete the resource group and everything inside it goes with it - one click, no orphans left billing you.

```bash
# Create a resource group named "blog-prod" in the East US region
az group create --name blog-prod --location eastus

# Later, tear the whole thing down - every resource inside it goes too
az group delete --name blog-prod --yes
```

*What just happened:* `az group delete` is the cleanest teardown in Azure. Anything you created inside `blog-prod` - the VM, the database, the disks - is deleted in one command. This is why grouping by lifecycle matters: the resource group is the unit of deletion.

A resource group itself has a region, but that region only stores the group's *metadata*. The resources inside it can live in different regions. Don't overthink the group's region - pick one near you and move on.

## Subscription: the billing and quota wall

A **subscription** is where money and limits live. Every resource bills to exactly one subscription, and quotas (like "how many CPU cores can you spin up in this region") are enforced at the subscription level.

This is the single most important boundary to get right early, because people use subscriptions to separate environments and teams:

```text
Subscription: "Acme Production"   → prod resource groups, locked-down access, the real bill
Subscription: "Acme Dev/Test"     → dev resource groups, looser access, separate budget
```

*What just happened:* by splitting prod and dev into separate subscriptions, a runaway script in dev can't blow the production budget, and a junior dev with full access to "Dev/Test" has zero access to "Production." The boundary does the enforcing for you.

> A common beginner mistake is cramming everything into one subscription and using resource groups to separate prod from dev. Resource groups are folders, not walls - they don't bound billing or quotas. Use subscriptions for that hard separation.

## Management group: the org-wide policy layer

A **management group** sits above subscriptions and exists for one job: applying rules across many subscriptions at once. If your company has fifty subscriptions and wants a policy like "no one may create resources outside European regions," you set that policy once on the management group and every subscription beneath it inherits it.

If you're an individual or a small team, you may never touch management groups - you'll have one or two subscriptions and that's plenty. They matter at organizational scale, where applying a setting fifty times by hand is how mistakes happen.

```text
Management Group: "Acme Corp" (policy: allowed regions = EU only)
├── Subscription: Acme Production
└── Subscription: Acme Dev/Test
```

*What just happened:* both subscriptions inherit the "EU only" guardrail from above. Policy flows *down* the tree - set it high, and everything below obeys.

## Why this design, not a flat list?

You might wonder why Azure didn't give you a flat bucket of resources with tags. The hierarchy buys you three things a flat list can't: a clean **deletion unit** (the resource group), a hard **billing and security boundary** (the subscription), and **inherited policy** (the management group). Each level answers a different question - "what gets cleaned up together," "who pays and who has access," and "what rules apply everywhere." Tags are still useful for cross-cutting labels like cost-center, but they don't replace the boxes.

**For builders:** when you script infrastructure, this hierarchy becomes your deployment scope. A Bicep or ARM template deploys *into* a resource group; a policy assignment targets a management group or subscription. Knowing which level a thing attaches to tells you where your automation has to point.

```quiz
[
  {
    "q": "You finish a project and want to delete the web app, its database, and its storage account in one action. What's the cleanest way?",
    "choices": [
      "Delete each resource individually from the portal",
      "Put them in one resource group and delete the resource group",
      "Delete the subscription",
      "Apply a management group policy that removes them"
    ],
    "answer": 1,
    "explain": "Resources in a resource group share a lifecycle; deleting the group deletes everything inside it with no orphans left billing you."
  },
  {
    "q": "Which level is the hard boundary for billing and quotas?",
    "choices": [
      "Resource group",
      "Resource",
      "Subscription",
      "Tag"
    ],
    "answer": 2,
    "explain": "Every resource bills to exactly one subscription, and quotas are enforced at the subscription level. Resource groups are folders, not billing walls."
  },
  {
    "q": "What is the main purpose of a management group?",
    "choices": [
      "To store the actual VMs and databases",
      "To apply policy across many subscriptions at once",
      "To act as a billing account for a single project",
      "To define the region a resource runs in"
    ],
    "answer": 1,
    "explain": "Management groups sit above subscriptions so a single policy (like allowed regions) is inherited by every subscription beneath them."
  }
]
```


---

# The services you'll actually use

Azure has hundreds of services and a marketing page for every one of them. You will use maybe six of them ninety percent of the time. This phase is those six: how to think about each one, when to reach for it, and - if you already know AWS - what it maps to so you can stop translating in your head.

We'll group them the way you'll reach for them: compute (run my code), storage (hold my files), and databases (hold my structured data).

## The AWS-to-Azure map (read this first if you know AWS)

If you've used AWS, the fastest way into Azure is a translation table. The concepts are nearly identical; the names are different and occasionally the boundaries are drawn in slightly different places.

```text
AWS                          Azure
-----------------------------------------------------
EC2                       →  Virtual Machines (VMs)
S3                        →  Blob Storage (in a Storage Account)
RDS (managed SQL)         →  Azure SQL Database
Lambda                    →  Azure Functions
Elastic Beanstalk        →  App Service
IAM (identity)            →  Entra ID + Azure RBAC
VPC                       →  Virtual Network (VNet)
CloudFormation           →  ARM / Bicep templates
Account (per env)         →  Subscription
```

*What just happened:* most of your AWS instincts transfer directly. The biggest mental shift is identity - AWS folds users, roles, and permissions into one service (IAM), while Azure splits *who you are* (Entra ID) from *what you can do* (RBAC). We cover that split in phase 3.

## Compute: three ways to run your code

Azure gives you a ladder of compute, from "I manage everything" to "I manage almost nothing." Climb only as high as you need.

**Virtual Machines (VMs)** are a whole server you rent - you pick the OS, you patch it, you install your stack. Maximum control, maximum responsibility. Reach for a VM when you have software that needs a specific OS setup, or you're lifting an existing server into the cloud as-is.

```bash
# Spin up an Ubuntu VM in the blog-prod resource group
az vm create \
  --resource-group blog-prod \
  --name web-01 \
  --image Ubuntu2204 \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys
```

*What just happened:* you now have a full Linux box you can SSH into. You also now own its security patches, its uptime, and its disk. That's the tradeoff for total control.

**App Service** runs your web app or API without you touching the server. You hand it your code (or a container) and it handles the OS, patching, scaling, and HTTPS. This is the sweet spot for most web apps - far less to manage than a VM, far more structure than raw functions.

```bash
# Deploy a web app onto a shared App Service plan
az webapp up --name acme-blog --resource-group blog-prod --runtime "NODE:20-lts"
```

*What just happened:* `az webapp up` packaged your local app, created the hosting plan if needed, and deployed it behind a public HTTPS URL - no VM, no OS to patch.

**Azure Functions** run a single piece of code in response to an event (an HTTP request, a queue message, a timer) and bill you only while it runs. This is serverless: no server to think about, scales to zero when idle. Reach for Functions when your work is bursty or event-driven - a webhook handler, a nightly job, an image-resize trigger.

> Rule of thumb for compute: start at App Service for web apps and Functions for event-driven snippets. Drop down to a VM only when you genuinely need to own the operating system. Every rung down the ladder is more work you're signing up to do forever.

## Storage: the Storage Account and Blob Storage

Almost all unstructured storage in Azure lives inside a **Storage Account** - a top-level container that holds blobs (files), queues, tables, and file shares. The piece you'll use most is **Blob Storage**, which is object storage for files of any size: images, backups, logs, videos, static website assets. It's the S3 equivalent.

The structure is: a storage account holds *containers*, and containers hold *blobs*.

```bash
# Upload a file to a blob container
az storage blob upload \
  --account-name acmestorage \
  --container-name uploads \
  --name avatar.png \
  --file ./avatar.png
```

*What just happened:* your file now lives as a blob in the `uploads` container inside the `acmestorage` account, reachable by URL and durable across hardware failures. You didn't provision a disk or worry about size - blob storage grows as you add to it and you pay for what you store.

Blob storage also has **access tiers** - Hot for data you read often, Cool for infrequent access, and Archive for long-term cold storage you rarely touch. Moving stale data to a colder tier cuts cost; the colder the tier, the cheaper the storage but the slower (and pricier) the retrieval.

## Databases: Azure SQL and the managed-database idea

When you need a real relational database without running the database server yourself, reach for **Azure SQL Database** - a fully managed SQL Server engine. Microsoft handles patching, backups, and high availability; you connect, create tables, and run queries.

```sql
-- Once connected to your Azure SQL Database, it's ordinary SQL
CREATE TABLE posts (
  id        INT IDENTITY PRIMARY KEY,
  title     NVARCHAR(200) NOT NULL,
  body      NVARCHAR(MAX),
  published DATETIME2 DEFAULT SYSUTCDATETIME()
);
```

*What just happened:* this is plain T-SQL against a database you never installed or patched. The "managed" part means the operational chores - backups, failover, version upgrades - are Microsoft's job, not yours. You're paying for someone else to be on call for the database.

Azure also offers managed PostgreSQL and MySQL (the **Azure Database for PostgreSQL/MySQL** family) and a multi-model NoSQL service called **Cosmos DB** for globally distributed, low-latency workloads. Start with Azure SQL or managed Postgres unless you have a specific reason - Cosmos DB is powerful but priced and modeled differently, and overspending is easy if you adopt it without needing its global-scale features.

**In the wild:** a typical small web product on Azure is one resource group containing an App Service for the app, an Azure SQL Database for the data, and a Storage Account for user uploads - three resources, one lifecycle, deleted together when the project ends. That's the shape most projects converge on, and it maps cleanly onto the hierarchy from phase 1.

```quiz
[
  {
    "q": "You're deploying a standard web API and want minimal server management without going fully serverless. Which compute service fits best?",
    "choices": [
      "Virtual Machines",
      "App Service",
      "A management group",
      "Blob Storage"
    ],
    "answer": 1,
    "explain": "App Service runs your web app without you managing the OS, patching, or scaling - the sweet spot for most web apps, between a raw VM and event-driven Functions."
  },
  {
    "q": "In AWS terms, Azure Blob Storage is most like which service?",
    "choices": [
      "EC2",
      "RDS",
      "S3",
      "Lambda"
    ],
    "answer": 2,
    "explain": "Blob Storage is object storage for files of any size - Azure's equivalent of S3. It lives inside a Storage Account."
  },
  {
    "q": "What does 'managed' mean when describing Azure SQL Database?",
    "choices": [
      "You must manually patch and back up the database server",
      "Microsoft handles patching, backups, and high availability for you",
      "It can only be created through a management group",
      "It stores files instead of relational tables"
    ],
    "answer": 1,
    "explain": "A managed database means the operational chores - patching, backups, failover, upgrades - are the provider's responsibility; you just connect and run SQL."
  }
]
```


---

# Identity, access, and production reality

This is the phase where Azure trips up the most people, including experienced AWS engineers. The reason is one design decision: Azure splits *who you are* from *what you're allowed to do* into two separate systems. AWS folds both into IAM; Azure does not. Once that split clicks, the permission errors stop feeling random and the production gotchas become predictable.

## Entra ID: who you are

**Entra ID** (formerly Azure Active Directory) is the identity service. It answers one question: *is this person or program who they claim to be?* It holds your users, groups, and the identities of applications. When you sign into the portal, Entra ID is what authenticated you.

Crucially, Entra ID does **not** decide what you can do with resources. It only proves identity. A brand-new user in Entra ID can sign in and see nothing - because identity and permission are separate.

> If you know AWS: Entra ID is the directory-of-users half of IAM, pulled out into its own service. It also doubles as the identity provider for Microsoft 365, which is why org accounts and Azure accounts are the same login.

## RBAC: what you're allowed to do

**Azure RBAC** (Role-Based Access Control) is the permission system. It answers: *what is this identity allowed to do, and where?* You grant access by creating a **role assignment**, which is always three things bolted together:

```text
Role assignment = WHO  +  WHAT  +  WHERE
                  (identity) (role) (scope)
```

*What just happened:* every grant of access in Azure is this triple. "Who" is an Entra ID user, group, or app. "What" is a role (a bundle of permissions like Reader, Contributor, or Owner). "Where" is the scope - a management group, subscription, resource group, or a single resource. Miss any of the three and the grant is meaningless.

Here's a real assignment:

```bash
# Give the user "dana" the Contributor role, scoped to the blog-prod resource group
az role assignment create \
  --assignee dana@acme.com \
  --role "Contributor" \
  --scope "/subscriptions/<sub-id>/resourceGroups/blog-prod"
```

*What just happened:* Dana can now create, modify, and delete resources - but *only inside blog-prod*. She has no access to anything else in the subscription. Scope is the dial that limits blast radius.

The three roles you'll use constantly:

- **Reader** - can look, can't touch. Good for auditors and dashboards.
- **Contributor** - can create, change, and delete resources, but *cannot grant access to others*. The everyday working role.
- **Owner** - Contributor plus the power to assign roles. Hand this out sparingly; an Owner can give anyone any access.

## Scope inheritance: the thing that surprises people

Permissions flow **downward**. A role assigned at the subscription level applies to every resource group and resource beneath it. This is convenient and dangerous in equal measure.

```text
Subscription      ← assign "Reader" here...
└── blog-prod     ← ...and it's inherited here automatically
    └── web-01    ← ...and here too
```

*What just happened:* one assignment at the top covers everything below - you don't re-grant at each level. The flip side: assigning at too high a scope gives someone broad access without meaning to. The fix is the same principle every time - **assign at the narrowest scope that does the job.** Don't grant at the subscription when the resource group would do.

## Managed identities: stop putting passwords in code

A classic production mistake is hardcoding a database password or storage key into your app's config. Azure's answer is the **managed identity**: an Entra ID identity that Azure creates and rotates for a resource (like your App Service), so the app authenticates to other Azure services *with no credentials in your code at all*.

```text
App Service (with managed identity)  →  needs to read Blob Storage
   1. Azure gives the app an Entra ID identity automatically
   2. You assign that identity a role (e.g. "Storage Blob Data Reader") on the storage account
   3. The app requests a token at runtime - no key stored anywhere
```

*What just happened:* the app proves its identity to storage without you ever handling a secret. There's no key to leak in a Git commit and nothing to rotate by hand. If you take one production habit from this guide, take this one.

## The gotchas that bite in production

A short list of the things that cost people a real afternoon:

- **"Access denied" with a valid login.** Almost always RBAC, not Entra ID. The user is authenticated but has no role at the right scope. Check the role assignments on the resource group, not the sign-in.
- **Subscription quota walls.** You try to create more VM cores than your subscription allows in a region and the deploy fails. Quotas are per-subscription, per-region, and per-VM-family - request an increase before a big rollout, not during.
- **Region mismatch latency.** Putting your app in one region and its database in another adds a network hop to every query. Keep tightly-coupled resources in the same region.
- **Forgotten resources keep billing.** A VM you "stopped" in the OS is still allocated and still charging unless you *deallocate* it (`az vm deallocate`). Stopping inside the guest OS is not the same as releasing the hardware.
- **Owner sprawl.** Handing out Owner because Contributor "didn't work" usually means the real need was a narrower role at a narrower scope. Owner should be rare.

> The single most common Azure support question is some flavor of "I can sign in but can't do anything." Train yourself to immediately separate the two questions - *am I authenticated?* (Entra ID) and *am I authorized?* (RBAC) - and you'll diagnose it in seconds instead of minutes.

**In the wild:** a healthy small-team setup looks like this - users grouped in Entra ID by team, Contributor granted to those groups at the resource-group scope (never the subscription), Owner held by one or two admins, and every app talking to storage and databases through managed identities with zero secrets in config. That's not advanced Azure; it's the baseline that keeps you out of trouble.

For the broader picture of how cloud platforms compare and when to pick one, see /guides/cloud-platforms-explained.

```quiz
[
  {
    "q": "A user can sign into the Azure portal but gets 'access denied' when creating a resource. Where is the problem most likely?",
    "choices": [
      "Entra ID - their identity is broken",
      "RBAC - they're authenticated but have no role at the right scope",
      "Blob Storage tiering",
      "The management group region setting"
    ],
    "answer": 1,
    "explain": "Signing in proves identity (Entra ID works). Being unable to act means no role assignment at the right scope - an RBAC issue. Azure separates who you are from what you can do."
  },
  {
    "q": "What three parts make up an Azure role assignment?",
    "choices": [
      "Region, tier, and quota",
      "Identity (who), role (what), and scope (where)",
      "VM, storage, and database",
      "Subscription, billing, and policy"
    ],
    "answer": 1,
    "explain": "Every grant of access in Azure is the triple: an identity, a role bundling permissions, and a scope defining where it applies. Miss one and the grant means nothing."
  },
  {
    "q": "Why use a managed identity for your App Service?",
    "choices": [
      "It makes the app run in more regions",
      "It lets the app authenticate to other Azure services with no credentials stored in code",
      "It lowers the storage tier automatically",
      "It assigns the Owner role to every user"
    ],
    "answer": 1,
    "explain": "A managed identity is an Entra ID identity Azure creates and rotates for the resource, so the app gets tokens at runtime with no secret to leak or rotate by hand."
  }
]
```
