Deploy a Static Website with Terraform
Use Terraform to provision AWS S3 infrastructure and deploy a live static website.
Introduction
30 Second Summary
Setting up a website usually means clicking through settings one screen at a time. What if you could write your entire setup as a simple text file and let a tool do the work for you?
In this project, you will use Terraform to deploy a static website to AWS S3. You'll write configuration files that create your cloud resources, upload your website, and produce a live URL you can visit in your browser.
What You'll Build
Using Infrastructure as Code, you'll deploy a static website to AWS S3 through Terraform configuration files written in HCL (HashiCorp Configuration Language).
By the end of this project, you'll have:
- A Terraform project with HCL configuration files (main.tf, index.html, and error.html).
- A publicly accessible S3-hosted website with a live URL.
- Experience modifying infrastructure and watching Terraform detect and apply only the changes.
- Secret Mission: Parameterize your config with Terraform variables and auto-generate unique bucket names using the random provider.
Are there any prerequisites?
This project assumes you have an AWS account and a code editor like VS Code or Cursor. You'll install Terraform and AWS CLI in Step 1.
Install Terraform and AWS CLI
Before you can deploy infrastructure with code, you need two tools on your machine. Terraform reads your configuration files and translates them into real cloud resources on AWS.
The AWS CLI connects your local terminal to your AWS account so Terraform can authenticate and create resources on your behalf. In this step, you'll install both tools and link them to your AWS account with your credentials.
In this step, get ready to:
- Install Terraform.
- Install AWS CLI.
- Configure your AWS credentials.
Install Terraform
Terraform is the tool that turns your configuration files into real cloud infrastructure. You need version 1.15.0 or higher for this project.
- Check if Terraform is already installed by running this command in your terminal:
terraform version
✔️ I see version 1.15.0 or higher
Terraform is ready to go. Skip ahead to the next substep.
ⓧ I see an older version
You have Terraform installed, but the version is too old for this project. You need to download and install version 1.15.7.
Follow the same steps in the "terraform is not recognized" tab below to download and install the new version. The new binary replaces the old one in the same folder.
ⓧ terraform is not recognized
Terraform is not installed yet. Follow these steps to install it on Windows.
- Download the Terraform 1.15.7 Windows binary from this link: Download Terraform 1.15.7 (Windows AMD64).
- Open File Explorer and create a new folder at C:\terraform.
- Open the downloaded .zip file and extract terraform.exe into the C:\terraform folder.
Now you need to add C:\terraform to your system PATH so your terminal can find the terraform command.
- Press the Windows key, type Environment Variables, and press Enter.
- Click Environment Variables in the System Properties window.
- Under System variables, select Path and click Edit.
- Click New and type C:\terraform.
- Click OK on all open dialogs to save your changes.
- Close your current terminal and open a new one. PATH changes only take effect in new terminal sessions.
- Verify the installation by running:
terraform version
You should see output showing Terraform v1.15.7.
Using macOS or Linux?
Visit the official Terraform install page for platform-specific instructions. On macOS, you can install Terraform with Homebrew.
Still seeing "terraform is not recognized"?
Make sure you opened a brand new terminal after editing your PATH. Existing terminals do not pick up PATH changes.
Double-check that terraform.exe is inside C:\terraform and that the PATH entry matches this folder exactly.
Help me troubleshoot my Terraform installation on Windows. I edited my PATH but the terraform command is still not recognized.
Install AWS CLI
The AWS CLI is a command-line tool that lets you interact with AWS services from your terminal. Terraform uses the credentials stored by the AWS CLI to authenticate with your account.
- Check if AWS CLI is already installed by running this command:
aws --version
✔️ I see aws-cli/2
AWS CLI v2 is installed and ready. Skip ahead to the next substep.
ⓧ I see aws-cli/1
You have AWS CLI version 1 installed. This project requires version 2. Follow the install steps in the "Command not found" tab to install AWS CLI v2. The v2 installer will replace your v1 installation.
ⓧ Command not found
AWS CLI is not installed yet. Follow these steps to install it on Windows.
- Download the AWS CLI v2 installer from this link: Download AWS CLI v2 MSI Installer.
- Run the downloaded AWSCLIV2.msi file. The installer opens a setup wizard.
- Accept the license agreement and click through the prompts, keeping the default settings.
- Click Install and wait for the installation to complete.
- Close your current terminal and open a new one. The MSI installer adds aws to your PATH automatically, but existing terminals will not see it.
- Verify the installation by running:
aws --version
You should see output starting with aws-cli/2 followed by a version number.
Using macOS or Linux?
Visit the official AWS CLI install guide for instructions specific to your operating system.
AWS CLI command still not found?
Make sure you opened a new terminal window after the installer finished. If it still does not work, try restarting your computer so all PATH changes take effect.
Help me troubleshoot why aws --version is not working after I installed the AWS CLI MSI installer on Windows.
Configure your AWS credentials
Terraform needs permission to create resources in your AWS account. The aws configure command stores your access credentials locally so Terraform can authenticate on your behalf.
You will need an AWS Access Key ID and Secret Access Key for this step. If you do not have them yet, expand the callout below to create them.
How do I create AWS access keys?
Access keys let your local tools authenticate with your AWS account. You create them in the IAM console.
- Sign in to the AWS Management Console and open the IAM console at https://console.aws.amazon.com/iam/.
- In the navigation pane, select Users, then click your username.
- Select the Security credentials tab.
- Under Access keys, click Create access key.
- Choose Command Line Interface (CLI) as the use case, then click Next.
- Click Create access key.
- Click Download .csv file to save your Access Key ID and Secret Access Key. Store this file in a safe location. You will not be able to view the secret key again after closing this page.
- Click Done.
- Connect your terminal to AWS by running this command:
aws configure
What does each prompt ask for?
This command asks you four questions, one at a time. Enter these values when prompted:
- AWS Access Key ID - paste the Access Key ID from the .csv file you downloaded.
- AWS Secret Access Key - paste the Secret Access Key from the same .csv file.
- Default region name - type us-east-1.
- Default output format - type json.
After you enter all four values, the command returns to your terminal prompt with no output. This is normal. Your credentials are now saved locally.
Entered the wrong value?
Run aws configure again. It overwrites your previous entries, so you can re-enter the correct values at any time.
Help me fix my AWS CLI credentials configuration.
Your tools are installed and connected to AWS. Next up, you'll write your first Terraform configuration file and define the infrastructure for a live website.
Write Your Terraform Configuration
Your tools are installed and your AWS credentials are configured. Now it is time to write the code that tells Terraform exactly what infrastructure to create.
In this step, you will write a single HCL configuration file that declares an S3 bucket, configures it for public website hosting, and uploads two HTML pages. By the end, your project folder will contain everything Terraform needs to deploy a live website.
In this step, get ready to:
- Create a Terraform project folder and write the provider configuration.
- Add resources for public access, website hosting, and a bucket policy.
- Create HTML files and configure Terraform to upload them to S3.
Create your project folder and Terraform file
Every Terraform project starts with a folder containing .tf files. Terraform reads every .tf file in the folder and combines them into one configuration.
- Open your terminal.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir terraform-website
cd terraform-website
- Confirm the folder was created by running:
ls
The folder is empty for now. You will add files to it next.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Select the terraform-website folder on your Desktop, then click Open.
- Create a new file by clicking File then New File.
- Name the file main.tf and save it inside the terraform-website folder.
The main.tf file is where all of your Terraform configuration will live.
- Set up the Terraform and provider configuration by pasting this code into main.tf:
# Declare the required providers and Terraform version
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.52"
}
}
required_version = ">= 1.15.0"
}
# Configure the AWS provider with a region
provider "aws" {
region = "us-east-1"
}
What does this code do?
The terraform block tells Terraform which provider plugins to download. Here you are requiring the hashicorp/aws provider at version ~> 6.52, which means any version in the 6.52.x series.
The provider "aws" block configures how Terraform connects to AWS. Setting region = "us-east-1" tells Terraform to create all resources in the US East (N. Virginia) region, matching the credentials you configured in Step 1.
Now you will declare your first AWS resource. The S3 bucket is the container that will hold your website files.
- Add the S3 bucket resource below the provider block in main.tf:
# Create the S3 bucket for the website
resource "aws_s3_bucket" "website" {
bucket = "[[BUCKET_NAME=\"my-terraform-website-yourname-2026\"]]"
force_destroy = true
tags = {
Name = "My Terraform Website"
Environment = "Learning"
}
}
What does this code do?
The resource block creates a real AWS resource. "aws_s3_bucket" is the resource type from the AWS provider. "website" is the local name you use to reference this bucket elsewhere in your config.
The bucket argument sets the globally unique name for your S3 bucket. S3 bucket names must be unique across all AWS accounts worldwide, so pick a name that includes your own name or initials.
Setting force_destroy = true allows Terraform to delete the bucket even if it contains files. The tags block adds metadata labels to the bucket for identification.
Why does the bucket name need to be unique?
S3 bucket names are globally unique across every AWS account in the world. If someone else already has the name you picked, Terraform will fail with an error. Include your name or initials to avoid collisions.
- Save main.tf.
- You should see main.tf appear in the VS Code Explorer sidebar on the left.
Don't see main.tf in the sidebar?
Make sure you saved the file with the exact name main.tf (not main.tf.txt). Check that you opened the terraform-website folder in VS Code, not a parent directory.
Help me find my main.tf file in VS Code.
Configure public access and website hosting
An S3 bucket is private by default. To host a public website, you need three more resources: one to allow public access, one to enable website hosting, and one to set a bucket policy that lets anyone read your files.
- Add the following three resources below the aws_s3_bucket resource in main.tf:
# Disable S3's default public access restrictions
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
}
# Enable static website hosting on the bucket
resource "aws_s3_bucket_website_configuration" "website" {
bucket = aws_s3_bucket.website.id
index_document {
suffix = "index.html"
}
error_document {
key = "error.html"
}
}
# Allow anyone to read objects in the bucket
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id
depends_on = [aws_s3_bucket_public_access_block.website]
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.website.arn}/*"
}
]
})
}
What do these three resources do?
- aws_s3_bucket_public_access_block disables the four default restrictions that prevent public access. All four must be false for a public website.
- aws_s3_bucket_website_configuration tells S3 to serve index.html as the homepage and error.html for any missing page.
- aws_s3_bucket_policy attaches a JSON policy granting s3:GetObject permission to everyone ("*"). This is what makes your files readable by any browser.
Why does the bucket policy use depends_on?
The depends_on forces Terraform to apply the public access block before the bucket policy. Without it, Terraform might try to attach the public policy while the bucket still blocks public access, causing AWS to reject it.
- Save main.tf.
Your main.tf now declares four resources. You will not see any visible changes yet because Terraform has not run. The next substep adds the HTML files and the final resources.
Create your website files and upload resources
Your Terraform config references two HTML files that do not exist yet. You need to create them so Terraform can upload them to S3.
- In VS Code, create a new file by clicking File then New File.
- Name it index.html and save it inside the terraform-website folder.
- Paste the following content 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 Terraform Website</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
text-align: center;
padding: 2rem;
}
h1 { font-size: 2.5rem; margin-bottom: 1rem; }
p { font-size: 1.2rem; opacity: 0.9; }
</style>
</head>
<body>
<div class="container">
<h1>Hello from Terraform!</h1>
<p>This website was deployed using Infrastructure as Code.</p>
<p>Powered by AWS S3 and Terraform.</p>
</div>
</body>
</html>
What is this file?
This is the homepage for your website. It displays a centered heading on a purple gradient background. When Terraform uploads it to S3, anyone visiting your website URL will see this page.
- Save index.html.
- You should see index.html appear in the Explorer sidebar next to main.tf.
- Create another new file named error.html in the same terraform-website folder.
- Paste the following content 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>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
.container {
text-align: center;
padding: 2rem;
}
h1 { font-size: 3rem; margin-bottom: 1rem; }
p { font-size: 1.2rem; opacity: 0.9; }
</style>
</head>
<body>
<div class="container">
<h1>404</h1>
<p>Oops! This page was not found.</p>
<p>This error page was also deployed by Terraform.</p>
</div>
</body>
</html>
- Save error.html.
- You should now see three files in the Explorer sidebar: main.tf, index.html, and error.html.
Now you need to tell Terraform to upload these HTML files to S3 and output the live website URL.
- Add the following S3 object resources and outputs at the bottom of main.tf:
# Upload the index page to S3
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")
}
# Upload the error page to S3
resource "aws_s3_object" "error" {
bucket = aws_s3_bucket.website.id
key = "error.html"
source = "error.html"
content_type = "text/html"
etag = filemd5("error.html")
}
# Output the website URL after deployment
output "website_url" {
description = "URL of the S3 static website"
value = "http://${aws_s3_bucket_website_configuration.website.website_endpoint}"
}
# Output the bucket name for reference
output "bucket_name" {
description = "Name of the S3 bucket"
value = aws_s3_bucket.website.id
}
What does this code do?
Each aws_s3_object resource uploads a local file to the S3 bucket. The source argument points to the local file, key sets the filename inside S3, and content_type tells browsers to render it as HTML.
The etag = filemd5("index.html") line is a change-detection trick. It hashes the file content so Terraform knows when the file has changed and needs re-uploading.
The output blocks print useful values after Terraform finishes. website_url gives you the live URL to visit in your browser.
- Save main.tf.
Seeing red squiggly lines in VS Code?
VS Code may not recognize .tf files by default. The syntax highlighting is cosmetic and does not affect Terraform. You can optionally install the HashiCorp Terraform extension from the VS Code marketplace for better HCL support.
Why is VS Code showing errors in my Terraform file?
✔️ Awesome, I've got everything!
Great. Double check that you have saved all three files: main.tf, index.html, and error.html.
ⓧ I'd like to double check the full code
Here is the complete main.tf file. Compare it against yours line by line. Your bucket name will differ from the example below.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.52"
}
}
required_version = ">= 1.15.0"
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "website" {
bucket = "my-terraform-website-demo-2026"
force_destroy = true
tags = {
Name = "My Terraform Website"
Environment = "Learning"
}
}
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_website_configuration" "website" {
bucket = aws_s3_bucket.website.id
index_document {
suffix = "index.html"
}
error_document {
key = "error.html"
}
}
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id
depends_on = [aws_s3_bucket_public_access_block.website]
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.website.arn}/*"
}
]
})
}
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" "error" {
bucket = aws_s3_bucket.website.id
key = "error.html"
source = "error.html"
content_type = "text/html"
etag = filemd5("error.html")
}
output "website_url" {
description = "URL of the S3 static website"
value = "http://${aws_s3_bucket_website_configuration.website.website_endpoint}"
}
output "bucket_name" {
description = "Name of the S3 bucket"
value = aws_s3_bucket.website.id
}
Here is the complete 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 Terraform Website</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
text-align: center;
padding: 2rem;
}
h1 { font-size: 2.5rem; margin-bottom: 1rem; }
p { font-size: 1.2rem; opacity: 0.9; }
</style>
</head>
<body>
<div class="container">
<h1>Hello from Terraform!</h1>
<p>This website was deployed using Infrastructure as Code.</p>
<p>Powered by AWS S3 and Terraform.</p>
</div>
</body>
</html>
Here is the complete 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>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
.container {
text-align: center;
padding: 2rem;
}
h1 { font-size: 3rem; margin-bottom: 1rem; }
p { font-size: 1.2rem; opacity: 0.9; }
</style>
</head>
<body>
<div class="container">
<h1>404</h1>
<p>Oops! This page was not found.</p>
<p>This error page was also deployed by Terraform.</p>
</div>
</body>
</html>
Your configuration is complete. You have three files that together describe an entire website deployment. Next up, you will let Terraform read this configuration, show you what it plans to create, and deploy it all to AWS with a single command.
Deploy Your Website with Terraform
Your Terraform configuration is complete. Three files are ready to go, and your AWS credentials are configured.
Now it is time to turn that code into real infrastructure. Terraform uses a three-command workflow to deploy resources: terraform init, terraform plan, and terraform apply. Each command gives you a checkpoint before anything touches your AWS account.
In this step, get ready to:
- Initialize Terraform and download the AWS provider plugin.
- Preview the execution plan to verify what Terraform will create.
- Deploy all six resources and visit your live website in the browser.
Initialize the Terraform project
Before Terraform can create any resources, it needs to download the provider plugins specified in your main.tf file. The terraform init command handles this one-time setup.
- Open the integrated terminal in VS Code by pressing Cmd+` (macOS) or Ctrl+` (Windows).
The terminal opens inside your terraform-website folder automatically, since that is the folder open in VS Code.
- Initialize your Terraform project by running this command:
terraform init
What does this command do?
terraform init reads the terraform block in your config, finds the hashicorp/aws provider requirement, and downloads the matching plugin.
It creates a hidden .terraform directory to store the downloaded plugin. It also creates a .terraform.lock.hcl file that pins the exact provider version so future runs stay consistent.
You should see output ending with Terraform has been successfully initialized! in your terminal.
Seeing an error during init?
- Make sure your terminal is inside the terraform-website folder. Run cd to navigate to the correct directory if needed.
- Check your internet connection. Terraform needs to download the provider plugin from the HashiCorp registry.
- If you see a provider version constraint error, make sure the version in your required_providers block matches ~> 6.52 exactly.
Help me troubleshoot my terraform init error
Preview the execution plan
Before creating any real resources, Terraform lets you preview exactly what it plans to do. This is your safety net to catch mistakes before they reach AWS.
- Preview the planned changes by running this command:
terraform plan
How do I read the plan output?
Terraform uses symbols to show what action it will take on each resource. The + symbol means a resource will be created. You will also see a summary line at the bottom: Plan: 6 to add, 0 to change, 0 to destroy. confirming all six resources are new.
Each resource in the plan lists its attributes and their planned values. Values marked (known after apply) are generated by AWS at creation time, so Terraform cannot show them until the resources actually exist.
- Scroll through the output and count the resources. You should see six resources marked with +: the S3 bucket, public access block, website configuration, bucket policy, and two S3 objects.
Deploy and visit your website
The plan looks good. Time to create real infrastructure on AWS.
- Deploy your infrastructure by running this command:
terraform apply
Terraform asks for confirmation
Terraform displays the full plan one more time, then prompts you to confirm before making any changes. Only typing yes will proceed. Any other input cancels the operation.
- Type yes and press Enter when you see the confirmation prompt.
Watch Terraform create each resource in the terminal. After it finishes, you should see Apply complete! Resources: 6 added, 0 changed, 0 destroyed. followed by two output values.
What happens during apply?
Terraform creates each resource in the correct order based on the dependencies in your configuration. For example, the S3 bucket is created first because every other resource references it.
After all resources are created, Terraform saves their current state to a local terraform.tfstate file. This state file is how Terraform tracks what it manages in the real world.
- Find the website_url value in the terminal output and copy it.
- Paste the URL into your browser's address bar and press Enter.
You should see a purple gradient page with the heading "Hello from Terraform!" and two lines of text beneath it.
Website not loading?
- Make sure you copied the full website_url value, including the http:// prefix. S3 website endpoints use HTTP, not HTTPS.
- If you see an "Access Denied" error, the bucket policy or public access block may not have applied correctly. Run terraform apply again to retry.
- If the terminal shows an error about the bucket name already existing, another AWS account is using that name. S3 bucket names are globally unique across all accounts. Update the bucket value in main.tf to something unique (for example, add your initials), then run terraform apply again.
Help me troubleshoot my Terraform deployment
What is Terraform state?
The terraform.tfstate file that Terraform just created is a JSON record of every resource it manages. Terraform compares this state file against your configuration and the real AWS resources every time you run terraform plan or terraform apply.
This is how Terraform detects changes. If someone modifies a resource in the AWS Console, Terraform notices the difference on the next plan. You will see this in action in the next step.
Your website is live on AWS, deployed entirely through code. Next up, you will modify your infrastructure and watch Terraform detect and apply only the changes.
Modify Your Infrastructure
In Step 3, you deployed a live website with a single terraform apply command. Deploying from scratch is impressive, but the real power of Terraform shows up when you need to make changes to existing infrastructure.
Instead of manually editing resources in the AWS Console, you update your code files and let Terraform figure out what changed. It only touches the resources that need updating, leaving everything else alone.
In this step, get ready to:
- Update your website content and Terraform configuration.
- Preview the exact changes Terraform will make with terraform plan.
- Apply the changes and verify your updated website is live.
Update your website content and Terraform configuration
Right now, your live website shows the heading "Hello from Terraform!" and your S3 bucket has two tags. You will make one change to each file so Terraform has something new to detect.
- In VS Code, switch back to index.html.
- Find this line inside the <div class="container"> section:
<h1>Hello from Terraform!</h1>
- Replace the text inside the <h1> tags so the line looks like this:
<h1>Hello from Terraform! I updated this with IaC!</h1>
✔️ Awesome, I've got everything!
Great. Double check you have saved index.html 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 Terraform Website</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
text-align: center;
padding: 2rem;
}
h1 { font-size: 2.5rem; margin-bottom: 1rem; }
p { font-size: 1.2rem; opacity: 0.9; }
</style>
</head>
<body>
<div class="container">
<h1>Hello from Terraform! I updated this with IaC!</h1>
<p>This website was deployed using Infrastructure as Code.</p>
<p>Powered by AWS S3 and Terraform.</p>
</div>
</body>
</html>
Now let's add a tag to the S3 bucket configuration in your Terraform code.
- Switch to main.tf.
- Find the tags block inside the aws_s3_bucket "website" resource:
tags = {
Name = "My Terraform Website"
Environment = "Learning"
}
- Add a new line ManagedBy = "Terraform" below the Environment line, so the tags block looks like this:
tags = {
Name = "My Terraform Website"
Environment = "Learning"
ManagedBy = "Terraform"
}
- Save main.tf.
✔️ Awesome, I've got everything!
Great. Make sure both index.html and main.tf are saved before moving on.
ⓧ I'd like to double check the full code
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.52"
}
}
required_version = ">= 1.15.0"
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "website" {
bucket = "my-terraform-website-demo-2026"
force_destroy = true
tags = {
Name = "My Terraform Website"
Environment = "Learning"
ManagedBy = "Terraform"
}
}
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_website_configuration" "website" {
bucket = aws_s3_bucket.website.id
index_document {
suffix = "index.html"
}
error_document {
key = "error.html"
}
}
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id
depends_on = [aws_s3_bucket_public_access_block.website]
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.website.arn}/*"
}
]
})
}
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" "error" {
bucket = aws_s3_bucket.website.id
key = "error.html"
source = "error.html"
content_type = "text/html"
etag = filemd5("error.html")
}
output "website_url" {
description = "URL of the S3 static website"
value = "http://${aws_s3_bucket_website_configuration.website.website_endpoint}"
}
output "bucket_name" {
description = "Name of the S3 bucket"
value = aws_s3_bucket.website.id
}
Why does changing the HTML file matter to Terraform?
Your aws_s3_object resource uses etag = filemd5("index.html") to track the file's contents. When you edit the HTML, the MD5 hash changes. Terraform compares the new hash against the old one stored in its state file and knows the S3 object needs updating.
Preview your changes with terraform plan
Before applying any changes, Terraform lets you preview exactly what it will do. This is one of the biggest advantages of Infrastructure as Code. You see every change before it touches real infrastructure.
- Preview the planned changes by running this command in your terminal:
terraform plan
What should I see?
Terraform compares your updated code against the state file from your last apply. It detects exactly two changes:
- The aws_s3_bucket.website resource needs an in-place update because you added the ManagedBy tag.
- The aws_s3_object.index resource needs an update because the etag hash changed when you edited index.html.
Both changes are marked with a ~ symbol, meaning an in-place update. This is different from the + symbol you saw during the initial deploy, which meant a brand-new resource.
You should see a plan summary showing 2 resources to change and 0 to add or destroy.
Seeing more than 2 changes?
If Terraform shows more than 2 changes, you may have accidentally edited another part of main.tf. Use the double-check tab above to compare your full file against the expected code.
If you see 0 changes, make sure you saved both files before running the command.
Help me debug my terraform plan output.
Apply your changes and verify the updated website
The plan looks good. Time to apply those two changes to your live infrastructure.
- Apply the changes by running:
terraform apply
- Terraform will show the same plan and ask for confirmation. Type yes and press Enter.
What does this command do?
Terraform sends only the two changes to AWS. It updates the S3 object with your new HTML content and adds the ManagedBy tag to the bucket. The other four resources (public access block, website configuration, bucket policy, and error page) are untouched.
- After the apply completes, switch to your browser and refresh the website URL from earlier.
You should see your updated heading: "Hello from Terraform! I updated this with IaC!".
- Optionally, verify the new tag in the AWS Console. Open the S3 service.
- Click on your bucket name (my-terraform-website-demo-2026).
- Select the Properties tab and scroll down to the Tags section.
You should see three tags: Name, Environment, and the new ManagedBy tag with the value Terraform.
Website still showing the old heading?
Try a hard refresh in your browser. Press Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows) to bypass the browser cache.
If that does not help, check that you saved index.html before running terraform apply. The etag hash is calculated from the file on disk, so unsaved changes are invisible to Terraform.
Help me figure out why my updated website is not showing.
Secret mission
Parameterize with Variables and Random Naming
Your Terraform config works, but the bucket name is hardcoded. What happens when someone else tries to use your code? S3 bucket names are globally unique across all AWS accounts, so a name collision means instant deployment failure. Can you refactor your config to auto-generate unique names and organize your project like a professional?
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 AWS S3 Free Tier, which includes 5 GB of storage and 20,000 GET requests per month for 12 months. Two small HTML files cost effectively nothing, even outside the free tier.
Resources you used:
- S3 bucket with static website hosting, public access settings, bucket policy, and two uploaded HTML objects.
- Local terraform-website project folder containing main.tf, variables.tf, outputs.tf, index.html, error.html, and terraform.tfstate.
Keep everything running
No action needed. Choose this if you are still actively building or want to keep exploring your live website.
- Your static website stays live at the S3 URL.
- Storage costs for two HTML files are effectively zero.
- Terraform and AWS CLI remain installed on your machine. You can use them for future projects.
Pause - I'll come back to this later
Remove the live website from AWS but keep your local project files so you can redeploy anytime.
- Open a terminal in the terraform-website folder and run the following command:
terraform destroy
- Type yes when prompted to confirm.
- Terraform removes all your AWS resources. Your local project folder stays on your machine.
- When you are ready to come back, run terraform apply from the same folder to redeploy everything.
Delete - I don't want to use this again
Remove all AWS resources and delete the local project folder to start completely fresh.
- Open a terminal in the terraform-website folder and run the following command:
terraform destroy
- Type yes when prompted to confirm.
- You should see Destroy complete! Resources: N destroyed. where N is the number of resources Terraform removed.
- Verify the bucket is gone by opening the S3 Console in AWS. Your bucket should no longer appear in the bucket list.
Now delete the local project folder. Choose the command for your operating system.
macOS
- Run the following commands to navigate to your Desktop and delete the project folder:
cd ~/Desktop
rm -rf terraform-website
- Confirm the folder is gone by running:
ls
- You should no longer see terraform-website in the output.
Windows
- Run the following commands to navigate to your Desktop and delete the project folder:
cd ~\Desktop
rmdir /s /q terraform-website
- Confirm the folder is gone by running:
dir
- You should no longer see terraform-website in the output.
That's a Wrap!
That's a Wrap!
Nice work! You just deployed a live static website to AWS S3 using nothing but Terraform configuration files.
You've learned how to:
- Written HCL configuration that declares AWS infrastructure as code, including an S3 bucket, public access settings, website configuration, bucket policy, and uploaded objects.
- Deployed a live static website using the full Terraform workflow: terraform init, terraform plan, and terraform apply.
- Modified infrastructure (content and tags) and watched Terraform detect and apply only the differences, proving how Infrastructure as Code tracks real-world resources through state.
- Secret Mission: Refactored your configuration with Terraform variables, multi-file organization (variables.tf and outputs.tf), and the random provider for automatically generating unique bucket names.
Ready to quiz yourself?