Learning/AWS Backend Developer/Day 1 — Cloud, the AWS Account, and Identity

Day 1 — Cloud, the AWS Account, and Identity

3h 30m · Phase 1 of 4 · Curriculum

Learning Objectives

By the end of today you can:

  1. Explain what cloud computing actually changes for a company that already owns servers, and where the responsibility boundary sits.
  2. Describe AWS's global infrastructure — Region, Availability Zone, edge location — and say what fails when each one fails.
  3. Secure a brand-new AWS account correctly: root user locked down, billing alarm set, an admin identity that is not root.
  4. Explain IAM's four nouns — principal, action, resource, condition — and read a policy document line by line.
  5. Describe, in order, everything that happens between your Java code calling s3.putObject(...) and the object existing.
  6. Reach AWS three ways — Console, CLI v2, SDK v2 — and explain why all three are the same thing.

The sentence you should be able to say tonight: "Every interaction with AWS is an authenticated, authorized HTTPS API call, and IAM decides whether it happens."

Prerequisites

  • The baseline: backend development experience, HTTP, basic networking vocabulary.
  • An AWS account you control. Create one before starting if you don't have one; it takes ~10 minutes and requires a payment card.
  • Installed: AWS CLI v2, JDK 17+, Maven 3.9+, an IDE.

Verify your toolchain:

aws --version     # expect: aws-cli/2.x.x ...
java -version     # expect: openjdk version "17" or higher
mvn -version      # expect: Apache Maven 3.9.x

No AWS knowledge is assumed. Nothing from any later day is used here.

Why Does This Exist?

You already know how to build a backend. So why does AWS exist at all?

Consider a company running its own servers. To add capacity they must: forecast demand months ahead, get budget approval, order hardware, wait for delivery, rack it, cable it, install an OS, patch it, and keep a team on call for the disk that fails at 3am. If the forecast was wrong high, they own idle metal for three years. If it was wrong low, they turn customers away.

Two things dominate that story, and neither is about writing software:

  1. Capacity is a guess made far in advance, and it is expensive in both directions.
  2. Most of the work is undifferentiated — every company patches operating systems, and no customer has ever chosen a product because of it.

Cloud computing attacks both. Capacity becomes an API call that completes in seconds, billed by the hour or the request. The undifferentiated work moves to the provider, in exchange for accepting their abstractions and their failure modes.

That trade — elasticity and operational offload, in exchange for a dependency you cannot inspect — is the whole deal. Everything in the next 14 days is a consequence of it.

Beginner Explanation

The cloud in one picture

Today, your mental model is probably this:

flowchart LR
    L[My laptop] --> I((Internet)) --> S[A server somewhere] --> D[(A database)]

AWS does not change that picture. It changes who owns the boxes and how fast you can get another one.

flowchart LR
    L[My laptop] --> I((Internet)) --> A[AWS] --> App[My application<br/>running on AWS resources]

For now, treat "AWS" as a very large company that will rent you compute, storage, databases, queues and about two hundred other things — through an API, priced per unit, available in seconds.

IaaS, PaaS, SaaS — a responsibility boundary, not a product tier

These three acronyms are usually taught as a hierarchy. They are better understood as a line drawn through a stack, marking where your responsibility stops.

flowchart TB
    subgraph ON["On-premises"]
        O1[Your app] --- O2[Runtime] --- O3[OS] --- O4[Virtualization] --- O5[Servers] --- O6[Data centre]
    end
    subgraph IAAS["IaaS · e.g. EC2"]
        I1[Your app] --- I2[Runtime] --- I3[OS] --- I4["AWS: virtualization, servers, DC"]
    end
    subgraph PAAS["PaaS · e.g. Lambda"]
        P1[Your app] --- P2["AWS: runtime, OS, virtualization, servers, DC"]
    end
    subgraph SAAS["SaaS · e.g. Gmail"]
        S1["Someone else's app.<br/>You bring data and config."]
    end
ModelYou manageAWS managesExample
On-premEverythingNothingYour own rack
IaaSApp, runtime, OS, patchingHardware, virtualization, network, facilityEC2 (Day 3)
PaaSApp, and the config of the platformEverything below the appLambda (Day 5), RDS (Day 6)
SaaSYour data and settingsThe whole productNot our subject

Why a backend developer cares: every AWS service you pick moves this line. Choosing Lambda over EC2 is choosing to stop owning the OS — which is a gift right up until you need something the platform doesn't allow. That is the trade-off you will be asked to defend in interviews, repeatedly, starting on Day 4.

The shared responsibility model

AWS states it as: AWS is responsible for security of the cloud; you are responsible for security in the cloud.

Concretely:

AWS's jobYour job
Physical data centre securityWho can call your APIs
Hypervisor, host OS patchingGuest OS patching (on EC2)
Network infrastructureYour security groups and firewall rules
Service-level durability of S3Whether your bucket is public
Encryption capability (KMS)Whether you turned encryption on
Availability of the AZWhether your app survives one AZ dying

Notice how many of the right-hand entries are application design decisions. Almost every well-known "AWS breach" was a right-hand-column failure — most famously, buckets made public by their owners. AWS did not fail; a developer made a configuration choice.

Core Concepts

1. Global infrastructure: Region → Availability Zone → data centre

flowchart TB
    subgraph GLOBAL["AWS Global Infrastructure"]
        subgraph R1["Region · eu-west-1 (Ireland)"]
            subgraph AZ1["AZ eu-west-1a"]
                DC1[(one or more<br/>data centres)]
            end
            subgraph AZ2["AZ eu-west-1b"]
                DC2[(data centres)]
            end
            subgraph AZ3["AZ eu-west-1c"]
                DC3[(data centres)]
            end
        end
        subgraph R2["Region · us-east-1 (N. Virginia)"]
            AZ4[3+ AZs]
        end
        EDGE[Hundreds of edge locations<br/>worldwide · CloudFront, Day 10]
    end
    AZ1 <-->|low-latency private links| AZ2
    AZ2 <-->|single-digit ms| AZ3

Region — a named geographic area (us-east-1, eu-west-1, ap-south-1) containing multiple Availability Zones. Regions are isolated from each other by design: a Region is the largest unit of failure containment AWS offers, and data does not move between Regions unless you make it.

Availability Zone (AZ) — one or more physically separate data centres within a Region, with independent power, cooling and network, connected to the other AZs by high-bandwidth, low-latency private links (single-digit milliseconds). Most Regions have three or more. The whole point of an AZ is that one can fail without the others failing.

Edge location — a much larger number of small points of presence used for content delivery (CloudFront) and DNS (Route 53). Not where your application runs. Covered on Day 10.

The AZ identifier trap (worth knowing on Day 1)

eu-west-1a in your account is not necessarily the same physical AZ as eu-west-1a in someone else's — AWS randomizes the mapping per account to prevent everyone piling into "a". The stable identifier is the AZ ID (euw1-az1), which you'll see in the Console and in aws ec2 describe-availability-zones. This matters the moment you share resources across accounts.

aws ec2 describe-availability-zones --region eu-west-1 \
  --query 'AvailabilityZones[].{Name:ZoneName,Id:ZoneId}' --output table

Global vs regional vs zonal services

This distinction predicts failure behaviour, so learn it now:

ScopeMeaningExamples
ZonalThe resource lives in exactly one AZ. If that AZ fails, it fails.EC2 instance, EBS volume, RDS instance (single-AZ), subnet
RegionalAWS runs it across multiple AZs for you. Survives one AZ.S3, SQS, DynamoDB, Lambda, ECS control plane
GlobalOne instance of the service for your whole account.IAM, Route 53, CloudFront, Organizations, WAF (for CloudFront)

⚠️ Common confusion: S3 is a regional service with a globally unique bucket namespace. Your bucket lives in one Region; nobody else on Earth can use its name. Those are two different facts that sound like one.

Choosing a Region

Four inputs, in roughly this order:

  1. Latency to your users — physics. A round trip London↔Virginia is ~75–90ms before your code runs.
  2. Data residency and legal requirements — often decides it outright.
  3. Service availability — not every service is in every Region, and new services land in us-east-1 first.
  4. Price — the same instance can differ by 10–30% between Regions.

us-east-1 is the oldest and largest Region. It has the most services and the most history of large-scale incidents, partly because several global service control planes are hosted there. This is a real architectural consideration, not folklore — we revisit it on Day 12.

2. The AWS account

An account is three things at once, and conflating them causes bad architecture:

The account is…Consequence
A billing boundaryOne bill, one payment method
A security/blast-radius boundaryResources in an account can be made to see each other easily; across accounts, never by accident
A quota boundaryService limits are largely per-account, per-Region

Large organizations therefore run many accounts (prod, staging, dev, per-team, per-workload) under AWS Organizations, governed by Service Control Policies. You do not need that today — but you need to know that "one account for everything" is a decision, not a default. We return to it on Day 11.

The root user

When you created the account, you created the root user — the email address and password you signed up with. It is not an IAM user and cannot be restricted by IAM policies. It can do everything, including close the account.

Your obligations on day one:

  1. Enable MFA on root. Non-negotiable.
  2. Delete any root access keys. If the signup flow created any, they should not exist.
  3. Create a separate admin identity and use that from now on.
  4. Do not use root again except for the handful of tasks that require it.

Tasks that genuinely require root (short list — these are the ones that come up):

  • Closing the account, changing the account name/email/root password
  • Changing the AWS Support plan
  • Restoring an IAM user's permissions after someone has locked everyone out
  • Enabling MFA Delete on an S3 bucket
  • A few billing/tax configuration actions

Everything else — everything you will do in the next 14 days — is done as an IAM identity.

3. IAM — the four nouns

Every authorization decision in AWS answers one question:

Can this principal perform this action on this resource under these conditions?

flowchart LR
    P["Principal<br/>who is asking"] --> A["Action<br/>s3:PutObject"]
    A --> R["Resource<br/>arn:aws:s3:::my-bucket/*"]
    R --> C["Condition<br/>optional constraints"]
    C --> D{"Allow or Deny?"}

Identities

EntityWhat it isWhen you use it
IAM userA long-lived identity with a password and/or access keysRarely, in modern practice. Humans should use IAM Identity Center; applications should use roles.
IAM groupA container of users, holding policiesOrganizing human permissions
IAM roleA set of permissions with no credentials attached, that a trusted principal can temporarily assumeAlmost always. EC2 instances, Lambda functions, ECS tasks, humans via SSO, other accounts.
IAM policyA JSON document granting or denying actions on resourcesAttached to any of the above

The single most important idea in IAM:

A role is not an identity you log into. It is a costume. Something already trustworthy — an EC2 instance, a Lambda function, a federated human — temporarily wears it and receives short-lived credentials that expire automatically.

Why this matters: an access key is a secret that exists until someone deletes it. Role credentials expire in minutes to hours and are rotated for you. Every credential leak you read about involves the first kind.

The policy document

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadWriteOrderDocuments",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::acme-orders-prod/documents/*",
      "Condition": {
        "StringEquals": { "s3:x-amz-server-side-encryption": "aws:kms" }
      }
    }
  ]
}

Line by line:

FieldMeaning
VersionThe policy language version. It is always 2012-10-17. It is not a date you choose; using anything else silently changes semantics (variables stop working).
SidOptional human label. Use it — it shows up when debugging.
EffectAllow or Deny. An explicit Deny always wins, anywhere in any applicable policy.
ActionService-prefixed API operations. Wildcards allowed (s3:Get*).
ResourceOne or more ARNs. This is where least privilege actually happens.
ConditionExtra constraints — source IP, MFA presence, encryption headers, tags, time.

ARNs — Amazon Resource Names

Every resource in AWS has a globally unique name in a fixed format:

arn:partition:service:region:account-id:resource

arn:aws:s3:::acme-orders-prod/documents/inv-1.pdf     S3: no region, no account (global namespace)
arn:aws:sqs:eu-west-1:123456789012:orders-queue       SQS: regional, account-scoped
arn:aws:iam::123456789012:role/order-service-task     IAM: global, so no region
arn:aws:dynamodb:eu-west-1:123456789012:table/orders

Read the empty fields — they tell you the service's scope. IAM ARNs have no region because IAM is global. S3 ARNs have neither region nor account because bucket names are globally unique.

The default is deny

If no policy allows an action, it is denied. There is no implicit permission anywhere in AWS. A new IAM user with no policies attached can do nothing — not even list what exists.

The full evaluation logic (SCPs, resource policies, permission boundaries, session policies) is Day 11. Today, two rules carry you:

  1. Implicit deny — not allowed means denied.
  2. Explicit deny wins — an explicit Deny beats any number of Allows.

Architecture

Where you are, after today

flowchart TB
    subgraph YOU["You"]
        DEV[Developer laptop]
        CONSOLE[Browser · Console]
    end
    subgraph AWS["AWS · one account"]
        subgraph GLOB["Global"]
            IAM[IAM<br/>users · roles · policies]
        end
        subgraph REG["Region: eu-west-1"]
            STS[STS endpoint]
            S3[S3 bucket]
            CW[CloudWatch<br/>billing alarm]
        end
    end
    DEV -->|CLI v2 · signed HTTPS| STS
    DEV -->|SDK v2 · signed HTTPS| S3
    CONSOLE -->|same APIs, browser session| S3
    IAM -.->|authorizes every call| S3
    IAM -.-> STS

Nothing here is running your application yet. Today establishes the control plane: identity, access, and the three ways you drive it.

How It Works Internally

The signed AWS API call — the diagram that explains most access errors

This is the first of the eleven mandatory internal-working diagrams. Learn it properly now and Days 3, 5 and 11 get much easier.

sequenceDiagram
    autonumber
    participant APP as Your code (SDK v2)
    participant CP as Credential provider chain
    participant SIG as SigV4 signer
    participant EP as Regional endpoint<br/>s3.eu-west-1.amazonaws.com
    participant IAM as IAM authorization
    participant SVC as S3 service

    APP->>CP: need credentials
    CP-->>APP: access key id + secret + session token
    APP->>APP: build HTTPS request<br/>(method, path, headers, body hash)
    APP->>SIG: sign canonical request
    SIG-->>APP: Authorization: AWS4-HMAC-SHA256 ...
    APP->>EP: HTTPS PUT /my-key
    EP->>IAM: who is this principal,<br/>and may they do s3:PutObject on this ARN?
    IAM-->>EP: Allow / Deny (+ reason)
    alt Denied
        EP-->>APP: 403 AccessDenied
    else Allowed
        EP->>SVC: execute
        SVC-->>APP: 200 + ETag
    end

What each step means for you:

1–2. The credential provider chain. The SDK looks for credentials in a fixed order and uses the first source it finds. For AWS SDK for Java v2, DefaultCredentialsProvider checks:

1. Java system properties       -Daws.accessKeyId, -Daws.secretAccessKey
2. Environment variables         AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
3. Web identity token            AWS_WEB_IDENTITY_TOKEN_FILE  (used by EKS/IRSA, OIDC federation)
4. Shared config/credentials     ~/.aws/credentials and ~/.aws/config, honouring AWS_PROFILE
5. Container credentials         AWS_CONTAINER_CREDENTIALS_RELATIVE_URI  (ECS tasks — Day 4)
6. Instance profile via IMDS     http://169.254.169.254/...              (EC2 — Day 3)

This chain is why "it works on my laptop but not in production" happens. Locally you hit step 4 (your profile, probably with broad permissions). In production you hit step 5 or 6 (a narrowly-scoped role). Same code, different principal, different answer from IAM.

3–5. SigV4 signing. The SDK builds a canonical form of the request (method, URI, sorted query string, sorted signed headers, SHA-256 of the body), derives a signing key from your secret plus the date, Region and service, and attaches an HMAC signature. Three consequences you will actually hit:

  • Your clock matters. Signatures include a timestamp with a tolerance of about 5 minutes. A machine with badly skewed time gets SignatureDoesNotMatch or RequestTimeTooSkewed.
  • The secret never crosses the wire. Only the signature does.
  • Region and service are baked into the signing key — which is why you cannot point a client configured for eu-west-1 at a us-east-1 endpoint and have it work.
  1. The endpoint is regional for regional services. sqs.eu-west-1.amazonaws.com and sqs.us-east-1.amazonaws.com are different services holding different data. There is no "global SQS."

7–8. IAM authorization evaluates every applicable policy and returns allow or deny. On deny, the error message names the principal, the action and the resource — read it; it is usually telling you exactly what is wrong.

What the Console actually is

The AWS Console is a web application that makes these same signed API calls on your behalf, using temporary credentials from your browser session. There is nothing the Console can do that the API cannot.

flowchart LR
    C[Console] --> API[AWS Service APIs]
    CLI[AWS CLI v2] --> API
    SDK[AWS SDK v2] --> API
    API --> IAM{IAM authorization}
    IAM --> SVC[Service]

Why this matters from day one: if you learn AWS by clicking, you learn a UI that changes every few months. If you learn the API, you learn the thing that is stable, automatable, testable and the same in every interview. We use the Console to see, and the CLI/SDK to do.

Code

Setup: configure the CLI with a named profile

aws configure --profile aws-course
# AWS Access Key ID     [None]: AKIA...              (from Lab 0)
# AWS Secret Access Key [None]: ...
# Default region name   [None]: eu-west-1            (pick one near you; be consistent)
# Default output format [None]: json

This writes two files:

~/.aws/credentials      [aws-course]  aws_access_key_id, aws_secret_access_key
~/.aws/config           [profile aws-course]  region, output

Use it either per-command or per-shell:

aws sts get-caller-identity --profile aws-course
export AWS_PROFILE=aws-course   # then omit --profile

"Who does AWS think I am?" — the first command you should ever run

aws sts get-caller-identity
{
    "UserId": "AIDA2EXAMPLEEXAMPLE",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/aws-course-admin"
}

Read that ARN every time something is denied. Nine times out of ten the surprise is which identity you are, not which permission is missing.

The same question, from Java

pom.xml — note the BOM pattern, used in every Java example in this course:

<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.acme</groupId>
  <artifactId>aws-course-day1</artifactId>
  <version>1.0.0</version>

  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <aws.sdk.version>2.28.16</aws.sdk.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>bom</artifactId>
        <version>${aws.sdk.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <!-- versions come from the BOM -->
    <dependency>
      <groupId>software.amazon.awssdk</groupId>
      <artifactId>sts</artifactId>
    </dependency>
    <dependency>
      <groupId>software.amazon.awssdk</groupId>
      <artifactId>s3</artifactId>
    </dependency>
  </dependencies>
</project>

The SDK version above is a concrete example. Check the current version before you start; the BOM pattern is what matters and does not change.

WhoAmI.java

Java
package com.acme;

import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.GetCallerIdentityResponse;

public class WhoAmI {
    public static void main(String[] args) {
        // Region is set explicitly. Relying on ambient region configuration is
        // fine locally and a source of surprises in production.
        try (StsClient sts = StsClient.builder()
                .region(Region.EU_WEST_1)
                // This is the default; naming it makes the credential chain visible
                // to whoever reads this code next.
                .credentialsProvider(DefaultCredentialsProvider.create())
                .build()) {

            GetCallerIdentityResponse id = sts.getCallerIdentity();
            System.out.println("Account : " + id.account());
            System.out.println("ARN     : " + id.arn());
            System.out.println("UserId  : " + id.userId());
        }
    }
}
mvn -q compile exec:java -Dexec.mainClass=com.acme.WhoAmI

Two things to notice, both of which recur for the next 13 days:

  • The client is AutoCloseable and holds a connection pool. In a real application it is a singleton, created once. Creating one per request is a latency and file-descriptor bug (Day 5 shows what it does to a Lambda cold start).
  • No credentials appear in the code. They never will, in any example in this course.

S3 from Java — put and get one object

S3RoundTrip.java

Java
package com.acme;

import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;

import java.nio.charset.StandardCharsets;

public class S3RoundTrip {

    public static void main(String[] args) {
        String bucket = System.getenv("COURSE_BUCKET");   // never hardcode names
        String key = "day1/hello-from-java.txt";

        try (S3Client s3 = S3Client.builder().region(Region.EU_WEST_1).build()) {

            PutObjectResponse put = s3.putObject(
                    PutObjectRequest.builder()
                            .bucket(bucket)
                            .key(key)
                            .contentType("text/plain")
                            .build(),
                    RequestBody.fromString("hello from the SDK", StandardCharsets.UTF_8));

            System.out.println("PUT ok, etag=" + put.eTag());

            ResponseBytes<GetObjectResponse> got = s3.getObjectAsBytes(
                    GetObjectRequest.builder().bucket(bucket).key(key).build());

            System.out.println("GET ok, body=" + got.asUtf8String());

        } catch (NoSuchBucketException e) {
            // A *client* error: the bucket does not exist or is in another Region.
            // Do not retry this.
            System.err.println("Bucket not found: " + e.awsErrorDetails().errorMessage());
        } catch (S3Exception e) {
            // Typed error handling from day one. 403 here almost always means
            // "the principal from get-caller-identity lacks this action on this ARN".
            System.err.println("S3 error " + e.statusCode() + ": "
                    + e.awsErrorDetails().errorMessage());
            throw e;
        }
    }
}

catch (Exception e) does not appear anywhere in this course. AWS errors carry a status code and a typed exception, and the difference between "retry this" and "fix your policy" is the whole game. Day 5 and Day 8 build on this.

Hands-on

Lab 0 — Secure the account (20 min)

Objective: end with a root user nobody uses, an admin identity you do use, and an alarm that tells you before the bill surprises you.

Architecture:

flowchart LR
    ROOT[Root user<br/>MFA on · keys deleted · unused] -.creates once.-> ADMIN[IAM admin user<br/>MFA · access keys]
    ADMIN --> CLI[CLI profile: aws-course]
    ROOT -.-> BUDGET[AWS Budget<br/>+ CloudWatch billing alarm]

Steps:

  1. Sign in as root. Console → your account name (top right) → Security credentials.
  2. Enable MFA on the root user. Use an authenticator app or a hardware key. Do not skip this.
  3. Check for root access keys. If any exist, delete them. Root should have zero access keys, permanently.
  4. Create an admin IAM user: IAM → Users → Create user → name aws-course-admin → enable Console access → attach the AWS managed policy AdministratorAccess.

    AdministratorAccess is not least privilege. We are using it because the alternative on day one is spending an hour on policies before you can create anything. Day 11 narrows it, and you will do that exercise for real.

  5. Enable MFA on the admin user too, and create access keys for it (CLI use case).
  6. Configure the CLI: aws configure --profile aws-course.
  7. Set a budget: Billing and Cost Management → Budgets → Create budget → Cost budget → monthly, e.g. $20 → alert at 80% and 100% to your email.
  8. Add a CloudWatch billing alarm as a second signal:
# NOTE: billing metrics are published only in us-east-1, regardless of where you work.
aws cloudwatch put-metric-alarm \
  --alarm-name course-estimated-charges \
  --namespace AWS/Billing \
  --metric-name EstimatedCharges \
  --dimensions Name=Currency,Value=USD \
  --statistic Maximum --period 21600 --evaluation-periods 1 \
  --threshold 20 --comparison-operator GreaterThanThreshold \
  --region us-east-1

Billing metrics must be enabled once in Billing preferences ("Receive Billing Alerts") before the alarm has data. Both the budget and the alarm are worth having: the budget forecasts, the alarm reacts.

  1. Sign out of root. Do not sign back in.

Verification:

aws sts get-caller-identity
# Arn must contain :user/aws-course-admin — NOT :root

Common errors:

SymptomCauseFix
Unable to locate credentialsNo profile configured, or AWS_PROFILE unsetaws configure --profile aws-course, then export AWS_PROFILE=aws-course
InvalidClientTokenIdAccess key deleted or typo'dRecreate the key pair in IAM
Alarm shows "Insufficient data" for hoursBilling alerts not enabled, or metrics publish slowly (up to several hours)Enable in Billing preferences; wait

Cleanup: nothing billable was created. Keep all of it.

Production relevance: this is, minus the IAM user, exactly what a real account baseline looks like — root locked and unused, an alarm on spend before anything exists to spend on. In a company, step 4 is replaced by IAM Identity Center and federated SSO; the principle is identical.

Lab 1 — One object, three ways (20 min)

Objective: prove to yourself that Console, CLI and SDK are one API.

Prerequisites: Lab 0. Permissions: s3:CreateBucket, s3:PutObject, s3:GetObject, s3:ListBucket, s3:DeleteObject, s3:DeleteBucket (covered by AdministratorAccess today).

Steps:

# 1. Create a bucket. The name must be globally unique — put your account id in it.
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
BUCKET="aws-course-${ACCOUNT}-day1"
REGION=eu-west-1

aws s3api create-bucket --bucket "$BUCKET" --region "$REGION" \
  --create-bucket-configuration LocationConstraint="$REGION"
# us-east-1 is the exception: omit --create-bucket-configuration entirely.

# 2. Block public access explicitly (it is on by default for new buckets; be deliberate).
aws s3api put-public-access-block --bucket "$BUCKET" \
  --public-access-block-configuration \
  "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

# 3. CLI upload
echo "hello from the CLI" > /tmp/cli.txt
aws s3 cp /tmp/cli.txt "s3://${BUCKET}/day1/hello-from-cli.txt"

# 4. List, with keys and sizes
aws s3api list-objects-v2 --bucket "$BUCKET" \
  --query 'Contents[].{Key:Key,Size:Size}' --output table
  1. Console: open S3, find the bucket, upload a file by drag-and-drop. Notice it appears alongside the CLI object — same bucket, same API.
  2. Java: run S3RoundTrip with COURSE_BUCKET=$BUCKET.

Verification — the point of the lab:

aws s3api list-objects-v2 --bucket "$BUCKET" --query 'Contents[].Key' --output text
# All three objects are listed. Nothing distinguishes the one the Console made.

# Now watch the API call itself:
aws s3api head-object --bucket "$BUCKET" --key day1/hello-from-cli.txt --debug 2>&1 \
  | grep -i "Authorization: AWS4-HMAC-SHA256" | head -1

That last line is the SigV4 signature from the internal-working diagram, on your own terminal.

Cleanup (do it now):

aws s3 rm "s3://${BUCKET}" --recursive
aws s3api delete-bucket --bucket "$BUCKET"

Common errors:

SymptomCauseFix
BucketAlreadyExistsBucket names are globally unique across all AWS customersAdd your account id / a random suffix
IllegalLocationConstraintExceptionLocationConstraint mismatched the --region, or you passed it for us-east-1Match them; omit for us-east-1
AccessDenied on list-objects-v2s3:ListBucket is on the bucket ARN, s3:GetObject on the object ARN — they are different resourcesGrant both, on the right ARNs (Day 11 goes deep)
BucketNotEmpty on deleteObjects (or versions) remainaws s3 rm --recursive first

Production relevance: the CLI is how you investigate production, the SDK is how your application works, and the Console is how you look. Teams that only know the Console cannot automate, cannot test, and cannot debug at 3am.

Production Considerations

ConcernWhat a real account does
Human accessIAM Identity Center (SSO) federated to the corporate IdP. IAM users for humans are legacy; they mean long-lived keys, which means leaked keys.
Application accessAlways roles. An access key in a deployment artifact is a finding in any security review.
Account structureMultiple accounts under Organizations — at minimum prod separate from everything else. The account boundary is the strongest isolation AWS gives you, and it is free.
Region strategyPick one primary Region and be consistent. Resources scattered across Regions by accident are invisible, unbilled-to-anyone, and impossible to reason about.
Naming and taggingTag everything with owner, environment and cost centre from the first resource. Retrofitting tags across 400 resources is a quarter of someone's life.
Break-glassOne documented, MFA-protected root procedure, stored offline, tested annually.
AuditCloudTrail on from day one (Day 11). It is the only record of who did what.

Failure Scenarios

S1 — "What happens if that AZ dies?"

A startup runs a single EC2 instance serving 500 RPS in eu-west-1a.

QuestionAnswer
What can fail?The AZ — power, network, or the physical host
What happens?Total outage. The instance is gone; its EBS volume is in that AZ too.
How does recovery happen?Manually. Someone launches a new instance in another AZ from an AMI — if an AMI exists, and if the data was not only on that EBS volume.
How quickly?Tens of minutes, optimistically. Hours if the runbook is imaginary.
Can the operation happen twice?N/A today
Can data be lost?Yes — anything on that instance's EBS volume without a snapshot or replica
Can data be duplicated?N/A today

The Day 1 lesson: AZs exist so that your architecture can survive one of them. Nothing about AWS gives you that automatically — placing an application in two AZs is a decision you make (Days 3, 4 and 6 make it).

S2 — Credentials in a public repository

A developer commits application.yml containing an access key. It is public for 11 minutes.

What actually happens: automated scanners find exposed AWS keys within seconds to minutes. Typical abuse is launching expensive GPU instances in every Region for cryptomining. Bills in the tens of thousands of dollars within hours are well documented.

QuestionAnswer
What can fail?The credential's confidentiality
What happens?Anything the key's identity was permitted to do — which, for a convenience-created admin key, is everything
Recovery?Deactivate the key immediately, rotate, review CloudTrail for the whole exposure window, contact AWS Support, delete everything created
How quickly?Minutes to contain; days to be confident
Data lost / duplicated?Possibly both, plus exfiltration

The Day 1 lesson, stated as a rule: an application should never possess a credential that does not expire. Use roles. Every service in this course — EC2 (Day 3), ECS (Day 4), Lambda (Day 5) — has a mechanism for exactly this, and it is always the correct answer.

Detective controls that matter: enable IAM Access Analyzer, turn on GuardDuty (it flags anomalous credential use), and never create an access key for a workload.

Security

Today's security is entirely about identity, because on day one identity is the only attack surface you have.

The four questions the course asks of every architecture, applied to what you built:

QuestionToday's answer
Who can access this?The aws-course-admin IAM user, and root (which you have locked and abandoned)
What credentials are used?An access key on your laptop — acceptable for a personal learning account, not acceptable for a workload or a corporate human
Where is data encrypted?S3 encrypts at rest by default (SSE-S3) and all API traffic is TLS. You made no explicit choice — Day 11 makes you make it
What happens if credentials leak?Full account compromise. This is why the budget alarm from Lab 0 exists today and not later

Least privilege, stated honestly: you attached AdministratorAccess today. That is a deliberate, temporary teaching choice. The course's position is that least privilege is derived from evidence (what did the service actually call?), not guessed up front — and Day 11's Lab 14 has you derive it from CloudTrail for a real service.

Performance

There is little to tune today, but two facts about latency will shape everything later:

  1. Every AWS API call is a network round trip — typically single-digit to low tens of milliseconds within a Region, and much more across Regions. Code that makes 50 sequential SDK calls in a request handler has a latency problem no instance size can fix. This is why batch APIs exist (Day 8) and why cross-Region chattiness is a design error (Day 12).
  2. Region choice is a latency floor. If your users are in London and your Region is us-east-1, every request starts ~75ms in debt before your code runs. No amount of optimization recovers it.

Cost

What today cost: essentially nothing. S3 storage for a few text files is a fraction of a cent, and both labs end with deletion.

Prices used in this course (us-east-1, illustrative, verified at authoring time — always re-check current pricing):

ItemApprox. price
S3 Standard storage~$0.023 per GB-month
S3 PUT/COPY/POST/LIST~$0.005 per 1,000 requests
S3 GET~$0.0004 per 1,000 requests
Data transfer out to InternetFirst 100 GB/month free, then ~$0.09/GB (tiered down)
Data transfer inFree
IAM, STSFree
CloudWatch alarm~$0.10 per alarm-month (a small number of alarms are in the free tier)

The one cost idea for Day 1: AWS bills for resources that exist, not resources you use. An idle EC2 instance, an unattached EBS volume, a NAT Gateway with no traffic, a provisioned database nobody queries — all bill by the hour, forever, silently. The single most common personal-AWS-bill story is a forgotten resource in a Region the person never looks at.

This is why every lab in this course ends with cleanup, and why you set the budget before you created anything.

Alternatives

Instead ofYou couldWhy we use what we use
AWSAzure, GCP, a rented rackThe concepts (identity, regions, managed services, failure domains) transfer almost entirely; the names do not. AWS has the largest share of backend job descriptions.
IAM user + access keyIAM Identity Center (SSO) with short-lived credentialsCorrect for organizations; adds ~30 minutes of setup that teaches nothing on day one. Know that it is the real answer.
CLI v2CLI v1v1 is legacy; v2 has better auth/SSO support and is the current tool
SDK v2 for JavaSDK v1v1 is in maintenance. v2 has non-blocking clients, better pluggability, and is what new code uses. v1 appears in this course only when discussing legacy migration.
Console-first learningAPI-first learningThe Console changes; the API does not. And you cannot automate a screenshot.

Trade-offs

DecisionGainCost
Cloud over owned hardwareElasticity, speed, operational offload, global reachVariable spend, provider dependency, failure modes you cannot inspect, a new skill set
Higher on the IaaS→PaaS ladderLess to operate, faster to shipLess control, platform limits, more specific lock-in
One accountSimple, fast, everything can see everythingEverything can see everything. No blast-radius boundary.
Many accountsStrong isolation, clean billing, clear ownershipCross-account plumbing, more governance, a steeper start
Roles over long-lived keysAutomatic expiry and rotation; nothing to leakRequires understanding trust policies — a real learning cost (Day 11)
AdministratorAccess while learningYou are never blockedIf the key leaks, you lose everything. Acceptable only in a throwaway account with a budget alarm.

Common Mistakes

  1. Using root for day-to-day work. It cannot be constrained and cannot be scoped. Lock it and walk away.
  2. Creating access keys for an application. Use a role. Every time. There is a role mechanism for every compute service in AWS.
  3. Assuming the Console is AWS. It is one client of the API.
  4. Forgetting which Region you are in. Half of all "my resource disappeared" reports are a Region selector. Set a default and be consistent.
  5. Ignoring the ARN in an error message. AccessDenied messages usually name the principal, the action and the resource. That is the answer, not a mystery.
  6. Thinking S3 is global because the bucket name is. The bucket lives in one Region.
  7. Believing an AZ letter is a place. eu-west-1a differs per account; the AZ ID is the stable identifier.
  8. Treating Version: "2012-10-17" as something to update. It is a language version, not a date.
  9. Assuming permissions are inherited from "being in the account." The default is deny, always.
  10. Creating resources before setting a budget. Ten minutes of prevention.

Interview Questions

(2–3 YOE and 4–5 YOE level — see the full plan)

2–3 YOE

  1. What is the difference between a Region and an Availability Zone?
  2. What is an IAM role, and how does it differ from an IAM user?
  3. What happens if you attach no policies to a new IAM user?
  4. What is an ARN, and what can you tell from one that has no Region in it?
  5. Name three ways to interact with AWS and explain what they have in common.
  6. Why should you enable MFA on the root user and then stop using it?

4–5 YOE

  1. Your code works locally and gets AccessDenied after deploying. Nothing about the policy changed. What is your first hypothesis, and what command confirms it?
  2. Walk me through what the SDK does between your putObject call and the HTTP request leaving the machine.
  3. A colleague suggests storing an access key in the application's environment variables for production. Make the argument against it, and say what you would do instead.
  4. What is the difference between an "implicit deny" and an "explicit deny", and why does it matter when you combine policies?
  5. Why can't you point a client configured for eu-west-1 at a us-east-1 endpoint?

Senior-Level Questions

(6–7 YOE and 8–10 YOE level)

6–7 YOE

  1. You are designing the account structure for a company with three products and four environments. How many AWS accounts, and why? What do you put in each?
  2. A service in account A needs to read objects from a bucket in account B. Describe the mechanisms available and which you would choose.
  3. How would you detect that an access key has been leaked, and what does your first hour look like?
  4. Your company must operate in the EU only. What does that constrain, beyond "pick an EU Region"?
  5. Explain why us-east-1 has a different risk profile from other Regions, and whether that should change your architecture.

8–10 YOE

  1. Argue both sides of "one AWS account per microservice." Then decide, and say what would change your mind.
  2. Your organization has 200 engineers and no IAM strategy. Describe the first 90 days, in the order you would do it, and what you would deliberately not do.
  3. Where exactly does the shared responsibility model leave ambiguity, and how have you seen that ambiguity cause an incident?
  4. A team proposes granting AdministratorAccess to a CI pipeline "temporarily, to unblock delivery." What is your response, and what do you offer instead that does not slow them down?

Answering pattern reminder: state assumptions → name the dominating constraint → give 2–3 options → choose and say what you traded → say what would prove you wrong. See the five-move pattern.

Day-End Revision

The five sentences

  1. A Region is an isolated geographic area of 3+ Availability Zones; an AZ is a physically separate failure domain you can span.
  2. Every AWS interaction is a signed HTTPS API call — Console, CLI and SDK are three clients of the same API.
  3. IAM answers one question: may this principal perform this action on this resource under these conditions? Default deny; explicit deny wins.
  4. Roles give expiring credentials to things that are already trustworthy; access keys are long-lived secrets and are the wrong answer for workloads.
  5. AWS bills for resources that exist, not resources you use.

The diagram to redraw from memory: the signed API call sequence — credential chain → SigV4 → regional endpoint → IAM → service.

The numbers

Policy Versionalways 2012-10-17
SigV4 clock tolerance~5 minutes
AZs per Region3 or more, typically
Billing metrics Regionus-east-1, always
Credential chain steps (Java v2)6, in fixed order

Today's trap: "S3 is a global service." It is regional, with a global bucket namespace. Two different facts.

Tomorrow needs: IAM (you'll write your first non-trivial policies), the idea that a Region contains AZs (subnets are per-AZ), and the CLI profile you configured.

Mini Assignment

Time: 30–40 minutes. This is the first commit of the repository that becomes your capstone.

  1. Create a Git repository named aws-course. Add a day-01/ directory.
  2. Write a small Java program, AccountInventory, that:
    • prints the caller identity (account, ARN);
    • lists all S3 buckets in the account with their creation dates;
    • lists all Regions enabled for the account (Ec2Client.describeRegions());
    • handles AccessDeniedException distinctly from other errors, printing which action was denied.
  3. Run it with your aws-course profile, then run it again with AWS_PROFILE unset and record what changes and why.
  4. In day-01/NOTES.md, answer in your own words:
    • Which step of the credential provider chain served each run?
    • What would need to change for this code to run unmodified on an EC2 instance with no credentials file?
  5. Commit.

Success criterion: you can explain, without notes, why the same binary behaves differently in the two runs — and you have not typed a credential into a file the repository tracks.

AWS Documentation

Primary sources for today. AWS changes frequently — prefer these over any summary, including this one.


Next: Day 2 — Networking for Backend Developers