AWS · Learn by doing

A little website.
A real cloud lesson.

Open an AWS account, keep costs small, and put a website online. Then rebuild the same design with Terraform and change its content with Ansible.

Beginner friendlyWindows + LinuxCopyable examplesPrivate S3 + HTTPS

These are sample names, not our infrastructure. Before copying commands, read Customize the examples: paths, keys, IPs and names. Substitute your own authorized account and resources. Existing lab? Retain its original resource names, usernames and state; renamed samples can cause replacement. See the existing-lab warning before applying updated recipes.

What “a server in S3” really means

Think of S3 as a toy box for your website files. CloudFront is the helper who brings those files to visitors. Terraform is the instruction sheet for building the toy box and helper. Ansible swaps the toys inside.

S3 is storage, not a computer running Windows or Linux. It can hold HTML, CSS, JavaScript and pictures. This lab creates a static website: the browser displays saved files. It cannot run Python, PHP, a database or a login backend in S3. For those jobs, you would add a service such as Lambda or EC2 in a later lab.

Visitor's browserAsks for a page
CloudFront + HTTPSDelivers a cached copy
Private S3 bucketStores index.html

The website is public; the bucket stays private. CloudFront receives permission to read it. We use the normal S3 bucket origin, not the S3 website endpoint, because website endpoints only support HTTP and do not support origin access control.

AWS: S3 website limitationsAWS: private S3 origins

1. Open your account and find your credits

  1. Visit AWS Free Tier and choose Create an AWS account. Use an email you control and a unique password. Complete the contact, payment and identity verification steps shown by AWS. Select Basic support for learning.
  2. Choose the Free account plan if offered and you want a limited learning account. A Paid plan can charge for usage beyond applicable credits. Read the signup summary before accepting it.
  3. Open Billing and Cost Management. Check your account plan, credit balance, expiration dates and available credit-earning activities.
  4. New eligible customers receive $100 at signup and can earn up to $100 more through the activities AWS presents. Follow those activities individually and remove their resources afterward. An S3 upload alone does not guarantee a credit reward.
The Free account plan ends after six months or when credits run out, whichever comes first. It is not permanent hosting. AWS closes an expired Free-plan account unless it is upgraded. Credits have a separate expiration date. Existing or former AWS customers may not qualify; creating another account does not make you a new customer.

AWS: current eligibility, credits and plan rules

Put a lock on the account

Your first login is the root user, the master key to the account. Enable multi-factor authentication (MFA) in its security credentials page. Keep recovery details safe. Use root for initial account administration only, never for routine deployments or access keys.

Create a separate lab identity

If you already have IAM Identity Center, use its existing temporary-credential login. Otherwise, for this standalone beginner lab, create an IAM user named contoso-lab and a customer-managed policy using the JSON below. Attach the policy to that user. If you enable console sign-in, give the user a unique password and MFA, then use its account sign-in URL for the lab.

This policy allows S3 administration only for buckets beginning contoso-lab-. Its listed CloudFront permissions apply across the account, so use it in a dedicated learning account. It is a lab starting point, not a tightly scoped production policy. Do not attach it to a user in a shared production account.

Ready to continue: you know the account plan and credit expiry, root MFA is enabled, and your lab identity is separate from root.

2. Make the bill boring

A budget alert is a smoke alarm. It tells you something is happening; it does not turn off the stove.

  1. In Billing, open Budgets → Create budget. If available, start with the zero-spend template. Also create a monthly cost budget of $5 with email alerts at 50%, 80% and 100%, plus a forecasted 100% alert.
  2. For your learning budget, inspect the advanced cost options and exclude credits and refunds so credits do not hide underlying service usage. Check both this budget and the actual bill.
  3. Enable Free Tier usage alerts in Billing preferences if offered. Read the email you receive, and check Billing after every lab. Cost data and alerts can arrive late.
  4. Keep the website tiny: one HTML file, a few manual visits, no large downloads or traffic generators. Use us-east-1 for this example and label resources Project=ContosoLearning.
  5. Use the supplied CloudFront domain. Skip buying a domain, Route 53, WAF, access logs and extra services for this first lab. No EC2 computer, NAT gateway or load balancer is needed.
What can cost money?
ItemWhat to watch
S3Stored data and read/write requests. Delete unused files; do not upload large videos.
CloudFrontRequests and data delivered, depending on pricing plan and allowances. Caching reduces origin reads.
Optional extrasDomains, DNS, logs and paid security options can create separate charges.
Aim: a tiny learning workload, not a guaranteed $0 bill. CloudFront now offers a $0 flat-rate plan as well as pay-as-you-go pricing. The Terraform example below creates a pay-as-you-go distribution; it does not enroll in the flat-rate plan. Verify the current allowances and your account's eligible services before applying it. PriceClass_100 limits edge locations, not spending.

For an estimate, open the AWS Pricing Calculator, add S3 and CloudFront, and try 0.01 GB stored, 1,000 S3 reads, 100 writes, 1,000 viewer requests and 0.1 GB delivered per month. These are example inputs, not enforced limits. Review the region and delivery geography; calculate before credits and compare with the offers in your account.

AWS: budget delays and behaviorS3 pricingCloudFront pricingPricing Calculator

3. Build it once in the browser

This part works the same on Windows and Linux. Console labels can change; follow the settings below rather than choosing paid add-ons when offered.

A. Save your first page

Copy this into a text editor and save it as index.html using UTF-8. On Windows, choose “All files” in Notepad so it does not become index.html.txt. Open it locally to see the page.

B. Create the S3 bucket

  1. Sign in as your lab user. Open S3 → Create bucket, choose a general purpose bucket in us-east-1, and give it a unique name such as contoso-lab-blue-kite-7f3a-manual. Change the random suffix if taken. Bucket names are globally unique and can appear in URLs; do not use personal information.
  2. Keep Block all public access enabled, ACLs disabled (Bucket owner enforced), and default S3-managed encryption (SSE-S3). Leave versioning off for this disposable lab. Do not enable static website hosting.
  3. Create the bucket, open its Objects tab, and upload index.html at the bucket root. In upload properties set content type to text/html and Cache-Control to public, max-age=60 if available. Keep the original local file.

C. Add the HTTPS delivery service

  1. Open CloudFront → Create distribution. To match the Terraform lab, use a standard pay-as-you-go distribution; inspect the price summary before creating it.
  2. Choose your S3 bucket as the origin using its regular bucket domain. Do not switch to the website endpoint if suggested.
  3. For origin access, create an Origin Access Control (OAC), select S3 and “Sign requests / always.” Think of OAC as CloudFront's signed permission slip.
  4. Set default root object to index.html, viewer protocol policy to Redirect HTTP to HTTPS, and allowed methods to GET and HEAD. Use the default CloudFront certificate and no custom domain.
  5. For the learning cache, use a custom/legacy cache configuration with minimum TTL 0, default 60 seconds and maximum 300 seconds; forward no cookies or query strings. This keeps content updates easy to observe. Decline optional paid add-ons.
  6. Create the distribution. Copy the bucket policy generated for this OAC into S3 → your bucket → Permissions → Bucket policy. It should allow cloudfront.amazonaws.com to read objects only when AWS:SourceArn equals this distribution's ARN. Keep public access blocked.
  7. Wait until deployment finishes, then open https://YOUR-DISTRIBUTION.cloudfront.net. CloudFront supplies HTTPS for this address; no certificate purchase is needed.
Prove it: the HTTPS CloudFront address shows your page; an unsigned direct S3 object URL returns AccessDenied. That denial means the bucket is private.

OAC and generated bucket policyHTTPS between CloudFront and S3

4. Prepare Windows or Linux

Use PowerShell on Windows for AWS CLI and Terraform. Ansible runs from Ubuntu in WSL on Windows, or directly on Linux. WSL is a small Linux environment inside Windows; it is not an AWS resource.

  1. Install AWS CLI v2 for Windows using its official installer.
  2. Install Terraform for Windows, put its executable on PATH, and open a new PowerShell window.
  3. Run the checks below. Install WSL from an administrator PowerShell window, restart if requested, and open Ubuntu to finish its username/password setup. Your local Linux password is separate from AWS.
PowerShell · verify tools / install WSL
aws --version
terraform version
wsl --install -d Ubuntu

For Terraform, keep the lab in a local folder such as C:\Labs\contoso-aws, outside a shared or cloud-synced folder. For Ansible, copy index.html and update-site.yml into ~/contoso-ansible inside Ubuntu. Install AWS CLI there too and configure the contoso profile again; Windows credentials do not automatically become WSL credentials.

Let the tools use your lab identity

Prefer temporary credentials if your existing account offers them. With an already configured IAM Identity Center portal, run aws configure sso --profile contoso, then aws sso login --profile contoso. Do not create an AWS Organization solely for this lab: joining one can change Free Tier credit eligibility.

For the standalone IAM user from step 1, an administrator can create a lab access key in IAM → Users → contoso-lab → Security credentials. Choose the CLI use case and configure it below. Enter the key only at the prompts. The local credentials file stores it in plain text, so keep it outside your project and remove the key after cleanup. This fallback uses long-lived keys; MFA on console sign-in does not automatically protect API calls made with them.

PowerShell or Linux terminal · standalone IAM-user fallback
aws configure --profile contoso
# Enter your lab user's access key and secret at the prompts.
# Default region: us-east-1
# Default output format: json
aws sts get-caller-identity --profile contoso

The last command should show the lab identity, not root. Do not post its output publicly. Never place keys in HTML, Terraform, Ansible, Git or screenshots.

AWS: access-key handlingAWS: existing SSO setupMicrosoft: install WSL

5. Rebuild the design with Terraform

Instead of clicking each setting, write down what the finished setup should look like. Terraform reads that recipe, shows the changes, then builds it.

This recreates the same design in a second, new bucket. It does not take over the manually created resources. Use a different bucket name. When this copy works, remove the manual copy using step 7 so you do not leave two labs running.

Save the following as main.tf in your empty Terraform lab folder. Save a copy of index.html in that folder too. The code keeps S3 private, signs origin requests, redirects visitors to HTTPS and uses a short cache. It intentionally leaves page content outside Terraform so Ansible can change it later.

Your three important commands

init downloads the AWS provider. plan previews the proposed resources. apply executes the reviewed plan. The provider version is limited to major version 6; keep the generated lockfile so later runs use the same selected version.

Create terraform.tfvars with the line below. Replace the name with a globally unique one beginning contoso-lab-. Keep this name distinct from your manual bucket.

terraform.tfvars
bucket_name = "contoso-lab-blue-kite-7f3a-code"
PowerShell or Linux terminal · run in the lab folder
terraform init
terraform fmt
terraform validate
terraform plan -out=lab.tfplan
# Read the plan. Expect only this lab's S3 and CloudFront resources.
terraform apply lab.tfplan
terraform output

Applying the saved plan creates resources without another yes/no prompt, so inspect it first. CloudFront can take several minutes to deploy. The output prints the bucket, distribution ID and HTTPS address. Save .gitignore below before committing anything; state and saved plans can expose account details. Keep local state safe because Terraform needs it to remove resources.

Upload the page, then open the URL

PowerShell · upload your first page
$labBucket = terraform output -raw bucket_name
aws s3 cp ./index.html "s3://$labBucket/index.html" --profile contoso --region us-east-1 --content-type "text/html; charset=utf-8" --cache-control "public, max-age=60"
terraform output -raw site_url
Prove it: the output URL displays your page. Run terraform plan again: without configuration changes, expect no infrastructure changes. The uploaded file is intentionally not a Terraform resource.

Terraform: CloudFront configurationHashiCorp: Terraform basics

6. Change the page with Ansible

Terraform builds the bookshelf. Ansible replaces the book on it. Neither tool needs to log into a server in this lab.

Use a Linux terminal (Ubuntu in WSL for Windows). Install Ansible and its AWS dependencies in a Python virtual environment, a separate toolbox that keeps packages out of the system Python.

Ubuntu / WSL terminal · install the toolbox
sudo apt update
sudo apt install -y python3 python3-venv
python3 -m venv ~/.venvs/contoso
source ~/.venvs/contoso/bin/activate
python -m pip install ansible boto3 botocore
ansible-galaxy collection install amazon.aws
ansible --version
aws sts get-caller-identity --profile contoso

Save update-site.yml beside your local index.html. Change the heading or a sentence in that HTML file, then run the playbook with the bucket name from Terraform's output. The playbook writes just index.html, not the whole bucket.

Ubuntu / WSL terminal · publish the change
# Replace the example name with your Terraform bucket output.
ansible-playbook update-site.yml --syntax-check
ansible-playbook update-site.yml -e 'bucket_name=contoso-lab-blue-kite-7f3a-code'
# Run again without editing the file: expect changed=0.
ansible-playbook update-site.yml -e 'bucket_name=contoso-lab-blue-kite-7f3a-code'

Revisit the HTTPS site after about a minute and refresh. CloudFront and your browser may still have an old copy cached. If you previously used a longer cache policy, wait for that earlier lifetime to expire. No invalidation is needed for this short-cache exercise.

Prove it: your edited sentence appears online, a second unchanged Ansible run reports no change, and terraform plan still reports no infrastructure changes.

Ansible: S3 object module and dependencies

7. Finish the lab and stop paying for leftovers

The manual copy

  1. In CloudFront, select the manual lab distribution, disable it, wait until that change is deployed, then delete it.
  2. In S3, open the manual lab bucket, verify its exact name, empty it and then delete it. Emptying permanently removes its files. Keep your local copy first.
  3. Delete the unused manual Origin Access Control after its distribution is gone.

The Terraform copy

The bucket contains a file uploaded outside Terraform. Empty only this lab bucket first; then destroy the infrastructure. These commands permanently remove that bucket's contents and the resources in this folder's state. Verify the printed name before running the deletion.

PowerShell · lab cleanup
$labBucket = terraform output -raw bucket_name
$labBucket
# Stop if this is not your disposable contoso-lab- bucket.
aws s3 rm "s3://$labBucket" --recursive --profile contoso --region us-east-1
terraform plan -destroy -out=cleanup.tfplan
# Read the deletion plan before proceeding.
terraform apply cleanup.tfplan

CloudFront removal may take several minutes. If it fails, keep your state and rerun the destroy plan after resolving the error. If you enabled versioning outside this guide, delete all versions and delete markers too: aws s3 rm alone will not empty a versioned bucket.

  1. Confirm both lab buckets and distributions are gone in the AWS console. Check other regions for resources created while earning credits.
  2. Deactivate and delete the IAM access key used for the lab. Remove the lab identity/policy when no longer needed. For SSO, use aws sso logout.
  3. Keep budgets enabled and inspect Billing again after usage data catches up. Destroying resources stops future use; it does not erase charges already incurred.
Your portfolio: keep the code, a simple architecture drawing and an explanation of OAC, HTTPS, caching and cleanup. Remove identifiers and credentials from screenshots before sharing.

When something goes wrong

SymptomTry this
Bucket name already takenChange the random suffix. If Terraform already created it, inspect the plan before renaming: renaming replaces the bucket.
CloudFront shows 403Confirm index.html exists at the root, default root object is set, origin is the normal bucket endpoint, and the OAC policy points to the correct distribution ARN. Keep public access blocked.
Direct S3 URL shows AccessDeniedThis is expected for the private bucket. Test through CloudFront.
Old page still appearsWait for the previous cache lifetime, then refresh. Verify Ansible targeted the code lab's bucket, not the manual bucket.
Profile not found / expired loginConfigure contoso in the same Windows, Linux or WSL environment that runs the tool. Renew SSO login if needed.
AccessDenied during applyCheck the lab policy is attached to the correct identity, the bucket begins contoso-lab-, and the account plan permits the service. Inspect the denied action before adding permissions.
Ansible module missingActivate the Python environment and install amazon.aws with ansible-galaxy; make sure boto3 and botocore are installed there.
Destroy says bucket not emptyVerify the exact lab bucket and remove its files. Versioned buckets also require deleting older versions and delete markers.

You have now learned storage, identity, HTTPS, caching, infrastructure as code and repeatable content updates—without running a virtual server.