Laptop to Production

Servers on EC2

Launching a machine, and what an image is.

Time
40 minutes
Level
Beginner

Part 1: The problem

QuickCart, the first server

QuickCart’s first API server was set up by Leo, one evening, by hand. He created an SSH key, saved it on his laptop, opened port 22 to the whole internet, logged in and installed everything by typing commands.

It worked for months. Then the server had to be replaced. Nobody could remember the exact steps, and the only key was on Leo’s laptop, which had just been wiped. It took two days to make a new server that behaved like the old one.

So QuickCart wants servers that:

  • can be rebuilt from scratch, by a machine, in minutes;
  • need no open login port and no key file on anyone’s laptop;
  • get AWS permissions without anyone storing a password or key on them.

Part 2: The idea

An EC2 instance is a computer you rent from AWS by the second. You choose what it runs, how big it is, and which network it sits in. When you don’t need it any more, you delete it and the bill stops.

What you choose when you launch one

ChoiceWhat it isQuickCart’s choice
AMIThe starting disk: an operating system, and anything already installedAmazon Linux 2023
Instance typeHow much CPU and memoryt3.micro
NetworkWhich VPC and subnet it sits inA private app subnet (the default VPC while learning)
Security groupIts firewallNo inbound rules at all
Key pairAn SSH key for logging inNone — Session Manager instead
IAM roleThe AWS permissions the server’s software getsA role with Session Manager access
StorageThe disk, an EBS volume8 GiB gp3, the default
User dataA script that sets the server up on first bootInstalls the web server

The AMI: the starting disk

An AMI (Amazon Machine Image) is a snapshot of a disk. Every instance starts as a copy of one. AWS publishes AMIs for common operating systems, like Amazon Linux 2023 and Ubuntu, and keeps them patched.

Teams later build their own AMIs with their software already installed. Those are often called golden images. They make new servers start faster, because there’s less to install at boot.

The instance type: how big

The name t3.micro has three parts:

PartMeans
tThe family. T is general purpose and “burstable”
3The generation. Higher is newer, and usually better value
microThe size within the family: nano, micro, small, medium, large, xlarge and up
FamilyGood atTypical use
T, MA balance of CPU and memoryWeb servers and APIs
CCPUEncoding video, heavy calculations
RMemoryCaches, in-memory data

Burstable means a T instance runs at a low baseline and earns credits while it’s quiet. It spends them to run faster during short busy spells. That suits a small web server. It doesn’t suit something that works flat out all day: once the credits are gone, it either slows down or charges extra, depending on its credit setting.

Start small and watch the CPU graph. Choosing the smallest type that copes is called right-sizing.

User data: setting up without logging in

User data is a script you paste in when you launch. The instance runs it once, as root, on its first boot. That’s how a plain Amazon Linux AMI becomes a QuickCart server without anyone logging in.

Its output goes to /var/log/cloud-init-output.log on the server. That’s the first place to look when a new server doesn’t behave.

User data is not secret. Anyone who can view the instance’s settings can read it. Never put a password in it; Secrets covers where passwords go.

Reboot, stop and terminate

These three sound similar. They’re very different:

ActionInstance IDRoot diskPublic IPCompute billUser data runs again?
RebootSameKeptSameContinuesNo
Stop, then startSameKeptNew oneStops while stopped; the disk is still billedNo
TerminateGoneDeleted by defaultGoneStops— it’s gone

Terminating is permanent. When auto scaling replaces a server, it terminates the old one and launches a new one from the AMI and user data. Anything that was only on the old disk is lost.

Key point: Treat every server as replaceable: everything it needs must come from the AMI and the user data, never from someone typing on it.

Getting in without SSH keys: Session Manager

The old way to reach a server is SSH: open port 22, create a key pair, and keep the private key file safe on every laptop that needs access. Keys get copied, lost and never rotated, and port 22 is scanned by bots all day.

Session Manager, part of AWS Systems Manager, gives you a shell in the browser instead:

SSH with a key pairSession Manager
Inbound port22, open to someoneNone
What proves who you areHaving the key fileYour IAM login
Losing accessLose the key fileSomeone removes your IAM permission
Record of who did whatNone by defaultEvery session can be logged
Works in a private subnetOnly through a jump serverYes, if it can reach Systems Manager

It works because a small program on the server, the SSM agent, makes an outbound connection to AWS and waits for you. Nothing ever connects in. It needs three things:

NeedsWhyHow
The SSM agentIt holds the connection openAlready installed on Amazon Linux 2023
An IAM role with AmazonSSMManagedInstanceCoreSo Systems Manager accepts the agentThe instance profile, below
A way out to Systems Manager on port 443The agent has to reach AWSA public IP, a NAT gateway, or VPC endpoints — and an outbound security group rule

Roles: AWS permissions without keys

QuickCart’s code on the server will need to call AWS: read a file from S3, fetch a password. The wrong way is to paste an access key into a config file. The right way is to give the server an IAM role.

You attach a role to an instance through an instance profile, a small wrapper around the role. When you create a role for EC2 in the console, it creates the instance profile for you, with the same name. From then on, the AWS SDKs and the AWS CLI on the server pick up short-lived credentials automatically, and AWS rotates them for you. Who can do what: IAM explains roles in full.

IMDSv2: protecting those credentials

The credentials come from the instance metadata service, a special address every instance can call: 169.254.169.254. It answers questions like “what’s my instance ID?” and “what are my role’s credentials?”.

VersionHow a request worksThe risk
IMDSv1Any simple GET gets an answerA bug that tricks your app into fetching a URL can fetch the credentials — the case above
IMDSv2First a PUT to get a token, then every GET must send the tokenThat kind of bug can rarely send a PUT with the right header

Set Metadata version to V2 only (token required) on every instance. Amazon Linux 2023 already defaults to it, but saying so explicitly means it doesn’t depend on the AMI you pick.

Part 3: See it

Launch a server, fix something on it by hand, then reboot, stop, start and terminate it. Watch what happens to its ID, its public IP and its disk.

The instance

State
not launched
Instance ID
none
Public IP
none
Compute bill
nothing
Disk bill
nothing

Its disk

  • No disk yet

What happened

  1. Nothing yet
ReadyChoose Launch from the AMI to start a server.

What to notice: your hand-made fix survived a reboot and a stop, so it felt safe. It only disappeared when the server was replaced — which is exactly what auto scaling does, at 3 a.m., without asking.

Part 4: Do it

Pennies — one t3.micro for under an hour is about 2 cents in us-east-1, including its public IP address and disk. If your account is eligible for the free tier, it may cost nothing. Prices change, so check the EC2 pricing page if you’re unsure.

You’ll create a role for Session Manager, launch a server that sets itself up with user data, connect to it without any key, and see IMDSv2 refuse a request. About 30 minutes.

Check the region selector at the top right says N. Virginia (us-east-1).

  1. Create the role for the server

    In the AWS console: IAMRolesCreate role

    FieldEnter
    Trusted entity typeAWS service
    Service or use caseEC2, then the EC2 use case

    Choose Next. In the permissions search box, type AmazonSSMManagedInstanceCore, tick it, and choose Next.

    FieldEnter
    Role namequickcart-ec2-ssm
    DescriptionLets QuickCart servers use Session Manager

    Choose Create role.

    You should see

    quickcart-ec2-ssm in the list of roles. Behind the scenes, the console also made an instance profile with the same name. That’s what you’ll pick when you launch.

  2. Create a security group with no way in

    In the AWS console: EC2Security GroupsCreate security group

    FieldEnter
    Security group namequickcart-web-sg
    DescriptionQuickCart web server - no inbound access
    VPCThe default VPC
    Inbound rulesNone. Leave the list empty
    Outbound rulesLeave the default: All traffic to 0.0.0.0/0

    Choose Create security group.

    You should see

    The group, with 0 inbound rules and 1 outbound rule. Nothing on the internet can start a connection to a server in this group. The server can still reach out, which Session Manager needs.

  3. Launch the instance

    In the AWS console: EC2InstancesLaunch instances

    FieldEnter
    Namequickcart-web-1
    Application and OS ImagesAmazon Linux, then Amazon Linux 2023 AMI, 64-bit (x86)
    Instance typet3.micro
    Key pair (login)Proceed without a key pair

    Under Network settings, choose Edit:

    FieldEnter
    VPCThe default VPC
    SubnetNo preference
    Auto-assign public IPEnable
    Firewall (security groups)Select existing security group, then quickcart-web-sg

    Leave Configure storage at its default: 8 GiB, gp3.

    Open Advanced details:

    FieldEnter
    IAM instance profilequickcart-ec2-ssm
    Metadata versionV2 only (token required)
    User dataThe script below
    user-data.sh
    #!/bin/bash
    dnf install -y nginx
    echo "QuickCart web server, set up by user data" > /usr/share/nginx/html/index.html
    systemctl enable --now nginx
    LineWhat it does
    #!/bin/bashRun this script with bash. User data must start with a line like this, or it isn’t run as a script
    dnf install -y nginxInstall nginx, a small web server. dnf is Amazon Linux’s package installer; -y answers yes to its question, because nobody is there to type it
    echo "…" > /usr/share/nginx/html/index.htmlWrite one line of text into nginx’s home page file. > sends the text into the file instead of the screen
    systemctl enable --now nginxStart nginx now (--now), and start it again on every boot (enable)

    Why a public IP when nothing can connect in? In the default VPC, it’s the server’s only way out to Systems Manager. In QuickCart’s real network the server sits in a private subnet and goes out through a NAT gateway or VPC endpoints instead.

    Choose Launch instance.

    You should see

    Success, with a link to the instance ID. In EC2 → Instances, the state goes from Pending to Running in under a minute. Wait until Status check shows 3/3 checks passed. That can take two or three minutes.

  4. Connect with Session Manager

    In the AWS console: EC2Instancesquickcart-web-1ConnectSession Manager

    Choose Connect. A terminal opens in a new browser tab. No key, no port 22.

    If the Connect button is greyed out, wait a minute and reload: the agent registers itself shortly after boot.

    Check who you are:

    Terminal
    whoami
    PartWhat it means
    whoamiprint the name of the user you’re logged in as
    You should see
    ssm-user

    Session Manager logs you in as a user called ssm-user, which can use sudo.

  5. Check that user data ran

    Read the end of the log that user data writes to:

    Terminal
    sudo tail -n 3 /var/log/cloud-init-output.log
    PartWhat it means
    sudorun as root; this log is only readable by root
    tailprint the end of a file
    -n 3only the last 3 lines
    /var/log/cloud-init-output.logwhere the output of your user data script goes
    You should see

    A few lines from the end of the setup, finishing with one that starts Cloud-init v. and says finished at, with the date and how many seconds the boot took. Your version numbers and times will differ.

    Now ask nginx for its home page, from the server itself:

    Terminal
    curl localhost
    PartWhat it means
    curlmake a web request and print the answer
    localhostthis same machine
    You should see
    QuickCart web server, set up by user data

    Nobody logged in to install nginx. The user data did it.

  6. See IMDSv2 refuse a request without a token

    Ask the metadata service for the instance’s ID the old IMDSv1 way, with no token:

    Terminal
    curl -s -o /dev/null -w "%{http_code}\n" http://169.254.169.254/latest/meta-data/instance-id
    PartWhat it means
    curlmake a web request
    -ssilent: don’t show a progress bar
    -o /dev/nullthrow the answer’s body away
    -w "%{http_code}\n"print only the HTTP status code, then a new line
    http://169.254.169.254/latest/meta-data/instance-idthe metadata service’s address, and the question: “what’s my instance ID?”
    You should see
    401

    401 means Unauthorized. That’s IMDSv2 doing its job: no token, no answer. Now do it properly. First, get a token:

    Terminal
    curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 300"
    PartWhat it means
    curl -X PUTsend a PUT request, not the usual GET
    http://169.254.169.254/latest/api/tokenthe address that hands out tokens
    -H "X-aws-ec2-metadata-token-ttl-seconds: 300"a header asking for a token that lasts 300 seconds
    You should see

    A long string of letters and numbers. It has no new line at the end, so your next prompt starts straight after it on the same line. That string is the token: select and copy it, without the prompt.

    Then ask again, sending the token. Replace TOKEN with the string you copied:

    Terminal
    curl -H "X-aws-ec2-metadata-token: TOKEN" http://169.254.169.254/latest/meta-data/instance-id
    PartWhat it means
    -H "X-aws-ec2-metadata-token: TOKEN"send the token in a header
    http://169.254.169.254/latest/meta-data/instance-idthe same question as before
    You should see

    Your instance ID, starting i-, the same one the console shows. The AWS SDKs and CLI do this token dance for you automatically.

  7. Use the role, with no keys anywhere

    The AWS CLI is already installed on Amazon Linux 2023. Ask AWS who this server is:

    Terminal
    aws sts get-caller-identity
    PartWhat it means
    aws ststhe AWS service that deals with identities and credentials
    get-caller-identity“who am I?” It needs no special permission
    You should see

    A short JSON answer. The Arn line contains assumed-role/quickcart-ec2-ssm/ followed by the instance ID. The server is acting as the role you gave it. You never typed a key: the CLI fetched short-lived credentials from the metadata service, using a token.

    Close the Session Manager tab when you’re done.

  8. Stop and start it, and watch the IP change

    In the AWS console: EC2Instancesquickcart-web-1Details

    Write down the Public IPv4 address. Then choose Instance state → Stop instance, and confirm with Stop. When the state reads Stopped, choose Instance state → Start instance.

    You should see

    The same instance ID, but a different public IPv4 address. Connect with Session Manager again and run curl localhost: the page is still there, because the disk was kept.

    That changing address is why nothing should ever point straight at a server. The next lesson, Load balancers, puts one fixed address in front.

  9. Clean-up step: Clean up

    In the AWS console: EC2Instancesquickcart-web-1Instance stateTerminate instance

    Newer consoles label it Terminate (delete) instance. Confirm it. The instance goes to Shutting-down, then Terminated, and its disk is deleted with it.

    Then delete the security group:

    In the AWS console: EC2Security Groupsquickcart-web-sgActionsDelete security groups

    If it refuses because the group is still in use, wait until the instance shows Terminated and try again.

    Keep the quickcart-ec2-ssm role. IAM roles are free, and the next two lessons use it. If you aren’t carrying on, delete it in IAM → Roles.

    You should see

    The instance listed as Terminated (it disappears from the list after about an hour) and no quickcart-web-sg in the security groups. Nothing is left running.

Part 5: Check yourself

You fixed a config file by hand on a server. Later, auto scaling replaces that server. What happens to your fix?

What is user data?

Why does QuickCart use Session Manager instead of SSH keys?

An app on the server has a bug that lets an attacker make it fetch any URL. What stops them reading the server's role credentials from the metadata service?

You stop an instance and start it again the next day. What has changed?

Part 6: Cheat sheet

On one card
TermIn one line
InstanceA computer rented by the second
AMIThe starting disk every instance is copied from
t3.microFamily t, generation 3, size micro
User dataA script run once, as root, on first boot. Not secret
/var/log/cloud-init-output.logWhat your user data printed. Look here first
RebootSame ID, same disk, same IP
Stop, then startSame ID, same disk, new public IP
TerminateGone, disk and all
Session ManagerA shell with no open port and no key. Needs the agent, a role, and a way out on 443
Instance profileHow a role is attached to an instance
AmazonSSMManagedInstanceCoreThe policy Session Manager needs
IMDSv2Metadata needs a token first. Set V2 only
Golden ruleNever fix a server by hand. Change the AMI or user data

Finished the build and the quiz? Mark it done to fill in its stop on the route map.