Deploy a Static Website with Terraform
Use Terraform to provision an S3 bucket and deploy a live portfolio website.
Introduction
30 Second Summary
Every time you want to put a website on the internet, someone has to set up the server, configure the storage, and manage permissions. Clicking through cloud dashboards works once, but it does not scale when you need to recreate or update that infrastructure reliably.
In this project, you will use Terraform to deploy a personal portfolio website to AWS S3 entirely through code. You will write configuration files that create your cloud resources, upload your website files, and output a live URL.
What You'll Build
A static portfolio landing page hosted on S3, provisioned and managed through a Terraform project that handles the full lifecycle of your cloud infrastructure.
By the end of this project, you'll have:
- A working Terraform project with provider configuration, resource definitions, and outputs.
- An S3 bucket configured for static website hosting with public read access.
- A live portfolio website accessible from anywhere on the internet, deployed and updated through code.
- Secret Mission: Add a custom error page and enable S3 bucket versioning to track changes to your website files.
Are there any prerequisites?
You will need an AWS account (free tier eligible). No prior experience with Terraform or AWS is required. This project walks you through every installation and configuration step from scratch.
Install Terraform and the AWS CLI
To deploy infrastructure with code, you need two tools on your machine. Terraform is the tool that reads your configuration files and creates real cloud resources from them.
The AWS CLI is a command-line tool that authenticates you with AWS so Terraform can create resources in your account. In this step, you will install both tools and connect the AWS CLI to your AWS account.
In this step, get ready to:
- Install Terraform on Windows.
- Install the AWS CLI v2 on Windows.
- Configure the AWS CLI with your credentials so Terraform can access your account.
Download and install Terraform
Terraform is distributed as a single executable file. You will download it, place it in a folder on your system PATH, and verify the installation.
- Press the Windows key to open your search bar.
- Type Command Prompt and press Enter to open it.
- Check if Terraform is already installed by running this command:
terraform -version
✔️ I see version 1.2 or higher
Terraform is ready to go. You can skip ahead to the next substep where you install the AWS CLI.
ⓧ I see an older version
You need version 1.2 or higher for this project. Download the latest binary and replace your existing one.
- Open your browser and go to the Terraform install page.
- Under Windows, click the AMD64 download link.
- Extract the zip file. Copy the new terraform.exe into C:\terraform, replacing the old file.
- Close your Command Prompt and open a new one.
- Run terraform -version again to confirm you see 1.2 or higher.
ⓧ Command not found
Terraform is not installed yet. Follow these steps to set it up.
- Open your browser and go to the Terraform install page.
- Under Windows, click the AMD64 download link to download the zip file.
- Extract the zip file. Inside you will find a single file called terraform.exe.
- Create a new folder at C:\terraform.
- Move terraform.exe into C:\terraform.
Now you need to add C:\terraform to your system PATH so Windows can find the command.
- Press the Windows key and type Environment Variables.
- Click Edit the system environment variables.
- Click the Environment Variables button at the bottom.
- Under System variables, find the variable named Path and click Edit.
- Click New and type C:\terraform.
- Click OK on all open dialogs to save.
- Close your Command Prompt and open a new one.
- Run terraform -version to verify. You should see a version number like 1.15.5.
Why do I need to add to PATH?
The PATH is a list of folders Windows checks when you type a command. Adding C:\terraform to PATH lets you run terraform from any folder without typing the full file path.
Still seeing 'not recognized' after adding to PATH?
Make sure you opened a brand new Command Prompt window after saving the PATH change. Existing windows do not pick up PATH updates.
Double-check that terraform.exe is directly inside C:\terraform and not in a subfolder.
help me troubleshoot why terraform -version is not working on Windows
Download and install the AWS CLI
The AWS CLI lets you interact with AWS services from the command line. It also stores credentials that Terraform uses automatically when creating resources in your account.
- In your Command Prompt, check if the AWS CLI is already installed by running this command:
aws --version
✔️ I see version 2
The AWS CLI v2 is installed and ready. You can skip ahead to the configuration substep.
ⓧ I see version 1
You have an older version of the AWS CLI. This project requires version 2. Install v2 using the MSI installer and it will replace v1.
- Download the official AWS CLI v2 MSI installer from https://awscli.amazonaws.com/AWSCLIV2.msi.
- Run the downloaded AWSCLIV2.msi installer and follow the prompts.
- Close your Command Prompt and open a new one.
- Run aws --version again. You should now see output starting with aws-cli/2.
ⓧ Command not found
The AWS CLI is not installed yet. Follow these steps.
- Download the official AWS CLI v2 MSI installer from https://awscli.amazonaws.com/AWSCLIV2.msi.
- Run the downloaded AWSCLIV2.msi installer and follow the on-screen prompts.
- Close your Command Prompt and open a new one.
- Run aws --version to verify. You should see output starting with aws-cli/2.
AWS CLI command not found after installing?
Close ALL open Command Prompt windows and open a fresh one. The MSI installer updates your PATH, but existing windows do not pick up the change.
help me fix aws --version not working on Windows after installing the MSI
Configure the AWS CLI with your credentials
Terraform needs permission to create resources in your AWS account. The AWS CLI stores your credentials in a local file that Terraform reads automatically.
- In your Command Prompt, start the configuration wizard by running this command:
aws configure
What will this command ask me?
The aws configure command prompts you for four values, one at a time. Type each value and press Enter to move to the next prompt.
- AWS Access Key ID - paste the Access Key ID from your AWS account.
- AWS Secret Access Key - paste the Secret Access Key from your AWS account.
- Default region name - type us-east-1.
- Default output format - type json.
- Enter your AWS Access Key ID when prompted and press Enter.
- Enter your AWS Secret Access Key when prompted and press Enter.
- Type us-east-1 for the default region and press Enter.
- Type json for the output format and press Enter.
Where are my credentials stored?
The AWS CLI saves your credentials at %UserProfile%\.aws\credentials and your region/format settings at %UserProfile%\.aws\config.
Terraform automatically reads from these files when it needs to authenticate with AWS. You do not need to configure Terraform separately.
Keep your credentials safe
Never share your Access Key ID or Secret Access Key with anyone. Do not commit these values to version control or paste them into public code.
Not sure where to find your AWS Access Key?
Sign in to the AWS Management Console. Click your account name in the top right, then click Security credentials. Under Access keys, create a new key. Copy both the Access Key ID and Secret Access Key immediately. You will not be able to see the Secret Access Key again.
help me find or create AWS access keys for the CLI
Both tools are installed and connected to your AWS account. Next up, you will write your first Terraform configuration file that tells AWS exactly what infrastructure to create.
Write Your First Terraform Configuration
Your tools are installed and your AWS credentials are configured. Now it is time to write actual infrastructure code.
Terraform uses a language called HCL (HashiCorp Configuration Language) to describe the cloud resources you want to create. You write .tf files that declare what infrastructure should exist, and Terraform figures out how to build it. In this step, you will write a single main.tf file that defines everything needed to host a static website on S3.
In this step, get ready to:
- Create your Terraform project folder and open it in VS Code.
- Write a provider configuration that connects Terraform to AWS.
- Define an S3 bucket with static website hosting, public access, and a bucket policy.
Create your project folder and configure the AWS provider
Every Terraform project lives in its own folder. Terraform reads all .tf files in a directory and combines them into a single configuration.
- Open Command Prompt by pressing the Windows key, typing cmd, and pressing Enter.
- Create your project folder and move into it by running these commands:
mkdir C:\terraform-s3-website
cd C:\terraform-s3-website
- Confirm the folder was created by running:
dir
You should see an empty directory listing confirming you are inside C:\terraform-s3-website.
- Open VS Code by pressing the Windows key, typing Visual Studio Code, and pressing Enter.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to C:\terraform-s3-website, select it, then click Select Folder.
- Create a new file by clicking File then New File.
- Save the file immediately with Ctrl+S and name it main.tf.
Your main.tf file is where all of your Terraform configuration will live.
- Add the following code to main.tf to configure the Terraform version and AWS provider:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
required_version = ">= 1.2"
}
provider "aws" {
region = "us-east-1"
}
What does this code do?
The terraform block declares which providers your project needs. Here you are telling Terraform to download the AWS provider from hashicorp/aws and accept any version in the 6.x range.
The provider "aws" block tells the AWS provider which region to create resources in. Terraform will automatically pick up the credentials you configured with aws configure in Step 1.
What is a Terraform provider?
A provider is a plugin that lets Terraform talk to a specific cloud platform or service. The AWS provider translates your HCL configuration into AWS API calls. Without it, Terraform would not know how to create S3 buckets or any other AWS resource.
- Save main.tf with Ctrl+S.
You won't see a visible change yet. The next chunk adds the S3 bucket resource that uses this provider.
Seeing red squiggles in VS Code?
VS Code may show syntax warnings because it does not recognize .tf files by default. This does not affect Terraform. You can optionally install the HashiCorp Terraform extension from the VS Code Extensions marketplace to get syntax highlighting.
If you see an error about mismatched braces, double-check that every opening { has a matching closing }.
Why is my Terraform file showing syntax errors in VS Code?
Define the S3 bucket and website configuration
With the provider set up, you can now define your first AWS resource. An S3 bucket will store your website files and serve them to visitors.
- Add the following code below the provider "aws" block in main.tf to create an S3 bucket:
resource "aws_s3_bucket" "website" {
bucket = "my-terraform-portfolio-site"
tags = {
Name = "Portfolio Website"
Environment = "Learning"
ManagedBy = "Terraform"
}
}
What does this code do?
The resource keyword tells Terraform to create a real piece of infrastructure. "aws_s3_bucket" is the resource type (an S3 bucket) and "website" is the local name you use to reference this bucket elsewhere in your config.
The bucket argument sets the globally unique name for this bucket. The tags block adds metadata labels so you can identify this resource in the AWS console.
Why does my bucket need a unique name?
S3 bucket names must be globally unique across all AWS accounts worldwide. If my-terraform-portfolio-site is already taken, you will get an error when you deploy in a later step. Replace this name with something unique to you, such as yourname-terraform-portfolio-site.
- Save main.tf with Ctrl+S.
Now add the website configuration that tells S3 which file to serve as your homepage.
- Add this code below the aws_s3_bucket resource in main.tf:
resource "aws_s3_bucket_website_configuration" "website" {
bucket = aws_s3_bucket.website.id
index_document {
suffix = "index.html"
}
error_document {
key = "error.html"
}
}
What does this code do?
This resource turns your S3 bucket into a web server. The index_document block sets index.html as the homepage. The error_document block tells S3 to show error.html when a visitor navigates to a page that does not exist.
Notice bucket = aws_s3_bucket.website.id. This references the bucket you just defined above. Terraform uses this reference to understand that the website configuration depends on the bucket being created first.
- Save main.tf with Ctrl+S.
You now have a bucket and its website configuration defined. The next chunk will make the bucket publicly accessible.
Getting confused by the two resources?
In older versions of the AWS provider, bucket settings were all inside one resource block. The current provider splits them into separate resources for clarity. Each aws_s3_bucket_* resource configures one aspect of your bucket.
Why does Terraform use separate resources for bucket configuration?
Configure public access and the bucket policy
By default, S3 buckets block all public access. For a website, you need visitors to be able to read your files. This requires two changes: disabling the public access block and adding a policy that grants read permission.
- Add this code below the aws_s3_bucket_website_configuration resource in main.tf to disable the public access block:
resource "aws_s3_bucket_public_access_block" "website" {
bucket = aws_s3_bucket.website.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
What does this code do?
AWS S3 has four separate safety switches that prevent public access. Setting all four to false disables these guards so that you can attach a public bucket policy in the next step.
For a personal portfolio site, public read access is exactly what you want. Anyone on the internet can view your HTML and CSS files, which is how web hosting works.
- Save main.tf with Ctrl+S.
Now add the bucket policy that grants read access to everyone.
- Add this code below the aws_s3_bucket_public_access_block resource in main.tf:
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.website.arn}/*"
}
]
})
depends_on = [aws_s3_bucket_public_access_block.website]
}
What does this code do?
This is an IAM policy written in JSON (using Terraform's jsonencode function). It grants the s3:GetObject permission to all principals ("*" means everyone) for every object in your bucket.
The depends_on line is important. It tells Terraform to disable the public access block before trying to apply this policy. Without it, AWS would reject the policy because the safety guards are still active.
- Save main.tf with Ctrl+S.
Your main.tf now contains all the infrastructure configuration needed to host a public website on S3.
Seeing mismatched braces or parentheses?
The bucket policy resource has nested brackets: curly braces for HCL blocks, parentheses for the jsonencode() function, and curly braces again for the JSON object inside. Count each opening character and make sure it has a matching close.
A quick check: the resource block opens with { and closes with } at the very end. Inside, jsonencode( opens with ( and closes with ) just before the last }.
Help me fix a brace mismatch in my Terraform bucket policy
Why raw resources instead of a Terraform module?
The Terraform community offers pre-built modules (like terraform-aws-modules/s3-bucket) that bundle multiple resources into a single block. These are great for production, but they hide how Terraform actually works.
By writing each resource individually, you learn what Terraform does under the hood. You see how resources reference each other, how dependencies form, and how policies work. This understanding makes you effective with any Terraform project.
✔️ Awesome, I've got everything!
Great. Double check you have saved main.tf with Ctrl+S.
ⓧ I'd like to double check the full code
Here is the complete main.tf file you should have at this point:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
required_version = ">= 1.2"
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "website" {
bucket = "my-terraform-portfolio-site"
tags = {
Name = "Portfolio Website"
Environment = "Learning"
ManagedBy = "Terraform"
}
}
resource "aws_s3_bucket_website_configuration" "website" {
bucket = aws_s3_bucket.website.id
index_document {
suffix = "index.html"
}
error_document {
key = "error.html"
}
}
resource "aws_s3_bucket_public_access_block" "website" {
bucket = aws_s3_bucket.website.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.website.arn}/*"
}
]
})
depends_on = [aws_s3_bucket_public_access_block.website]
}
Your infrastructure blueprint is complete. Next up, you will create the HTML and CSS files for your portfolio page and tell Terraform to upload them to the bucket.
Create Your Website Files and Upload with Terraform
Your S3 bucket is configured with website hosting and public access permissions. Now it needs actual content to serve.
In this step, you'll create the HTML and CSS files for your portfolio landing page. Then you'll tell Terraform how to upload those files to your S3 bucket automatically.
In this step, get ready to:
- Create a portfolio landing page with HTML and CSS.
- Define Terraform resources to upload your files to S3.
- Add an output block to display your live website URL.
Create your website files
Every website needs an HTML file for structure and a CSS file for styling. You'll create both in your project folder so Terraform can find and upload them.
- In VS Code, right-click inside the Explorer sidebar (left panel).
- Click New File.
- Name it index.html and press Enter.
- Paste the following HTML 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>Hello, I'm a Cloud Engineer</h1>
<p>Building infrastructure with code</p>
</header>
<main>
<section>
<h2>About Me</h2>
<p>I'm learning to deploy and manage cloud infrastructure using Terraform and AWS. This website was deployed entirely through Infrastructure as Code.</p>
</section>
<section>
<h2>Skills</h2>
<ul>
<li>AWS Cloud Services</li>
<li>Terraform (Infrastructure as Code)</li>
<li>S3 Static Website Hosting</li>
</ul>
</section>
</main>
<footer>
<p>Deployed with Terraform on AWS S3</p>
</footer>
</body>
</html>
What does this HTML do?
- The <header> section displays your name and a short tagline at the top of the page.
- The About Me section introduces who you are and what this site demonstrates.
- The Skills section lists the technologies you're using in this project.
- The <link> tag in the <head> connects your HTML to the CSS file you'll create next.
- Save index.html by pressing Ctrl+S.
✔️ Awesome, I've got everything!
Great. You should see index.html listed in your Explorer sidebar.
ⓧ I'd like to double check the full code
<!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>Hello, I'm a Cloud Engineer</h1>
<p>Building infrastructure with code</p>
</header>
<main>
<section>
<h2>About Me</h2>
<p>I'm learning to deploy and manage cloud infrastructure using Terraform and AWS. This website was deployed entirely through Infrastructure as Code.</p>
</section>
<section>
<h2>Skills</h2>
<ul>
<li>AWS Cloud Services</li>
<li>Terraform (Infrastructure as Code)</li>
<li>S3 Static Website Hosting</li>
</ul>
</section>
</main>
<footer>
<p>Deployed with Terraform on AWS S3</p>
</footer>
</body>
</html>
Now create the stylesheet that makes your page look polished.
- Right-click in the Explorer sidebar again and click New File.
- Name it style.css and press Enter.
- Paste the following CSS 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;
margin-bottom: 2rem;
}
header h1 {
font-size: 2.5rem;
color: #0073bb;
margin-bottom: 0.5rem;
}
header p {
font-size: 1.2rem;
color: #666;
}
section {
margin-bottom: 2rem;
}
section h2 {
font-size: 1.5rem;
color: #232f3e;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #eee;
}
ul {
list-style: none;
padding: 0;
}
ul li {
padding: 0.5rem 0;
padding-left: 1.5rem;
position: relative;
}
ul li::before {
content: "▸";
position: absolute;
left: 0;
color: #0073bb;
}
footer {
text-align: center;
padding: 2rem 0;
border-top: 1px solid #eee;
margin-top: 2rem;
color: #666;
font-size: 0.9rem;
}
What does this CSS do?
- The global reset (* selector) removes default browser margins and padding so spacing is consistent.
- The body rule centers your content at a max width of 800px with comfortable padding.
- The header gets a blue accent border and centered text. The #0073bb color matches AWS branding.
- List items use a custom arrow (▸) instead of default bullets for a cleaner look.
- Save style.css by pressing Ctrl+S.
- Confirm both index.html and style.css appear in your Explorer sidebar alongside main.tf.
Don't see both files in the Explorer?
- Make sure you created the files inside the terraform-s3-website folder, not inside a subfolder.
- Check that the filenames have no extra spaces or uppercase letters. They must be exactly index.html and style.css.
Help me find my website files in VS Code
✔️ Awesome, I've got everything!
Great. Double check you've saved both files.
ⓧ I'd like to double check the full code
* {
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;
margin-bottom: 2rem;
}
header h1 {
font-size: 2.5rem;
color: #0073bb;
margin-bottom: 0.5rem;
}
header p {
font-size: 1.2rem;
color: #666;
}
section {
margin-bottom: 2rem;
}
section h2 {
font-size: 1.5rem;
color: #232f3e;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #eee;
}
ul {
list-style: none;
padding: 0;
}
ul li {
padding: 0.5rem 0;
padding-left: 1.5rem;
position: relative;
}
ul li::before {
content: "▸";
position: absolute;
left: 0;
color: #0073bb;
}
footer {
text-align: center;
padding: 2rem 0;
border-top: 1px solid #eee;
margin-top: 2rem;
color: #666;
font-size: 0.9rem;
}
Tell Terraform to upload your files to S3
Your website files exist locally, but S3 doesn't know about them yet. You need to define aws_s3_object resources that tell Terraform to upload each file to your bucket.
- Switch back to main.tf in VS Code.
- Scroll to the bottom of the file (after the aws_s3_bucket_policy resource).
- Add the following two resource blocks to upload your HTML and CSS files:
resource "aws_s3_object" "index" {
bucket = aws_s3_bucket.website.id
key = "index.html"
source = "index.html"
content_type = "text/html"
etag = filemd5("index.html")
}
resource "aws_s3_object" "styles" {
bucket = aws_s3_bucket.website.id
key = "style.css"
source = "style.css"
content_type = "text/css"
etag = filemd5("style.css")
}
What do these resources do?
- Each aws_s3_object resource represents one file upload to your S3 bucket.
- The key argument sets the filename as it will appear in S3.
- The source argument points to the local file on your machine.
- The content_type tells browsers how to interpret the file (HTML vs CSS).
- The etag = filemd5(...) creates a fingerprint of your file. When you edit the file, the fingerprint changes, and Terraform knows to re-upload it on the next apply.
- Save main.tf.
You won't see a change yet. The next substep adds the output, then the following step deploys everything.
Seeing red squiggles in main.tf?
- Make sure the new resources are placed after the closing } of the aws_s3_bucket_policy block, not inside it.
- Check that every quote is a straight double quote ("), not a curly/smart quote.
Help me fix errors in my main.tf file
Add an output for your website URL
Terraform can print useful values after a deployment completes. An output block tells Terraform to display your live website URL so you don't have to hunt for it in the AWS console.
- At the very bottom of main.tf (after the aws_s3_object resources you just added), add this output block:
output "website_url" {
description = "The URL of the static website"
value = "http://${aws_s3_bucket_website_configuration.website.website_endpoint}"
}
What does this output do?
After terraform apply finishes, Terraform will print the value of website_url to your terminal. This is the public URL where your portfolio site will be live.
The website_endpoint attribute comes from the aws_s3_bucket_website_configuration resource you defined in the previous step. Terraform resolves it automatically after creating the bucket.
- Save main.tf.
- You now have three files in your project folder: main.tf, index.html, and style.css. Confirm all three are visible in the Explorer sidebar.
✔️ Awesome, I've got everything!
Great. Double check you've saved main.tf one final time.
ⓧ I'd like to double check the full code
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
required_version = ">= 1.2"
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "website" {
bucket = "my-terraform-portfolio-site"
tags = {
Name = "Portfolio Website"
Environment = "Learning"
ManagedBy = "Terraform"
}
}
resource "aws_s3_bucket_website_configuration" "website" {
bucket = aws_s3_bucket.website.id
index_document {
suffix = "index.html"
}
error_document {
key = "error.html"
}
}
resource "aws_s3_bucket_public_access_block" "website" {
bucket = aws_s3_bucket.website.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.website.arn}/*"
}
]
})
depends_on = [aws_s3_bucket_public_access_block.website]
}
resource "aws_s3_object" "index" {
bucket = aws_s3_bucket.website.id
key = "index.html"
source = "index.html"
content_type = "text/html"
etag = filemd5("index.html")
}
resource "aws_s3_object" "styles" {
bucket = aws_s3_bucket.website.id
key = "style.css"
source = "style.css"
content_type = "text/css"
etag = filemd5("style.css")
}
output "website_url" {
description = "The URL of the static website"
value = "http://${aws_s3_bucket_website_configuration.website.website_endpoint}"
}
Your website content is ready and Terraform knows exactly which files to upload and where to put them. Next up, you'll run the Terraform commands that actually create your infrastructure and push everything live.
Deploy Your Infrastructure
Your Terraform configuration is complete. You have defined every resource your website needs, from the S3 bucket itself to the public access policies and file uploads.
Now it is time to bring that configuration to life. Terraform's deployment workflow follows three commands: init to prepare your workspace, plan to preview changes, and apply to make them real.
In this step, get ready to:
- Initialize the Terraform workspace and download the AWS provider plugin.
- Deploy all resources to AWS with terraform apply.
- Verify your live portfolio website in the browser.
Initialize the Terraform workspace
Before Terraform can create any resources, it needs to download the provider plugins specified in your configuration. The terraform init command sets up your working directory and prepares everything for deployment.
- In VS Code, open a terminal by pressing Ctrl+` (backtick key, top-left of keyboard).
- Make sure the terminal is in your terraform-s3-website folder. If not, navigate there by running:
cd C:\terraform-s3-website
- Initialize the Terraform workspace by running this command:
terraform init
What does this command do?
terraform init reads your main.tf file, finds the required_providers block, and downloads the AWS provider plugin from the Terraform Registry.
It creates two things in your project folder: a .terraform directory containing the downloaded plugin, and a .terraform.lock.hcl file that locks the exact provider version for reproducible builds.
- You should see output ending with Terraform has been successfully initialized! in your terminal.
- Confirm the new files exist by running:
dir
You should see .terraform listed as a directory and .terraform.lock.hcl as a file alongside your existing main.tf, index.html, and style.css.
Seeing an error during init?
- If you see a "No configuration files" error, make sure your terminal is in the C:\terraform-s3-website folder (the one containing main.tf).
- If you see a network-related error, check your internet connection. Terraform needs to download the provider plugin from the registry.
Ask for help: Why is terraform init failing in my project?
Preview and deploy your infrastructure
Terraform separates planning from execution. This lets you review exactly what will change before anything is created in AWS.
- Preview what Terraform will create by running:
terraform plan
What should I see?
The plan output lists every resource Terraform will create, each prefixed with a + symbol. You should see entries for aws_s3_bucket.website, aws_s3_bucket_website_configuration.website, aws_s3_bucket_public_access_block.website, aws_s3_bucket_policy.website, aws_s3_object.index, and aws_s3_object.styles.
At the bottom, you will see a summary line showing the total number of resources to add. Nothing has been created yet.
- Deploy your infrastructure by running:
terraform apply
What happens next?
Terraform displays the full plan again and asks for confirmation. It prints a prompt that says only yes will be accepted to approve.
- Type yes and press Enter to confirm the deployment.
Terraform now provisions each resource in AWS. You will see progress messages as each resource is created.
- Wait for the deployment to finish. You should see a summary line confirming resources were added and zero destroyed.
- Look at the Outputs: section at the very end. Copy the website_url value. This is your live website URL.
Seeing an error during apply?
- If you see an "access denied" or "invalid credentials" error, your AWS CLI credentials may not be configured correctly. Run aws configure again and verify your Access Key ID and Secret Access Key.
- If you see a "BucketAlreadyExists" error, your bucket name is taken globally. Open main.tf, change the bucket value in aws_s3_bucket.website to something unique (e.g., add your name or numbers), save, and run terraform apply again.
Ask for help: Why is terraform apply failing with an error?
Verify your live website and inspect state
- Open a browser and paste the website_url value from the terminal output into the address bar.
- Press Enter to load the page.
You should see your portfolio page with the heading "Hello, I'm a Cloud Engineer", an About Me section, and a Skills list. This page is live on the internet, served from your S3 bucket.
- Return to your terminal and inspect your infrastructure state by running:
terraform show
What does terraform show do?
terraform show reads your local state file (terraform.tfstate) and displays every resource Terraform is currently managing. You can see each resource's properties, including the bucket name, ARN, website endpoint, and uploaded object keys.
This is how Terraform tracks what it has created so it can detect changes later.
Your website is live and your infrastructure is fully managed by Terraform. Next up, you will make a change to your HTML and see how Terraform detects and applies only what is different.
Modify and Redeploy
Your website is live and serving traffic from S3. That is a great first deployment.
One of Terraform's superpowers is its ability to detect exactly what changed in your configuration and update only those resources. Instead of tearing everything down and rebuilding from scratch, Terraform compares your local files against the deployed state and makes the minimum change needed. Let's see this in action by editing your website and redeploying.
In this step, get ready to:
- Edit index.html to add a new Skills section to your portfolio.
- Run terraform plan and terraform apply to push only the changed file.
- Verify the updated website in your browser.
Add a Skills section to your website
Your portfolio currently has a header and an About Me section. Adding a Skills section gives you real content to trigger a redeploy.
- In VS Code, open index.html from the Explorer sidebar.
- Find the closing </section> tag after the About Me paragraph.
- Add this new section directly below it (before the closing </main> tag):
<section>
<h2>Skills</h2>
<ul>
<li>AWS Cloud Services</li>
<li>Terraform (Infrastructure as Code)</li>
<li>S3 Static Website Hosting</li>
</ul>
</section>
What does this code do?
This adds an unordered list of your key skills to the portfolio page. Each <li> item represents one skill.
The important thing is not what you add. It is that you changed the file's content, which will change its filemd5() hash. Terraform uses that hash (via the etag argument) to decide whether the file needs re-uploading.
- Save index.html.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before moving on.
ⓧ I'd like to double check the full code
<!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>Hello, I'm a Cloud Engineer</h1>
<p>Building infrastructure with code</p>
</header>
<main>
<section>
<h2>About Me</h2>
<p>I'm learning to deploy and manage cloud infrastructure using Terraform and AWS. This website was deployed entirely through Infrastructure as Code.</p>
</section>
<section>
<h2>Skills</h2>
<ul>
<li>AWS Cloud Services</li>
<li>Terraform (Infrastructure as Code)</li>
<li>S3 Static Website Hosting</li>
</ul>
</section>
</main>
<footer>
<p>Deployed with Terraform on AWS S3</p>
</footer>
</body>
</html>
Preview and apply the change with Terraform
Now let's see how Terraform handles an update. Instead of re-uploading everything, it should detect that only index.html changed.
- Preview what Terraform will change by running this command in your terminal:
terraform plan
What should I see?
Terraform's output should show that only aws_s3_object.index will be updated in-place. The summary line will say something like:
Plan: 0 to add, 1 to change, 0 to destroy.
This confirms that Terraform detected the etag change on your HTML file and will re-upload only that one file. Every other resource (the bucket, the policy, the CSS file) stays untouched.
- Apply the change by running:
terraform apply
- When prompted, type yes and press Enter to confirm the update.
Why only one resource changed?
Terraform compares the current state of each resource against your configuration. Because you included etag = filemd5("index.html") in your aws_s3_object resource, Terraform checks the file's hash every time you run plan.
If the hash differs from what was last deployed, Terraform knows the file changed. If it matches, Terraform skips it entirely.
- Confirm the apply completes with a message like Apply complete! Resources: 0 added, 1 changed, 0 destroyed. in your terminal.
Seeing 0 to change?
If Terraform reports no changes, make sure you saved index.html after editing it. The filemd5() function reads the file from disk, so unsaved changes in your editor will not be detected.
help me figure out why Terraform isn't detecting my file change
Verify the updated website in your browser
The change is deployed. Time to confirm it is live.
- Switch back to your browser where your portfolio site is open.
- Refresh the page (press Ctrl+R on Windows or Cmd+R on macOS).
- Confirm your new Skills section appears below the About Me section, showing three list items: AWS Cloud Services, Terraform (Infrastructure as Code), and S3 Static Website Hosting.
What just happened?
You made a single edit to one local file. Terraform detected the change automatically, re-uploaded only that file to S3, and left all other infrastructure untouched. This is the core value of Infrastructure as Code: repeatable, incremental changes with zero manual clicking in the console.
Page looks the same after refreshing?
Try a hard refresh to clear your browser cache. Press Ctrl+Shift+R (Windows) or Cmd+Shift+R (macOS). S3 can sometimes serve a cached version for a few seconds.
help me figure out why my S3 website is not showing updated content after terraform apply
Secret mission
Add Error Handling and Versioning
Your website works, but what happens when someone visits a page that doesn't exist? And what if you accidentally overwrite a file with a broken version? In this secret mission, you'll add a custom 404 error page and enable S3 bucket versioning so every change to your files is tracked and recoverable.
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. This project uses the AWS Always Free tier (5 GB S3 storage, 20,000 GET requests, and 2,000 PUT requests per month), so there are no ongoing costs for a small personal site.
Resources you used:
- S3 bucket my-terraform-portfolio-site in us-east-1 with static website hosting, public access, uploaded files, and versioning enabled.
- Local project folder terraform-s3-website containing Terraform configuration files, HTML, and CSS.
Keep everything running
No action needed. Choose this if you want your portfolio site to stay live at its S3 website endpoint.
- Your site remains publicly accessible at the S3 website URL.
- S3 storage and requests stay within the Always Free tier for a small personal site, so you will not be charged.
- Your local terraform-s3-website folder stays on your machine for future edits.
- Terraform and the AWS CLI remain installed on your system for future projects.
Pause - I'll come back to this later
S3 does not have a "pause" state. Your bucket is billed only on usage, which is fully covered by the Always Free tier.
- There is nothing to shut down or stop. S3 static websites have no running processes.
- Your site will remain live and accessible at no cost within free tier limits.
- When you are ready to return, open your terraform-s3-website folder and continue editing.
Delete - I don't want to use this again
Remove all project resources and start fresh. This permanently deletes your S3 bucket, all uploaded files, and all versioned copies.
- Because versioning is enabled on your bucket, you need to add force_destroy = true to the bucket resource before destroying. Open main.tf and add the following line inside the aws_s3_bucket "website" block, below the bucket name:
resource "aws_s3_bucket" "website" {
bucket = "my-terraform-portfolio-site"
force_destroy = true
tags = {
Name = "Portfolio Website"
Environment = "Learning"
ManagedBy = "Terraform"
}
}
- Save main.tf.
- Apply the change so Terraform updates the bucket configuration by running:
terraform apply -auto-approve
- Destroy all resources by running:
terraform destroy
- Type yes when Terraform asks you to confirm. Terraform will delete all objects (including all versioned copies) and the bucket itself.
- Delete your local project folder by running:
cd ..
rmdir /s /q terraform-s3-website
- Type Y to confirm when prompted.
That's a Wrap!
That's a Wrap!
Nice work! You just deployed real cloud infrastructure using nothing but code.
You've learned how to:
- Build a complete Terraform project from scratch with provider configuration, resource definitions, and outputs.
- Deploy a live portfolio website on AWS S3 accessible from anywhere on the internet.
- Experience the full Terraform lifecycle: init, plan, apply, modify, and destroy.
- Secret Mission: Added bucket versioning and a custom error page to make your site production-ready.
Ready to quiz yourself?