Accounts, Organizations & billing
Multi-account isolation, SCPs, consolidated billing.
A ship stays afloat after a hull breach because of bulkheads, watertight walls that trap the flooding in one compartment instead of letting it fill the whole vessel. An AWS (Amazon Web Services) account is the cloud's bulkhead: a container with its own 12-digit number, its own resources, its own permission system, its own usage limits, and its own line on the bill. Nothing crosses that wall unless you deliberately cut a door in it. Which is why architects draw the account layout *before* they draw the workloads. AWS Organizations links many accounts into one governed tree, Service Control Policies cap what each compartment is allowed to do, and consolidated billing folds every compartment into a single invoice. Many bulkheads, one bridge, one bill.
The account is the isolation boundary
Everything you make in AWS lives inside exactly one account: an EC2 (Elastic Compute Cloud) virtual machine, an S3 (Simple Storage Service) bucket of files, an IAM (Identity and Access Management) user. By default none of it is visible from any other account. Permissions do not reach across the wall unless somebody wires up cross-account trust on purpose, so a leaked access key in the dev account cannot touch production if production is its own account. Service quotas are per-account too. A runaway test job that eats the EC2 instance limit starves dev and leaves production untouched. Architects call this blast radius, the amount of stuff one mistake or one stolen key can wreck, and the account boundary is the strongest tool AWS gives you for shrinking it. So make this your first move in any terminal, before you build anything: confirm whose account your credentials point at.
# Which account do these credentials belong to? Check before you build.aws sts get-caller-identity{"UserId": "AIDAQ3EXAMPLE7EXAMPLE","Account": "111111111111","Arn": "arn:aws:iam::111111111111:user/admin"}# Is this account already part of an organization?aws organizations describe-organization \--query 'Organization.{Id:Id,FeatureSet:FeatureSet,Payer:MasterAccountId}'{"Id": "o-a1b2c3d4e5","FeatureSet": "ALL","Payer": "111111111111"}
AWS Organizations: many accounts, one place to run them
AWS Organizations is a free service that arranges accounts into a tree, the way a company org chart arranges people. Whichever account creates the organization becomes the management account: it pays every bill and runs the hierarchy. Everything else is a member account. The tree starts at one root, a top-level container that has nothing to do with the root *user*, and branches into organizational units (OUs), folders for accounts that need the same governance. You can nest them five levels deep. Two feature sets exist. CONSOLIDATED_BILLING gives you shared billing and nothing more, while ALL adds policy-based control. Service Control Policies need ALL, so create the organization with ALL every time. Exam writers love that detail. Making an account is asynchronous (you ask, and AWS finishes the job in the background), and when it lands there is already an IAM role waiting inside it, OrganizationAccountAccessRole unless you name another, that the management account can assume. You have a way in from day one without anyone sharing a password.
# 1. Create the organization (run once, from the future management account)aws organizations create-organization --feature-set ALL# 2. Find the root container's idaws organizations list-roots --query 'Roots[0].Id'"r-9x2b"# 3. Carve out an OU for workloadsaws organizations create-organizational-unit \--parent-id r-9x2b --name Workloads{"OrganizationalUnit": {"Id": "ou-9x2b-8f00qq1z","Arn": "arn:aws:organizations::111111111111:ou/o-a1b2c3d4e5/ou-9x2b-8f00qq1z","Name": "Workloads"}}# 4. Create a member account — asynchronous, returns a request idaws organizations create-account \--email [email protected] --account-name prod \--iam-user-access-to-billing DENY{"CreateAccountStatus": {"Id": "car-1234567890abcdef01234567890abcde","AccountName": "prod","State": "IN_PROGRESS","RequestedTimestamp": "2026-07-13T09:14:02.113000+00:00"}}# 5. Poll until SUCCEEDED, then file the new account under the OUaws organizations describe-create-account-status \--create-account-request-id car-1234567890abcdef01234567890abcde \--query 'CreateAccountStatus.{State:State,AccountId:AccountId}'{ "State": "SUCCEEDED", "AccountId": "222233334444" }aws organizations move-account --account-id 222233334444 \--source-parent-id r-9x2b --destination-parent-id ou-9x2b-8f00qq1z# (no output on success)
Two practical details. Every account needs a globally unique email address, so teams use plus-addressing: [email protected] and [email protected] both land in one mailbox that somebody actually reads. And a brand-new organization is capped at ten accounts. That is a soft quota the management account raises through the Service Quotas console, not a ceiling on your design.
Service Control Policies: a ceiling, never a key
Some rental vans have a speed limiter bolted to the engine. It stops you going over 65 mph. It cannot make the van move, and it cannot hand you the keys. A Service Control Policy (SCP) works the same way. You attach it to the root, to an OU, or to a single account, and it sets the *maximum* of what anyone inside is allowed to do. It never grants a thing. For an API call (any request to AWS, whether clicked in the console, typed at the command line, or made by code) to succeed, the user or role has to be allowed by its own IAM policy *and* by every SCP above it in the tree. The effective permissions are the overlap. Straight out of the box AWS attaches a wide-open FullAWSAccess SCP everywhere, so nothing is blocked until you block it. Most teams work deny-list style: leave FullAWSAccess alone and stack narrow Deny statements on top. Here is one that stops everybody, account administrators included, from tampering with CloudTrail, the service that records every API call made in the account:
# deny-audit-tamper.json — an SCP is just an IAM-style policy document{"Version": "2012-10-17","Statement": [{"Sid": "ProtectAuditTrail","Effect": "Deny","Action": ["cloudtrail:StopLogging","cloudtrail:DeleteTrail","cloudtrail:UpdateTrail"],"Resource": "*"}]}# Create the policy, then pin it to the Workloads OUaws organizations create-policy \--name deny-audit-tamper \--type SERVICE_CONTROL_POLICY \--description "Nobody disables CloudTrail, admins included" \--content file://deny-audit-tamper.json{"Policy": {"PolicySummary": {"Id": "p-k3jl9z7q","Arn": "arn:aws:organizations::111111111111:policy/o-a1b2c3d4e5/service_control_policy/p-k3jl9z7q","Name": "deny-audit-tamper","Description": "Nobody disables CloudTrail, admins included","Type": "SERVICE_CONTROL_POLICY","AwsManaged": false},"Content": "{\n \"Version\": \"2012-10-17\", ..."}}aws organizations attach-policy --policy-id p-k3jl9z7q \--target-id ou-9x2b-8f00qq1z# (no output on success)# Now, from the prod account — even as a full administrator:aws cloudtrail stop-logging --name org-trailAn error occurred (AccessDeniedException) when calling the StopLoggingoperation: ...user/admin is not authorized to perform:cloudtrail:StopLogging with an explicit deny in a service control policy
A few points worth burning into memory. An SCP constrains every principal in a member account, *including that account's root user*, and very few controls in AWS can claim that. It does not touch service-linked roles (the roles AWS services create for their own housekeeping), and it has no say over anyone outside the organization. The hard limits: ten SCPs attached directly to any one root, OU or account, with policies inherited from parents not counting toward that number, and 10,240 characters per policy document. The character cap is why a region lockdown gets written as one compact Deny with an aws:RequestedRegion condition rather than a long list of services.
Consolidated billing: one bill, pooled discounts
Consolidated billing makes the management account the payer: all member usage rolls into one invoice, the way a family phone plan charges one card for five handsets. The pooling is the part people miss. Usage from every account is added together *before* pricing tiers apply, so ten accounts holding 10 TB (terabytes) each in S3 reach the same volume discount as one account holding 100 TB. Reserved Instance and Savings Plans discounts drift the same way. Buy them in one account and they cover matching usage anywhere in the family, though sharing can be switched off per account. Two traps the exam likes: an organization gets *one* free tier shared across all its accounts, measured org-wide rather than per account, and the payer sees everyone's spend while a member sees only its own. Read the pooled bill with Cost Explorer (the aws ce API) and hang tripwires on it with AWS Budgets:
# Month-to-date spend, broken out per member accountaws ce get-cost-and-usage \--time-period Start=2026-07-01,End=2026-07-13 \--granularity MONTHLY \--metrics UnblendedCost \--group-by Type=DIMENSION,Key=LINKED_ACCOUNT{"ResultsByTime": [{"TimePeriod": { "Start": "2026-07-01", "End": "2026-07-13" },"Groups": [{ "Keys": ["222233334444"],"Metrics": { "UnblendedCost": { "Amount": "1841.2211", "Unit": "USD" } } },{ "Keys": ["333344445555"],"Metrics": { "UnblendedCost": { "Amount": "212.0490", "Unit": "USD" } } }],"Estimated": true}]}# Alert at 80% of a $2,500/month budget — before the invoice surprises youaws budgets create-budget --account-id 111111111111 \--budget '{"BudgetName":"org-monthly","BudgetLimit":{"Amount":"2500","Unit":"USD"},"TimeUnit":"MONTHLY","BudgetType":"COST"}' \--notifications-with-subscribers '[{"Notification":{"NotificationType":"ACTUAL","ComparisonOperator":"GREATER_THAN","Threshold":80,"ThresholdType":"PERCENTAGE"},"Subscribers":[{"SubscriptionType":"EMAIL","Address":"[email protected]"}]}]'# (no output on success)
Numbers with no labels go stale fast, so pair the budget with a tagging convention: owner, environment and cost-center on every resource. Then switch those keys on as cost allocation tags so Cost Explorer can slice the bill by them. The deeper cost work, rightsizing and commitment strategy, gets a lesson of its own later in this course.
A reference structure, and what it costs you
One layout keeps showing up, in real estates and in exam answers alike. A management account with no workloads in it. A *Security OU* holding a log-archive account for CloudTrail and AWS Config history that nobody can alter, plus a security-tooling account running GuardDuty (threat detection) and Security Hub (findings gathered in one place) on behalf of everyone. An *Infrastructure OU* for shared networking and build pipelines. A *Workloads OU* where prod and non-prod are split per team. AWS Control Tower builds exactly this and calls the result a *landing zone*, so whenever a question asks for a multi-account setup with the least operational effort, that is your answer. The compartments are not free, though. Every boundary you gain in isolation you pay back in cross-account IAM plumbing, network wiring between VPCs (Virtual Private Clouds, your own private networks inside AWS), and one more root credential to defend, so give each account a unique email alias, hardware MFA, and zero access keys. Small shop? Start with two accounts, production and everything-else, rather than one big one. Most resources cannot be moved between accounts, so splitting later hurts.
That gives you compartments, a ceiling on each one, and a single metered bill: an estate you can govern before a single workload exists. Time to put something inside it. The next lesson opens the biggest compartment door of all, EC2: how instances are sized, launched and priced, and why the pricing model you pick matters as much as the instance type.
One shared account feels convenient right up to the afternoon a developer's experiment deletes a production security group. Splitting the estate is blast-radius control, not bureaucracy. Prod, non-prod, security tooling and shared services each get a bulkhead, and Organizations and SCPs are the valves between them.
SCPs hand out nothing. They set the maximum. Someone carrying AdministratorAccess in a member account still cannot switch off CloudTrail if an SCP forbids it. That distinction shows up on the exam, and in every incident review where somebody asks why the break-glass role could not stop the logging.
Consolidated billing pools volume discounts. It does not pool quotas or IAM. You still size limits account by account. And tag policies and cost allocation tags only pay off if every account spells the keys the same way, so agree the taxonomy before you open the twentieth account.
Try this
In a sandbox organization, or read-only against your real one, list the roots and OUs and print the SCPs attached to a target. You want the hierarchy on your screen, not buried in a wiki page somebody last touched a year ago.
aws organizations describe-organization --query 'Organization.{Id:Id,Master:MasterAccountId}'aws organizations list-roots --query 'Roots[].{Id:Id,Name:Name}' --output tableaws organizations list-organizational-units-for-parent --parent-id r-exampleroot \--query 'OrganizationalUnits[].{Id:Id,Name:Name}' --output tableaws organizations list-policies-for-target --target-id ou-xxxx-yyyy --filter SERVICE_CONTROL_POLICY \--query 'Policies[].{Name:Name,Id:Id}' --output table
{"Id": "o-abc12defgh","Master": "111122223333"}-----------------------------| ListRoots |+--------+------------------+| Id | Name |+--------+------------------+| r-ex1 | Root |+--------+------------------+Security OU | ou-xxxx-secWorkloads OU | ou-xxxx-wlDenyLeaveOrganization | p-deny-leave
Takeaway
Three things to hold on to: the account is the isolation boundary, Organizations and SCPs set ceilings without granting anyone anything, and one shared bill does not mean one shared blast radius.
Next, sketch your OU layout for prod, non-prod and security, write one SCP that forbids disabling CloudTrail, and test it in a throwaway member account.