Deploy a Website with GitHub Actions
Host a portfolio site on S3 and automate deployments with GitHub Actions.
Introduction
30 Second Summary
Every time you update a website, someone has to get those files to the server. Doing it manually means logging in, uploading files, and hoping you didn't miss anything. Professionals automate this so that a single code push triggers the entire deployment.
In this project, you will build a personal portfolio website, host it on AWS S3, and create a CI/CD pipeline with GitHub Actions that automatically deploys your site every time you push code.
What You'll Build
A personal portfolio page hosted on AWS S3 with automated deployments via GitHub Actions, so every code push updates your live site within seconds.
By the end of this project, you'll have:
- A live static website accessible via a public AWS S3 URL.
- An IAM user with least-privilege permissions scoped only to your S3 bucket.
- A GitHub Actions workflow that automatically deploys your code to S3 on every push to main.
- Secret Mission: Add CloudFront as a CDN in front of your S3 bucket for HTTPS and global caching.
Are there any prerequisites?
You need a GitHub account and an AWS account. Step 1 walks you through installing any tools you don't already have (Git and the AWS CLI).
Install and Configure Your Tools
Before you can deploy anything to the cloud, you need two tools installed on your machine. Git tracks your code changes and connects to GitHub. The AWS CLI lets you control AWS services directly from your terminal.
You also need an IAM user with permission to upload files to S3. This is how AWS knows who is making requests and what they are allowed to do.
In this step, get ready to:
- Install Git and the AWS CLI on your machine.
- Create an IAM user with least-privilege permissions for S3.
- Configure the AWS CLI with your new credentials.
Check and install Git
Git is the version control tool you will use to track changes and push code to GitHub.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or cmd (Windows) and press Enter to open it.
- Check if Git is already installed by running this command:
git --version
✔️ I see a version number
You are all set. Git is installed and ready to go.
ⓧ Command not found
Git is not installed yet. Follow these steps to install it.
- Open your browser and go to the Git for Windows download page:
- https://git-scm.com/downloads/win
- Click the 64-bit Git for Windows Setup link to download the installer.
- Run the downloaded .exe file and click Next through each screen, accepting the defaults.
- Click Install, then click Finish when the installation completes.
- Close and reopen your terminal so it picks up the new installation.
- Verify the installation by running:
git --version
You should see a version number like git version 2.47.1.windows.1 or similar.
Still seeing 'command not found'?
Close ALL open terminal windows and open a fresh one. The installer adds Git to your PATH, but existing terminals do not pick up the change automatically.
help me troubleshoot why Git is not recognized after installation
Check and install the AWS CLI
The AWS CLI is a command-line tool that lets you interact with AWS services from your terminal. You will use it to create buckets, upload files, and configure permissions.
- In your terminal, check if the AWS CLI is already installed by running this command:
aws --version
✔️ I see a version number
The AWS CLI is installed. You should see something like aws-cli/2.27.41 or a newer version.
ⓧ Command not found
The AWS CLI is not installed yet. Follow these steps to install it.
- Download the AWS CLI v2 installer for Windows (64-bit):
- https://awscli.amazonaws.com/AWSCLIV2.msi
- Run the downloaded AWSCLIV2.msi file.
- Click Next through each screen, accept the license agreement, and click Install.
- Click Finish when the installation completes.
- Close and reopen your terminal so it picks up the new PATH entry.
- Verify the installation by running:
aws --version
You should see output like aws-cli/2.27.41 Python/3.12.6 Windows/10 or similar.
Still seeing 'command not found'?
Close ALL open terminal windows and open a fresh one. The MSI installer adds the AWS CLI to your PATH, but existing terminals do not pick up the change.
help me troubleshoot why the AWS CLI is not recognized after installation
Create an IAM user and configure the AWS CLI
Your AWS account has broad permissions, but you should never use your root credentials for automation. Instead, you will create a dedicated IAM user with only the permissions needed to manage your S3 bucket.
What is an IAM user?
An IAM user is an identity within your AWS account that has specific permissions. By creating a separate user for this project, you follow the principle of least privilege. If the credentials are ever compromised, the attacker can only touch one S3 bucket.
- Open the AWS Management Console by going to https://console.aws.amazon.com/iam/ in your browser.
- Sign in with your AWS account credentials.
- In the left navigation pane, click Users.
- Click Create user.
- In the User name field, enter github-actions-deployer.
- Leave the Provide user access to the AWS Management Console checkbox unchecked. This user only needs programmatic access.
- Click Next.
- On the Set permissions page, do not add any permissions yet. Click Next.
- Click Create user.
Your user is created. Now you will attach an inline policy to give it the exact S3 permissions it needs.
- Click on the user name github-actions-deployer to open the user details page.
- Click the Permissions tab.
- Click Add permissions, then select Create inline policy.
- In the Policy editor section, click JSON to switch to the JSON editor.
- Delete any existing content in the editor and paste the following policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::[[BUCKET_NAME=\"my-portfolio-yourname\"]]",
"arn:aws:s3:::[[BUCKET_NAME=\"my-portfolio-yourname\"]]/*"
]
}
]
}
What does this policy do?
This policy follows the principle of least privilege. It only allows five specific S3 actions and scopes them to a single bucket.
- `s3:PutObject` and `s3:DeleteObject` let the CI/CD pipeline upload and remove files.
- `s3:GetObject` lets it read files from the bucket.
- `s3:ListBucket` and `s3:GetBucketLocation` let it enumerate the bucket contents and find its region.
- The two Resource lines cover both the bucket itself and all objects inside it.
Why an inline policy instead of an AWS managed policy?
AWS managed policies like AmazonS3FullAccess grant access to every bucket in your account. An inline policy lets you scope permissions to just one bucket. This is safer and teaches a critical DevOps skill.
- Click Next.
- In Policy name, enter S3DeployPolicy.
- Click Create policy.
You should see the policy appear under the Permissions tab for your user. Now you will generate access keys so the AWS CLI can authenticate as this user.
- Click the Security credentials tab.
- Scroll down to the Access keys section and click Create access key.
- On the Access key best practices & alternatives page, select Command Line Interface (CLI).
- Check the confirmation checkbox at the bottom of the page.
- Click Next.
- Skip the description tag and click Create access key.
- You will see your Access key ID and Secret access key. Copy both values and save them somewhere safe (like a temporary text file).
This is your only chance to view the secret key
AWS will never show you the secret access key again after you leave this page. If you lose it, you will need to delete the key and create a new one. Copy it now.
- Click Done.
Now you will configure the AWS CLI to use these credentials. Go back to your terminal.
- Configure the AWS CLI with your new credentials by running this command:
aws configure
What will this command ask?
The aws configure command prompts you for four values. Enter them exactly as shown below:
- AWS Access Key ID: paste the access key ID you just copied.
- AWS Secret Access Key: paste the secret access key you just copied.
- Default region name: enter us-east-1.
- Default output format: enter json.
- Verify the configuration worked by running:
aws sts get-caller-identity
You should see a JSON response containing your IAM user's ARN with github-actions-deployer in it.
Seeing 'Unable to locate credentials'?
Run aws configure again and make sure you paste the access key ID and secret access key without any extra spaces or line breaks.
help me troubleshoot why aws sts get-caller-identity returns an error about credentials
Your machine is ready. You have Git, the AWS CLI, and a properly scoped IAM user configured. Next up, you will build the portfolio website that gets deployed to the cloud.
Create Your Portfolio Website
Your tools are ready. Git is installed, your AWS CLI is configured, and your IAM user has the permissions it needs.
Now it's time to build the actual website you'll deploy. In this step, you'll create a simple portfolio page with HTML and CSS, then track it with Git so it's ready to push to the cloud.
In this step, get ready to:
- Create a project folder and initialize a Git repository.
- Build a portfolio page with HTML and CSS.
- Create an error page for 404 responses.
Create your project folder and initialize Git
Every project needs a home folder on your computer. You'll also initialize Git inside it so that every change you make is tracked from the start.
- Open your terminal. Press the Windows key, type Git Bash, and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir my-portfolio
cd my-portfolio
- Initialize a Git repository inside your new folder by running:
git init
You should see a message that says Initialized empty Git repository in ... confirming Git is tracking this folder.
- Confirm the folder exists by running:
ls
The output should be empty for now since you haven't added any files yet. That's expected.
Seeing 'No such file or directory'?
Make sure you ran cd ~/Desktop before creating the folder. If your Desktop path is different, check your actual Desktop location in File Explorer and navigate there instead.
Help me find my project folder on Windows.
Now open VS Code with your project folder loaded. This gives you a proper editor to create your website files.
- Open VS Code from your terminal by running:
code .
VS Code opens with your my-portfolio folder in the Explorer sidebar on the left.
code . not recognized?
If the code command isn't found, open VS Code manually. Press the Windows key, type Visual Studio Code, and press Enter. Then click File, select Open Folder, and choose the my-portfolio folder on your Desktop.
Build your portfolio page
Your portfolio needs two files working together. The index.html file holds the content and structure. The style.css file controls the visual design.
- In VS Code, hover over the Explorer sidebar where it says MY-PORTFOLIO.
- Click the New File icon (a page with a plus sign).
- Type index.html and press Enter.
- Paste the following code into index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Portfolio</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>Hi, I'm a Cloud Engineer</h1>
<p>Welcome to my portfolio. I build things in the cloud.</p>
</header>
<main>
<section class="about">
<h2>About Me</h2>
<p>I'm learning cloud computing and DevOps to build reliable, automated infrastructure. This site is deployed automatically using a CI/CD pipeline with GitHub Actions and AWS S3.</p>
</section>
<section class="skills">
<h2>Skills</h2>
<ul>
<li>AWS (S3, IAM, CloudFront)</li>
<li>CI/CD with GitHub Actions</li>
<li>Command Line and Git</li>
<li>Infrastructure as Code</li>
</ul>
</section>
<section class="projects">
<h2>Projects</h2>
<p>This website is my first cloud project. It is hosted on AWS S3 and deployed automatically every time I push code to GitHub.</p>
</section>
</main>
<footer>
<p>Deployed with GitHub Actions | Hosted on AWS S3</p>
</footer>
</body>
</html>
What does this HTML do?
This file creates a personal portfolio page with three sections. The header introduces you. The main area holds your About, Skills, and Projects sections. The footer mentions your deployment tools.
The <link rel="stylesheet" href="style.css"> line in the <head> connects this page to the stylesheet you'll create next.
- Save index.html by pressing Ctrl+S.
- Create another new file by clicking the New File icon in the Explorer sidebar.
- Name it style.css and press Enter.
- Paste the following code into style.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
header {
text-align: center;
padding: 3rem 0;
border-bottom: 2px solid #0073bb;
}
header h1 {
color: #0073bb;
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
header p {
font-size: 1.2rem;
color: #555;
}
main {
padding: 2rem 0;
}
section {
margin-bottom: 2rem;
}
h2 {
color: #0073bb;
margin-bottom: 1rem;
font-size: 1.5rem;
}
ul {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
ul li {
background: #e8f4fd;
color: #0073bb;
padding: 0.5rem 1rem;
border-radius: 4px;
font-size: 0.9rem;
}
footer {
text-align: center;
padding: 2rem 0;
border-top: 1px solid #ddd;
color: #777;
font-size: 0.9rem;
}
What does this CSS do?
This stylesheet gives your portfolio a clean, professional look. It centers the content at 800px wide, adds an AWS-blue accent color (#0073bb) to headings and borders, and turns the skills list into styled badges instead of plain bullets.
- Save style.css by pressing Ctrl+S.
- Open index.html in your browser by right-clicking the file in the Explorer sidebar and selecting Reveal in File Explorer, then double-clicking index.html.
You should see your portfolio page with a blue heading that says "Hi, I'm a Cloud Engineer", an About section, styled skill badges, and a footer.
Page looks like plain text with no styling?
Make sure style.css is saved in the same folder as index.html. The filename must be exactly style.css (lowercase, no typos) because the HTML references it by that exact name.
Help me fix my CSS not loading.
Create the error page
When someone visits a URL that doesn't exist on your site, S3 needs a page to show them. This is your 404 error page.
- In VS Code, click the New File icon in the Explorer sidebar.
- Name it error.html and press Enter.
- Paste the following code into error.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Not Found</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>404</h1>
<p>Page not found. Return to the <a href="index.html">homepage</a>.</p>
</header>
</body>
</html>
What does this page do?
This is a simple error page that shows a 404 heading and a link back to your homepage. It reuses your style.css so the error page matches the rest of your site's design.
- Save error.html by pressing Ctrl+S.
- Confirm all three files exist by switching back to your Git Bash terminal and running:
ls
You should see error.html, index.html, and style.css listed in the output.
Only see one or two files?
Check that you created all files inside the my-portfolio folder, not in a subfolder or on the Desktop. In VS Code, all three files should appear at the top level of the Explorer sidebar under MY-PORTFOLIO.
Help me find my missing files.
✔️ Awesome, I've got everything!
Great. Double check that all three files are saved in VS Code (no dots on the file tabs indicating unsaved changes).
ⓧ I'd like to double check the full code
Here's the complete content of each file. Compare yours against these:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Portfolio</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>Hi, I'm a Cloud Engineer</h1>
<p>Welcome to my portfolio. I build things in the cloud.</p>
</header>
<main>
<section class="about">
<h2>About Me</h2>
<p>I'm learning cloud computing and DevOps to build reliable, automated infrastructure. This site is deployed automatically using a CI/CD pipeline with GitHub Actions and AWS S3.</p>
</section>
<section class="skills">
<h2>Skills</h2>
<ul>
<li>AWS (S3, IAM, CloudFront)</li>
<li>CI/CD with GitHub Actions</li>
<li>Command Line and Git</li>
<li>Infrastructure as Code</li>
</ul>
</section>
<section class="projects">
<h2>Projects</h2>
<p>This website is my first cloud project. It is hosted on AWS S3 and deployed automatically every time I push code to GitHub.</p>
</section>
</main>
<footer>
<p>Deployed with GitHub Actions | Hosted on AWS S3</p>
</footer>
</body>
</html>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
header {
text-align: center;
padding: 3rem 0;
border-bottom: 2px solid #0073bb;
}
header h1 {
color: #0073bb;
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
header p {
font-size: 1.2rem;
color: #555;
}
main {
padding: 2rem 0;
}
section {
margin-bottom: 2rem;
}
h2 {
color: #0073bb;
margin-bottom: 1rem;
font-size: 1.5rem;
}
ul {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
ul li {
background: #e8f4fd;
color: #0073bb;
padding: 0.5rem 1rem;
border-radius: 4px;
font-size: 0.9rem;
}
footer {
text-align: center;
padding: 2rem 0;
border-top: 1px solid #ddd;
color: #777;
font-size: 0.9rem;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Not Found</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>404</h1>
<p>Page not found. Return to the <a href="index.html">homepage</a>.</p>
</header>
</body>
</html>
Your website files are ready and tracked by Git. Next up, you'll create an S3 bucket in AWS and configure it to serve these files as a live website.
Create and Configure Your S3 Bucket
You have your website files built and a Git repository initialized. Now you need a place on the internet to actually host them so anyone can visit your portfolio.
AWS S3 can serve static websites directly from a storage bucket. In this step, you'll create a bucket, turn on website hosting, and open it up for public access.
In this step, get ready to:
- Create an S3 bucket in your AWS account.
- Enable static website hosting on the bucket.
- Configure public access permissions so anyone can view your site.
Create your S3 bucket
An S3 bucket is a container that holds your files in the cloud. Your bucket name must be globally unique across all of AWS, so you'll personalize it.
What makes a valid bucket name?
S3 bucket names must be 3-63 characters long and can only contain lowercase letters, numbers, and hyphens. No uppercase letters or underscores are allowed. The name cannot start or end with a hyphen.
Since bucket names are globally unique, add your name or initials to avoid conflicts. For example: my-portfolio-jane-2026.
- Make sure your terminal is open and you are inside your my-portfolio folder.
- Create your S3 bucket by running this command:
aws s3 mb s3://[[BUCKET_NAME="your-bucket-name"]] --region us-east-1
What does this command do?
- aws s3 mb stands for "make bucket". It creates a new S3 bucket in your AWS account.
- --region us-east-1 specifies which AWS data center region to create the bucket in.
- You should see a message like make_bucket: your-bucket-name confirming the bucket was created.
Seeing a BucketAlreadyExists error?
This means someone else in the world already has that bucket name. Try adding your name, initials, or a few numbers to make it unique. Remember to use the same bucket name you specified in your IAM policy back in Step 1.
Help me fix my S3 bucket creation error
Enable static website hosting
By default, S3 buckets are just file storage. You need to tell AWS that this bucket should serve files as a website with proper HTML pages.
- Enable static website hosting on your bucket by running this command:
aws s3 website s3://[[BUCKET_NAME="your-bucket-name"]]/ --index-document index.html --error-document error.html
What does this command do?
- aws s3 website configures static website hosting on the bucket.
- --index-document index.html tells S3 which file to show when someone visits the root URL of your site.
- --error-document error.html tells S3 which file to show when someone visits a page that does not exist (a 404 error).
- This command produces no output on success. If you see no error message, it worked.
Your bucket now has a website endpoint URL. Note it down because you'll use it to visit your site later:
http://[[BUCKET_NAME="your-bucket-name"]].s3-website-us-east-1.amazonaws.com
Got an error about the bucket not existing?
Double check that the bucket name in this command exactly matches the one you created in the previous step. Even a single character difference causes a failure.
Help me troubleshoot my S3 website hosting configuration
Configure public access for your bucket
S3 buckets are private by default. AWS adds a safety layer called Block Public Access that prevents anyone from accidentally making bucket contents public. Since you want the world to see your website, you need to remove this block and add a policy that grants read access.
- Disable Block Public Access on your bucket by running this command:
aws s3api delete-public-access-block --bucket [[BUCKET_NAME="your-bucket-name"]]
What does this command do?
This removes the Block Public Access configuration from your bucket entirely. Without it, AWS allows public access policies to take effect. This is a deliberate security tradeoff for hosting a public website.
- This command produces no output on success. No error message means it worked.
Now you need a bucket policy that tells AWS exactly who can access your files and what they can do. You'll create a JSON policy file, apply it to your bucket, and then clean it up.
- Create a file called bucket-policy.json inside your my-portfolio folder. You can do this in your text editor or by running:
type nul > bucket-policy.json
- Open bucket-policy.json in your text editor and paste in the following policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::[[BUCKET_NAME="your-bucket-name"]]/*"
}
]
}
What does this policy do?
- "Principal": "*" means anyone on the internet (the general public).
- "Action": "s3:GetObject" allows only reading files. Nobody can modify or delete your content.
- "Resource" with /* at the end means every file inside the bucket.
- Save bucket-policy.json.
- Apply the policy to your bucket by running this command:
aws s3api put-bucket-policy --bucket [[BUCKET_NAME="your-bucket-name"]] --policy file://bucket-policy.json
- This command produces no output on success. No error message means the policy is now active.
- Delete the policy file since you no longer need it by running:
del bucket-policy.json
- Confirm the file is gone by running:
dir
- You should see only index.html, style.css, and error.html listed.
Seeing a MalformedPolicy error?
Make sure your bucket name in the Resource field of the JSON exactly matches your actual bucket name. Check for typos, missing quotes, or extra commas in the JSON file.
If you see an "Access Denied" error, make sure you ran the delete-public-access-block command first. The Block Public Access setting prevents policies from granting public access.
Help me fix my bucket policy error
Your S3 bucket is now fully configured for public website hosting. Next up, you'll upload your files and see your portfolio live on the internet.
Deploy Your Website Manually
Your S3 bucket is configured for public static website hosting and has a website endpoint ready to serve content. The bucket is waiting for files.
In this step, you'll upload your website files to S3 and see your portfolio go live for the first time. You'll also practice the manual deploy workflow so you understand exactly what happens before you automate it later.
In this step, get ready to:
- Upload your website files to S3 using the AWS CLI.
- Visit your live website in a browser and confirm it works.
- Make a change to your site, re-deploy, and verify the update appears.
Upload your website files to S3
The aws s3 sync command compares your local folder to an S3 bucket and uploads only the files that are new or changed. It works like a smart copy that keeps your bucket in sync with your local project.
- Make sure your terminal is inside your my-portfolio folder (the one containing your HTML and CSS files).
- Upload your website files to S3 by running this command:
aws s3 sync . s3://[[BUCKET_NAME="your-bucket-name"]] --delete --exclude ".git/*"
What does this command do?
- . means "the current folder" so it uploads everything in your project directory.
- s3://your-bucket-name is the destination bucket where your files land.
- --delete removes files from S3 that no longer exist locally. This keeps your bucket clean.
- --exclude ".git/*" skips your Git history folder. You only want your website files in S3, not internal Git data.
- You should see output listing each file being uploaded: index.html, style.css, and error.html.
Seeing an access denied error?
- Make sure your terminal is inside the my-portfolio folder. Run cd ~/Desktop/my-portfolio to navigate there.
- Check that your IAM policy includes s3:PutObject and s3:ListBucket permissions for your bucket.
- Verify your AWS CLI is configured with the correct access key by running aws configure list.
Help me fix an access denied error when running aws s3 sync.
Visit your live website
Your files are now in S3 and the bucket is configured to serve them as a website. Time to see it live.
- Open your web browser.
- Paste your S3 website endpoint URL into the address bar:
http://[[BUCKET_NAME="your-bucket-name"]].s3-website-us-east-1.amazonaws.com
You should see your portfolio page with the blue "Hi, I'm a Cloud Engineer" heading, your About Me section, your skills list, and the footer.
Why HTTP and not HTTPS?
S3 website endpoints only serve content over HTTP. To get HTTPS, you need a service like CloudFront in front of your bucket. You'll have a chance to set that up in the Secret Mission.
Seeing a 403 Forbidden or blank page?
- Double-check that Block Public Access is disabled on your bucket. All four checkboxes must be unchecked.
- Verify your bucket policy grants s3:GetObject to * (everyone) on your bucket's objects.
- Make sure you're using the website endpoint URL (contains s3-website), not the regular S3 object URL.
Help me troubleshoot my S3 website showing 403 Forbidden.
Make a change and re-deploy
A deployment pipeline is only useful if it can deliver changes. Let's prove your setup works by making a quick edit and syncing it to S3.
- Open index.html in your text editor.
- Find the <h1> tag inside the <header> section.
- Change the heading text to include your name. For example, change "Hi, I'm a Cloud Engineer" to "Hi, I'm [your name] - a Cloud Engineer".
- Save index.html.
- Re-deploy your updated file to S3 by running the same sync command:
aws s3 sync . s3://[[BUCKET_NAME="your-bucket-name"]] --delete --exclude ".git/*"
What should I see?
This time, the output only shows index.html being uploaded. The sync command is smart enough to skip style.css and error.html because they haven't changed.
- Refresh your browser tab with the S3 website URL.
- You should see your updated heading with your name.
Still seeing the old heading?
- Try a hard refresh with Ctrl+Shift+R to bypass your browser cache.
- Confirm the sync command output showed index.html being uploaded. If nothing was listed, your file may not have been saved before running the command.
My S3 website isn't showing my latest changes after running aws s3 sync.
Why deploy manually before automating?
You just ran the exact same command that your CI/CD pipeline will run automatically in a later step. Understanding the manual process first means you'll know exactly what's happening when the automation takes over.
Your portfolio is live on the internet. Next up, you'll push your code to GitHub and store your AWS credentials securely as repository secrets.
Push Your Code to GitHub
Your portfolio site is live on S3 and you can deploy updates with a single terminal command. That's already powerful, but you still have to remember to run that command every time you make a change.
In this step, you'll push your code to GitHub and store your AWS credentials as encrypted secrets. This sets the stage for the final step where GitHub automatically deploys your site on every push.
In this step, get ready to:
- Create a new public repository on GitHub.
- Store your AWS credentials as encrypted repository secrets.
- Connect your local repository to GitHub and push your code.
Create a new repository on GitHub
Your code lives locally on your computer right now. To automate deployments with GitHub Actions, you need your code hosted in a GitHub repository.
- Open your browser and go to github.com.
- Click the + icon in the top right corner of the page.
- Select New repository.
- Enter my-portfolio in the Repository name field.
- Select Public for the repository visibility.
- Leave all other options unchecked (no README, no .gitignore, no license).
- Click Create repository.
GitHub shows you a quick setup page with instructions for pushing an existing repository. Keep this page open because you'll need the URL in the next substep.
Why leave the options unchecked?
You already have a local Git repository with files in it. Adding a README or .gitignore from GitHub would create a separate commit history that conflicts with your local one.
Don't see the + icon?
Make sure you're signed in to GitHub. The + icon only appears when you're logged in. You can also go directly to github.com/new.
Help me create a new GitHub repository
Add your AWS credentials as repository secrets
Your GitHub Actions workflow will need your AWS credentials to deploy files to S3. Storing them as encrypted repository secrets keeps them safe and out of your code.
- In your new repository page on GitHub, click Settings in the top navigation bar.
- In the left sidebar, click Secrets and variables.
- Click Actions in the submenu that appears.
- Click the New repository secret button.
You'll add three secrets, one at a time. Start with your access key ID.
- Enter AWS_ACCESS_KEY_ID in the Name field.
- Paste your IAM user access key ID in the Secret field. This is the access key you generated for the github-actions-deployer IAM user.
- Click Add secret.
Now add your secret access key.
- Click New repository secret again.
- Enter AWS_SECRET_ACCESS_KEY in the Name field.
- Paste your IAM user secret access key in the Secret field.
- Click Add secret.
Finally, add your bucket name as a secret so the workflow knows where to deploy.
- Click New repository secret one more time.
- Enter AWS_S3_BUCKET in the Name field.
- Enter your bucket name (my-portfolio-bucket) in the Secret field.
- Click Add secret.
You should now see three secrets listed on the Actions secrets page: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_S3_BUCKET.
Why store credentials as secrets?
Repository secrets are encrypted by GitHub and only exposed to workflows at runtime. They never appear in logs or code. This prevents your AWS credentials from being leaked even if your repository is public.
Can't find Settings in the repository?
The Settings tab only appears if you are the repository owner. Make sure you're viewing your own repository and not someone else's.
Help me find the secrets settings in my GitHub repository
Connect your local repo to GitHub and push your code
Your GitHub repository is ready and your secrets are configured. Now you'll connect your local Git repository to GitHub and push your website files up.
- Open your terminal and make sure you're inside your my-portfolio folder by running:
cd ~/Desktop/my-portfolio
- Commit all your files by running these commands:
git add .
git commit -m "Initial commit: portfolio website"
What do these commands do?
`git add .` stages all files in your folder for the next commit. `git commit -m` saves a snapshot of those staged files with a message describing the change.
- Rename your default branch to main by running:
git branch -M main
- Connect your local repository to GitHub by running:
git remote add origin https://github.com/[[GITHUB_USERNAME="your-github-username"]]/my-portfolio.git
- Push your code to GitHub by running:
git push -u origin main
What does this command do?
`git push -u origin main` uploads your local commits to the `main` branch on GitHub. The `-u` flag sets `origin main` as the default, so future pushes only need `git push`.
Git may prompt you to authenticate with GitHub. If a browser window opens asking you to authorize Git, click Authorize to allow the connection.
- Go back to your repository page on GitHub and refresh the page.
- You should see your three files listed: index.html, style.css, and error.html.
Push failed or authentication error?
If you see a "remote: Permission denied" or "Authentication failed" error, Git cannot verify your identity with GitHub. Make sure you are signed into GitHub in your browser and try the push command again.
If you see "error: remote origin already exists", your local repo already has a remote configured. Run git remote remove origin first, then re-run the git remote add origin command.
Help me push my code to GitHub
Your code is now on GitHub and your AWS credentials are securely stored as secrets. Next up, you'll build the GitHub Actions workflow that ties everything together and deploys your site automatically on every push.
Build Your GitHub Actions CI/CD Pipeline
Your code is on GitHub and your secrets are configured. Right now, deploying a change still requires you to manually run aws s3 sync every single time. That gets old fast.
In this step, you'll create a GitHub Actions workflow that automatically deploys your website every time you push code to the main branch. Push your code, and your live site updates within seconds.
In this step, get ready to:
- Create a GitHub Actions workflow file that triggers on pushes to main.
- Push the workflow to GitHub and watch it deploy your site automatically.
- Test the full pipeline by making a code change and confirming the site updates.
Create the workflow directory and file
GitHub Actions looks for workflow files in a specific directory: .github/workflows/. Any YAML file placed there becomes a workflow that GitHub can run.
- In your terminal, make sure you're inside your my-portfolio folder.
- Create the workflow directory by running this command:
mkdir -p .github/workflows
What does this command do?
The -p flag tells mkdir to create parent directories as needed. It creates both .github/ and workflows/ inside it in one go.
- Confirm the directory was created by running:
ls .github/workflows
The command should run without errors. The folder is empty for now, which is expected.
- Create a new file called deploy.yml inside .github/workflows/. Open it in your text editor and paste the following code:
name: Deploy to S3
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to S3
run: aws s3 sync . s3://${{ secrets.AWS_S3_BUCKET }} --delete --exclude ".git/*" --exclude ".github/*"
What does this workflow do?
This file defines a CI/CD pipeline with three steps that run every time you push code to the main branch:
- Checkout code downloads your repository's files onto the GitHub runner (a temporary virtual machine).
- Configure AWS credentials reads the secrets you saved earlier and sets them as environment variables so the AWS CLI can authenticate.
- Deploy to S3 runs the same aws s3 sync command you used manually. The --delete flag removes files from S3 that no longer exist in your repo. The --exclude flags keep Git and GitHub Actions files out of your website.
Why does the workflow use secrets instead of hardcoded values?
The ${{ secrets.AWS_ACCESS_KEY_ID }} syntax pulls values from the repository secrets you configured in the previous step. This keeps credentials out of your code and out of version control.
- Save deploy.yml.
YAML indentation matters
YAML uses spaces (not tabs) for indentation, and incorrect indentation will break the file. Make sure each level is indented by exactly 2 spaces. If your editor uses tabs by default, check its settings.
Help me fix a YAML indentation error in my GitHub Actions workflow.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here's the complete deploy.yml file. Make sure yours matches exactly:
name: Deploy to S3
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to S3
run: aws s3 sync . s3://${{ secrets.AWS_S3_BUCKET }} --delete --exclude ".git/*" --exclude ".github/*"
Push the workflow and watch it run
Your workflow file only activates once it exists on the main branch in GitHub. Pushing the file triggers the workflow for the first time.
- Stage, commit, and push the new workflow file by running these commands:
git add .github/workflows/deploy.yml
git commit -m "Add CI/CD deploy workflow"
git push origin main
What does this do?
These three commands stage the new file, save it as a commit with a descriptive message, and push it to GitHub. Because you pushed to main, the workflow triggers immediately.
- Open your browser and go to your GitHub repository page.
- Click the Actions tab at the top of the repository.
You should see a workflow run in progress (or recently completed) called Add CI/CD deploy workflow. A green checkmark means it succeeded.
- Click on the workflow run name to see the details.
- Click the deploy job to expand its steps.
You can see each step (Checkout, Configure AWS credentials, Deploy to S3) with its output. The Deploy to S3 step shows the aws s3 sync output listing files uploaded.
Seeing a red X instead of a green checkmark?
Click the failed run, then click the deploy job to see which step failed. Common causes:
- "Configure AWS credentials" failed: Check that your repository secrets are named exactly AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_S3_BUCKET (case-sensitive).
- "Deploy to S3" failed with AccessDenied: Your IAM policy may not include all required permissions. Verify the policy includes s3:PutObject, s3:DeleteObject, and s3:ListBucket.
- YAML syntax error: Check that your deploy.yml uses 2-space indentation with no tabs.
Help me debug my failing GitHub Actions workflow.
Test the full pipeline with a code change
The real test of a CI/CD pipeline is making a change and seeing it go live automatically. Let's update your website and push the change to confirm the full cycle works.
- Open index.html in your text editor.
- Find this line:
<p>Welcome to my portfolio. I build things in the cloud.</p>
- Change the text to something different. For example:
<p>Welcome to my portfolio. I build and deploy things in the cloud.</p>
- Save index.html.
- Push the change to GitHub by running these commands:
git add index.html
git commit -m "Update portfolio tagline"
git push origin main
- Go to the Actions tab in your GitHub repository.
- Wait for the new workflow run to show a green checkmark.
- Visit your website URL: http://my-portfolio-bucket.s3-website-us-east-1.amazonaws.com.
Your updated text should appear on the live site. You did not run any manual deploy command. The pipeline handled everything.
Still seeing the old text?
Your browser may be showing a cached version of the page. Try a hard refresh with Ctrl+Shift+R (Windows). If that doesn't work, open the URL in an incognito/private window.
Why is my S3 website not showing my latest changes?
That's your CI/CD pipeline in action. Every push to main now triggers an automatic deployment. You've gone from manually running commands to a fully automated workflow.
Secret mission
Add CloudFront for HTTPS and Global Caching
Your site is live on S3, but it only serves over HTTP. In this secret mission, you'll add AWS CloudFront as a CDN in front of your bucket. This gives you HTTPS, global edge caching, and faster load times for visitors anywhere in the world.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your S3 bucket stores a few KB of files, so ongoing costs are fractions of a penny per month with no traffic.
Resources you used:
- An S3 bucket configured for static website hosting.
- An IAM user with an inline policy scoped to your S3 bucket.
- AWS access keys stored locally via aws configure.
- A GitHub repository with three secrets (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_S3_BUCKET).
- A local project folder with HTML, CSS, and workflow files.
Keep everything running
No action needed. Choose this if you want your portfolio site to stay live and accessible.
- Your site will remain accessible at your S3 website endpoint.
- Costs will stay below $0.01 per month with minimal or no traffic.
- Your GitHub Actions pipeline will continue deploying on every push to main.
Pause - I'll come back to this later
S3 static hosting does not have a pause mode like compute services do. The closest option is to remove the public bucket policy so your site is no longer accessible to the public.
- Open the AWS Console and navigate to your S3 bucket.
- Go to the Permissions tab and delete the bucket policy.
- Re-enable Block Public Access on the bucket.
- Your files remain in the bucket, but the site is no longer publicly reachable.
- To resume later, disable Block Public Access and re-add the bucket policy.
Delete - I don't want to use this again
Remove all project resources and start fresh. This cannot be undone.
- Delete the S3 bucket and all its contents by running:
aws s3 rb s3://[[BUCKET_NAME="your-bucket-name"]] --force
- In the AWS Console, navigate to IAM and delete the IAM user you created for this project.
- Delete the local AWS credentials by removing the .aws folder in your user profile directory, or run aws configure and clear the values.
- On GitHub, go to your repository's Settings > Secrets and variables > Actions and delete all three secrets.
- Optionally, delete the GitHub repository itself if you no longer need it.
- Delete the local project folder from your Desktop.
Nice Work!
Nice Work!
You just built your first cloud deployment pipeline from scratch. Every time you push code to GitHub, your website automatically updates on AWS within seconds.
Here's what you learned:
- Hosted a static website on AWS S3 with public access configuration and IAM least-privilege permissions scoped to a single bucket.
- Deployed files to the cloud using the AWS CLI to understand what happens under the hood before automating it.
- Built a GitHub Actions CI/CD pipeline that automatically deploys your site on every push to main.
- Secret Mission: Added CloudFront for HTTPS and global CDN delivery in front of your S3 bucket.
Ready to quiz yourself?