DEV Community

Cover image for Hosting a Static Website on Amazon S3 Using the AWS CLI 🌐🪣
Hashir Saud Khan
Hashir Saud Khan

Posted on

Hosting a Static Website on Amazon S3 Using the AWS CLI 🌐🪣

Creating a Website on S3

INTRO
This lab is about hosting a real static website — a Café & Bakery site — entirely on Amazon S3, using nothing but the AWS CLI from an EC2 instance. Along the way, you create a new IAM user with S3 access, upload the actual website files, and then build your own script so future updates take one command instead of repeating the whole upload process by hand.

AWS S3 static website

By the end, you'll have a real public URL you can open in a browser and see the site live.

TASK 1: CONNECT TO THE EC2 INSTANCE USING SSM
You start by connecting to the instance through Systems Manager Session Manager — either from the EC2 console (select the instance, choose Connect, then the Session Manager tab) or through a session link if your environment provides one. Either way, this drops you straight into a terminal session on the instance, no SSH key involved.

Once connected, switch to the ec2-user account:

sudo su -l ec2-user
pwd
Enter fullscreen mode Exit fullscreen mode

Why we did this: Every command in this lab needs to run from inside this instance, so this is just making sure you're actually in the right shell, as the right user, before doing anything else.

TASK 2: CONFIGURE THE AWS CLI
Amazon Linux comes with the AWS CLI already installed — unlike Red Hat, you don't need to download or unzip anything here. You just authenticate it:

aws configure
Enter fullscreen mode Exit fullscreen mode

Then supply:

  • Access Key ID and Secret Access Key for your IAM user
  • Region: us-west-2
  • Output format: json

Why we did this: The CLI is just a tool — on its own it doesn't know which AWS account it's allowed to touch. This step is what connects it to your actual lab account so every command after this actually does something.

TASK 3: CREATE AN S3 BUCKET
Bucket names have to be globally unique across all of AWS, so you pick something like your initials plus a few random numbers:

aws s3api create-bucket --bucket <your-bucket-name> --region us-west-2 --create-bucket-configuration LocationConstraint=us-west-2
Enter fullscreen mode Exit fullscreen mode

A successful run returns a JSON response with the bucket's location.

Why we did this: This bucket is going to be the website — every file you upload later needs somewhere to live first. Specifying the region explicitly matters too, because S3 defaults to us-east-1 if you don't say otherwise, and this lab needs everything in us-west-2 to line up.

TASK 4: CREATE A NEW IAM USER WITH S3 ACCESS
Instead of doing everything as the root/admin lab user, you create a dedicated IAM user just for this task:

aws iam create-user --user-name awsS3user
aws iam create-login-profile --user-name awsS3user --password Training123!
Enter fullscreen mode Exit fullscreen mode

Then you sign out of the console, sign back in as awsS3user using the account ID, and try opening S3 — you'll get an access error, because this new user has zero permissions yet.

Back in the terminal, you find the AWS-managed policy that grants full S3 access:

aws iam list-policies --query "Policies[?contains(PolicyName,'S3')]"
Enter fullscreen mode Exit fullscreen mode

And attach it:

aws iam attach-user-policy --policy-arn arn:aws:iam::aws:policy/<policyYouFound> --user-name awsS3user
Enter fullscreen mode Exit fullscreen mode

Why we did this: This is the same principle you'd apply in any real environment — don't do everything as a god-mode account. You create a user scoped to exactly the service it needs (S3, and nothing else), and you can see the access error firsthand before attaching the policy, so you actually witness IAM permissions in action rather than just taking it on faith.

TASK 5: OPEN UP THE BUCKET'S PUBLIC ACCESS
By default, S3 buckets block all public access — good for security, but a website needs to be reachable by anyone. So in the bucket's Permissions tab, you turn off Block all public access, then enable ACLs under Object Ownership.

Why we did this: A private bucket and a public website are contradictory by definition. This step is you deliberately telling AWS "yes, I want this specific bucket to be visible to the internet" — it's not a default AWS assumes for you, because most buckets shouldn't be public.

TASK 6: EXTRACT THE WEBSITE FILES
Back in the terminal:

cd ~/sysops-activity-files
tar xvzf static-website-v2.tar.gz
cd static-website
ls
Enter fullscreen mode Exit fullscreen mode

You should see index.html plus css and images folders — that's the actual site.

Why we did this: The lab ships the website as a compressed archive, so this step just unpacks it into a real folder structure you can upload, the same way you'd receive a client's website files as a zip and need to extract them before deploying anything.

TASK 7: UPLOAD THE FILES AND ENABLE STATIC HOSTING
First, tell S3 which file is the homepage:

aws s3 website s3://<my-bucket>/ --index-document index.html
Enter fullscreen mode Exit fullscreen mode

Then upload everything:

aws s3 cp /home/ec2-user/sysops-activity-files/static-website/ s3://<my-bucket>/ --recursive --acl public-read
Enter fullscreen mode Exit fullscreen mode

--recursive uploads every file in that folder and its subfolders. --acl public-read makes each uploaded file individually viewable by anyone — remember, opening bucket access in Task 5 controls the bucket, but this flag controls the objects inside it.

Confirm the upload:

aws s3 ls <my-bucket>
Enter fullscreen mode Exit fullscreen mode

Then head to the bucket's Properties tab, confirm Static website hosting is Enabled, and open the Bucket website endpoint URL — your Café & Bakery site is now live.

Why we did this: This is the actual deployment — everything before this was just setup. Notice you needed two separate permissions here: the bucket-level public access (Task 5) and the object-level public-read ACL (this task). Miss either one, and visitors get an access-denied error instead of your website.

TASK 8: BUILD A SCRIPT SO YOU DON'T REPEAT YOURSELF
Re-uploading manually every time you change a file gets old fast, so you turn that upload command into a reusable script.

Check your command history to find the exact aws s3 cp line you ran:

history
Enter fullscreen mode Exit fullscreen mode

Create and edit a new file:

cd ~
touch update-website.sh
vi update-website.sh
Enter fullscreen mode Exit fullscreen mode

Inside, add:

#!/bin/bash
aws s3 cp /home/ec2-user/sysops-activity-files/static-website/ s3://<my-bucket>/ --recursive --acl public-read
Enter fullscreen mode Exit fullscreen mode

Save with Esc, then :wq, then make it executable:

chmod +x update-website.sh
Enter fullscreen mode Exit fullscreen mode

Now make an actual change to the site — edit index.html and swap a couple of background colors — then run your new script:

./update-website.sh
Enter fullscreen mode Exit fullscreen mode

Refresh the site in your browser and you'll see your change live.

Why we did this: This is the difference between a one-time task and a repeatable process. The first upload was you doing the work manually; this script means every future update is one command, run the same way, every time — no risk of forgetting a flag or mistyping a bucket name six months from now.

OPTIONAL CHALLENGE: SWITCH FROM cp TO sync
Here's the catch with the script you just built — aws s3 cp --recursive re-uploads every single file, every single time, even the ones that haven't changed at all.

The fix is to swap it for aws s3 sync:

aws s3 sync /home/ec2-user/sysops-activity-files/static-website/ s3://<my-bucket>/ --acl public-read
Enter fullscreen mode Exit fullscreen mode

Make a small change to index.html, run this instead of cp, and refresh the site to confirm it still works.

Why we did this: sync compares what's already in the bucket against what's on your local disk, and only uploads files that are new or changed — not everything, every time. For a small site with three files that barely matters, but imagine a real site with hundreds of images: cp --recursive would re-upload all of them on every single deploy, while sync would only push the one file you actually touched. Same result on screen, far less wasted bandwidth and time behind the scenes.

WHY THIS MATTERS OVERALL

  • S3 static hosting means you don't need a running server at all — no EC2 instance, no patching, just files sitting in a bucket, served directly to the browser
  • Bucket-level public access and object-level ACLs are two separate switches — both need to be flipped for a public website to actually work
  • Creating a scoped IAM user instead of using the account's root permissions is a habit worth carrying into real projects, not just this lab
  • cp --recursive vs sync is a small command swap with a real efficiency difference once your site is bigger than three files

AWS #S3 #CLI #StaticWebsite #IAM #CloudComputing

Top comments (0)