Lesson 2-3 answered the question of what should hold AWS permissions for a program running on a cloud server.
The answer is a role attached to the server, handing the program temporary credentials. People get users, programs get roles.
That matters right now because a role is one of the launch settings for an instance. You choose the image, the size, the key pair, the firewall, and the role, all in the same step, so the permission decision happens before the server ever boots.
EC2
EC2 (Elastic Compute Cloud) is the rent-a-server service, the concrete version of everything in unit 1. One rented server is an instance. EC2 is the oldest and most-used compute service on AWS: most production software that is not serverless runs on EC2 or on services built on top of it, so launching, sizing, and securing instances is week-one work on real infrastructure teams.
An instance is a virtual machine (VM): software that behaves exactly like a physical computer. A layer called a hypervisor runs on AWS's real hardware and slices it into many isolated VMs, each with its own share of CPU, RAM, and its own operating system.
Here is the Docker connection. A container shares the host's operating system kernel and isolates just your process. A VM goes deeper and virtualizes the whole machine, its own kernel included. That makes VMs heavier but more isolated, which is why AWS can safely rent slices of one physical box to complete strangers. In practice you often run both: an EC2 instance as the host, your Docker containers on top of it.
When you launch an instance you pick three things: an AMI (Amazon Machine Image), the disk template it boots from (like "Ubuntu 24.04"), an instance type (the size), and the region and AZ it lives in, from lesson 1-2.
Splitting an instance type into its parts
Instance types are compact codes in the form family, generation, size, as in t3.micro. The family letter states the job: t for tiny burstable, m for general purpose, c for compute-heavy, r for RAM-heavy.
itype="t3.micro" family=${itype%%.*} size=${itype#*.} echo "instance type: $itype" echo "family: $family" echo "size: $size"
Output
instance type: t3.micro family: t3 size: micro
The two expansions are bash string slicing. ${itype%%.*} strips the longest match of .* from the end, leaving t3, and ${itype#*.} strips the shortest match of *. from the front, leaving micro.
Reading these codes fluently pays off constantly, because pricing pages, monitoring dashboards, and cost reports all identify machines this way and nowhere spell out what the letters mean.
Reading the catalog
Sizes scale by doubling: micro → small → medium → large → xlarge → 2xlarge and beyond, each step roughly doubling vCPUs, RAM, and price.
| type | vCPUs | RAM | rough price |
|---|---|---|---|
| t3.micro | 2 | 1 GB | ~$7.50/mo |
| t3.large | 2 | 8 GB | ~$61/mo |
| m5.xlarge | 4 | 16 GB | ~$140/mo |
| c5.2xlarge | 8 | 16 GB | ~$248/mo |
A t3.micro comfortably runs a hobby web app. Real workloads get measured, then sized, and thanks to elasticity from lesson 1-3 you can resize later with a reboot instead of buying a new machine.
Decoding c5.2xlarge
The same script splits a larger, compute-optimized type.
itype="c5.2xlarge" family=${itype%%.*} size=${itype#*.} echo "instance type: $itype" echo "family: $family" echo "size: $size"
Output
instance type: c5.2xlarge family: c5 size: 2xlarge
Reading the result
- Only the first line changed. The two slicing expressions work on any type code, because every one of them has exactly one dot.
c5is the compute-optimized family, 5th generation. Compute-optimized means a high ratio of CPU to RAM, for work like video encoding or request-heavy API servers.2xlargeis 8 vCPUs and 16 GB of RAM. Comparem5.xlargefrom the table above, which has the same 16 GB but only 4 vCPUs, and costs less. Same memory, different job.