In the world of Cloud Computing, efficiently deploying and managing web resources is key. This article dives into setting up a website hosted on Amazon S3 (Simple Storage Service), served through Amazon CloudFront, using Terraform, a popular Infrastructure as Code (IaC) tool. But first, let’s break down the core concepts.

What is AWS?

Amazon Web Services (AWS) is a comprehensive and broadly adopted cloud platform that offers over 200 fully featured services from data centers globally. AWS provides a variety of services including computing power, storage options, and networking capabilities, all of which contribute to the flexibility and scalability of cloud resources.


What is an S3 Bucket?

An Amazon S3 bucket is a public cloud storage resource available in the AWS S3 service. Like file folders, buckets are used to store objects, which consist of data and descriptive metadata. S3, renowned for its scalability and data availability, is commonly used for backup and recovery, data archiving, and web hosting.


What is CloudFront?

Amazon CloudFront is a fast content delivery network (CDN) service that securely delivers data, videos, applications, and APIs to customers globally with low latency and high transfer speed. CloudFront works seamlessly with AWS services, like S3, to accelerate the delivery of static and dynamic web content.


What is Terraform?

Terraform, by HashiCorp, is an Infrastructure as Code software tool that enables you to create, change, and improve infrastructure safely and predictably. It manages cloud services through declarative configuration files, making it an essential tool for modern cloud infrastructure.



The power of Terraform: understanding core mechanisms

Terraform stands out in the realm of IaC for its unique approach to managing and provisioning resources. At its core, Terraform operates based on two fundamental principles: idempotency and state management. Understanding how Terraform works and the role of the “terraform.tfstate” file is key to appreciating its power and capabilities.


How Terraform works

Terraform uses a declarative approach, where you define the desired state of your infrastructure in configuration files. Here’s a brief overview of its operation:

  • Configuration: you write code in HCL (HashiCorp Configuration Language) to describe your desired infrastructure state.
  • Initialisation: running “terraform init” initialises Terraform, setting up the required providers.
  • Planning: “terraform plan” compares your desired state with the real-world state and outlines a plan to achieve the desired state.
  • Applying: “terraform apply” executes the plan, making the necessary API calls to create, update, or delete resources.
  • Idempotency: this process is idempotent, meaning that reapplying the same configuration won't make changes unless the desired state differs from the current state.


Terraform state management (terraform.tfstate)

The “terraform.tfstate” file is pivotal in how Terraform tracks the state of your infrastructure:

  • State file: Terraform stores the state of your managed infrastructure and configuration in this file. This includes metadata and the current state of each resource Terraform manages.
  • Purpose: the state file helps map your real-world resources to your configuration, keeps track of metadata, and improves performance for large infrastructures.
  • Importance in planning: during the planning phase, Terraform uses this state file to determine what changes need to be made to achieve the desired state.
  • Sensitive data: the state file can contain sensitive data, so it's crucial to manage it securely, typically using remote state back-ends like AWS S3 with encryption and access control.


Idempotency, agnosticism, and integration

Before diving into the deployment process, it’s essential to understand the unique attributes of Terraform that make it an indispensable tool for modern infrastructure management, especially in our context of deploying a website on AWS.


Idempotency: reliable and consistent infrastructure

One of Terraform’s key features is its idempotency, which ensures that running the same configuration multiple times results in the same state, without creating unnecessary changes or duplications. This property is crucial for maintaining consistency and reliability in infrastructure management:

  • When you apply a Terraform configuration, it calculates and executes only what is necessary to achieve the desired state.
  • This means if you re-run Terraform without changing your configuration, it won't perform any actions, as the infrastructure is already in the desired state.


Agnosticism: multi-cloud and multi-service

Terraform is cloud-agnostic, meaning it can manage resources across multiple cloud providers, as well as on-premises infrastructure:

  • It provides a unified way to interact with different cloud services, from AWS to Azure and Google Cloud.
  • This agnosticism makes Terraform incredibly versatile for multi-cloud strategies, reducing vendor lock-in and allowing for more flexible infrastructure solutions.

Powerful in change management

Terraform’s ability to manage and apply changes incrementally is particularly powerful. In the context of our website deployment:

  • When you modify the “index.html” or any other website file, Terraform handles the update efficiently.
  • It only uploads the changed files to the S3 bucket and can trigger CloudFront cache invalidation, ensuring that your website visitors always see the most recent content.

Integration with CI/CD

The true strength of Terraform in a modern DevOps environment is its compatibility with Continuous Integration and Continuous Deployment (CI/CD) pipelines:

  • Terraform can be integrated into CI/CD workflows, allowing automatic deployment and management of infrastructure changes.
  • This integration ensures that infrastructure updates are as streamlined as code changes, aligning with Agile practices.



Ensuring security and efficiency in Terraform deployments to AWS

Before diving into the steps of deploying a website with Terraform on AWS, we need to set up secure and efficient communication between Terraform and AWS. This process ensures that Terraform can manage AWS resources effectively while adhering to best security practices.


Setting up a secure AWS user for Terraform

Creating a dedicated IAM user

To begin with, it's crucial to create a dedicated AWS Identity and Access Management (IAM) user specifically for Terraform. This user, which we can name “tf_s3_cloudfront”, will have permissions tailored to the resources Terraform will manage:

  • Creating a separate IAM user for Terraform helps in isolating permissions and auditing Terraform activities separately from other users and services.
  • This practice aligns with the principle of least privilege, ensuring the user has no more permissions than necessary to perform the intended tasks.

Configuring the IAM policy

The IAM policy for “tf_s3_cloudfront” should be as restrictive as possible while still allowing Terraform to perform required actions. Based on your requirements, the policy should include permissions for S3 and CloudFront:


Code 1-1


  • The “s3:*” permission allows Terraform to manage S3 buckets prefixed with “random-jokes-“.
  • The “cloudfront:*” permission enables Terraform to manage CloudFront distributions.


Handling AWS credentials securely

After setting up the IAM user, securely managing the AWS credentials (Access Key ID and Secret Access Key) is paramount:

  • Never hardcode credentials: avoid hardcoding AWS credentials in Terraform files or any source code. Exposing credentials can lead to security vulnerabilities.
  • Use environment variables or configuration files: store AWS credentials in environment variables or use the AWS credentials file (~/.aws/credentials). Terraform automatically detects credentials set through these methods.
  • CI/CD pipeline integration: when integrating Terraform with CI/CD pipelines, use your CI/CD tool's secure storage features to handle AWS credentials.


The importance of security and least privilege

Adhering to security best practices and the principle of least privilege is crucial in cloud infrastructure management:

  • Minimize security risks: restrictive IAM policies and secure handling of credentials minimise potential security risks and unauthorised access.
  • Compliance and auditing: using dedicated IAM users and following security best practices aid in compliance with security standards and make auditing easier.


By following these steps, you ensure that Terraform interacts with AWS securely and efficiently, paving the way to the safe and effective deployment of your website.

With security foundations firmly in place, we can now proceed to the steps of deploying a website on AWS using Terraform, confident in the knowledge that our infrastructure is not only efficient, but also secure and compliant with best practices.



Deploying a website on AWS using Terraform

Now, let’s explore the steps to upload a website to AWS S3 and set it as the origin of a CloudFront distribution using Terraform.


Step 1: setting up Terraform

Begin by writing Terraform configuration files that define the required AWS resources. This involves creating “”, “”, “”, and other necessary configuration files:

  • configures the AWS provider with necessary credentials and region.
  • defines variables such as AWS region, S3 bucket name, and CloudFront settings.
  • contains the core logic for creating and configuring AWS resources like S3 buckets, CloudFront distributions, and more.
  • this file specifies the output values that Terraform will report back to the user. These outputs can be crucial for understanding the configuration's results, such as URLs, IDs, or other important data.


Step 2: creating an S3 bucket

Define an S3 bucket in Terraform to store your website files. Enable website hosting on the bucket and configure public access settings according to your needs.


Code 2-1


Step 3: configuring the website on S3

Upload your website's static files (e.g., HTML, CSS, JavaScript) to the S3 bucket using the “aws_s3_object” resource. This step involves specifying the file path and ensuring the correct content type is set.


Code 3-1


Step 4: setting up CloudFront

Create a CloudFront distribution with your S3 bucket as the origin. This involves defining a CloudFront distribution resource, configuring origin access identity (OAI), and setting cache behaviours.


Code 4-1

Code 5-1


Step 5: implementing cache invalidation

Implement cache invalidation to ensure updates to your website are reflected promptly. This can be achieved using a “null_resource” with a local-exec provisioner in Terraform.


Code 6-1


Having explored the implementation of the main configuration, let’s show the role of variables in Terraform.

Variables in Terraform act as customisable parameters, making our infrastructure configurations more adaptable and easier to modify. Defined in the “” file, these variables can be assigned and altered without changing the core logic of our Terraform scripts.


Code 7-1


By leveraging these variables, we can easily customise our AWS resources, such as specifying the region for our CloudFront distribution or setting a prefix for our S3 bucket names.



Deploying index.html with Terraform

In our demonstration here, the key focus is deploying the “index.html” file, which serves as the entry point for our static website hosted on AWS S3 and distributed via CloudFront. Understanding the structure and best practices behind this setup is crucial.


Understanding index.html and its role:
  • The heart of the website: the index.html file is essentially the homepage or the main interface of your static website. It contains the HTML structure and is typically the first file that gets loaded when someone visits your site.
  • Content and structure: this file can include references to other resources like CSS for styling, JavaScript for interactivity, and multimedia content, forming the core of the website's user interface.


Detailed breakdown of the index.html code:

The provided “index.html” is a well-structured web page designed to display “random jokes”. Let's break down its key components.


Code 8-1

Code 9-1


HTML structure

  • <!DOCTYPE html>: this declaration defines the document type and HTML version, indicating that the document is an HTML5 document.
  • <html lang="en">: the root element of the HTML document, with lang="en" specifying that the primary language is English.


Head section

  • <head>: contains meta-information about the document, links to stylesheets, and the title.
  • <meta charset="UTF-8">: specifies the character encoding for the HTML document.
  • <meta name="viewport" content="width=device-width, initial-scale=1.0">: ensures the page is responsive and renders well on all devices.
  • <title>Random Jokes</title>: sets the title of the web page, displayed in the browser's title bar or tab.


Internal CSS styles

<style>: contains CSS styles used to style the HTML content. This includes:

  • importing a font from Google Fonts.
  • styling for the body, joke container, joke text, and the button.
  • the body style sets the background color, font, and centers content.
  • the button style changes on hover to enhance user interaction.


Body section

  • <body>: defines the document's body and contains all the content of the document, such as text, hyperlinks, images, tables, lists, etc.
  • <div id="joke-container">: a container that holds the joke and the button.
  • <div id="joke">: displays the joke text.
  • <button id="next-joke-btn">: a button for loading the next joke.


JavaScript for dynamic content

  • <script>: includes JavaScript to make the page interactive.
  • An event listener is attached to the “Next Joke” button.
  • The “fetchJoke” function fetches a random joke from an API and updates the joke text in the page.
  • Error handling is included to display a message if joke fetching fails.


This “index.html” file is an excellent example of a simple yet interactive web page. It combines HTML structure, CSS styling, and JavaScript functionality to create a user-friendly experience where visitors can enjoy a new joke with each button click. The use of external APIs for dynamic content and responsive design principles makes it a modern and engaging webpage.

In our Terraform setup, this file is strategically placed in the “static/html” directory to keep our project organised and to streamline the deployment process to AWS S3 and CloudFront.


Best practices: organising the index.html file

We place the “index.html” file inside the “static/html” directory. This structure is a best practice for a few reasons:

  • Clarity and organisation: it clearly separates static content (like HTML, CSS, JavaScript files) from other parts of the infrastructure code, making the project more organised and easier to navigate.
  • Scalability: as your website grows to include more pages and resources, this organised structure becomes increasingly beneficial.
  • Security: keeping static content in a specific directory helps in implementing security practices, like access permissions, more effectively.


Terraform’s role in deploying index.html

Using Terraform, we automate the deployment of the “index.html” file to the S3 bucket. Here’s a snippet of the Terraform configuration that accomplishes this:


Code 10-1


  • Automated upload: Terraform automatically uploads the “index.html” file from the local “static/html” directory to the specified S3 bucket.
  • Dynamic content management: changes made to the “index.html” file can be easily deployed by rerunning Terraform, ensuring that the latest version of your site is always available.



Visual guide: running Terraform commands

1. Running “terraform init”: initialising Terraform to download necessary plugins and set up the environment.


Code 11-1


2. Executing “terraform plan”: outlining the changes Terraform will make to your AWS infrastructure.

Code 12-1

Code 13-1


Interpreting Terraform’s plan output

A critical step is running “terraform plan”. This command provides an overview of the actions Terraform will perform when applying your configuration. Let's dissect a typical output you might encounter.


Plan: 10 to add, 0 to change, 0 to destroy

This output can be broken down as follows:

  • 10 to add: Terraform has identified 10 resources that need to be created to match your desired state. These could be new AWS resources like S3 buckets, CloudFront distributions, IAM roles, etc.
  • 0 to change: there are no existing resources that need to be updated. If Terraform had detected discrepancies between your configuration and the current state of resources, this number would reflect the count of resources to be modified.
  • 0 to destroy: no resources are slated for removal. If you remove a resource from your configuration or make changes that require replacing existing resources, this number indicates how many resources will be destroyed and potentially recreated.


Importance of the plan output

  • Review and confirmation: the plan output allows you to review and confirm the changes before they are applied. This step is crucial for avoiding unintended modifications to your infrastructure.
  • Safety and visibility: it provides a safety mechanism, giving you visibility into how your infrastructure will change without making any immediate alterations.
  • Auditing and documentation: this output can be used for auditing purposes and as documentation for changes made to your infrastructure.


3. Applying with “terraform apply”: applying the changes to deploy the “index.html” to the S3 bucket and configure CloudFront.


Code 14-1

Code 15-1



Understanding Terraform output and testing in the browser

After running “terraform apply”, Terraform provides output variables that give important information about the resources it has created or modified. Let's dissect the output from our Terraform execution and understand how to test our website in a browser.


Terraform output explained

Code 16-1


  • cloudfront_distribution_domain: this is the domain name assigned to your CloudFront distribution. It is through this URL that users will access your website. CloudFront serves as a content delivery network to efficiently serve your website to users from global locations.
  • s3_bucket_name: the name of the S3 bucket where your website's files are stored. This bucket name is dynamically generated using a combination of a specified prefix and a random string for uniqueness.
  • s3_bucket_url: the direct URL to access the S3 bucket. While useful for direct access, typically, you'll use the CloudFront URL for public access to leverage CDN caching and distribution.


Testing the website in a browser
  • Access via CloudFront: open a web browser and navigate to This URL should load your website, serving the “index.html” file from the S3 bucket through CloudFront.
  • Access via S3 bucket URL: you can also access the website directly via the S3 bucket URL ( However, this access method does not benefit from CloudFront's distribution network.

Code 17-1

Code 18-1


Also, as you can see, we can’t access the S3 bucket URL. Intentionally, it’s blocked to the outside world and you will receive an “access denied” message. In our code, only CloudFront can reach the content into the S3 as origin.


Code 19-1



Demonstrating dynamic content updates

Let's see how updates to your website are handled:

  • Update index.html: make a change to your “index.html” file in the “static/html” directory.
  • Reapply Terraform configuration: run “terraform apply” again. Terraform will upload the updated “index.html” to your S3 bucket.
  • Cache invalidation: Terraform will also perform cache invalidation in CloudFront, ensuring that the updated content is served to users.
  • Verify the update: refresh the CloudFront distribution URL in your browser. You should see the changes reflected, demonstrating the efficiency of Terraform in managing and deploying website updates.

To illustrate this, I’m going to change the “index.html” in the text of the button “Next Joke” – I’ll add exclamations marks !!!


Code 20-1


As you may notice, we can see the change after using the “terraform apply” command in the output. Also, Terraform itself manages cache invalidation to see our change in the browser:


Code 21-1

Code 22-1



Using the code

If you would like to try this IaC project yourself, you can access the complete Terraform code on my GitHub. Simply clone the repository and follow the instructions in the file to set up your own static website on AWS S3 with CloudFront.

By using this repository as a starting point, you can quickly deploy and manage your static websites on AWS with Terraform. Don't forget to review and adapt the code to your specific requirements.

Happy coding!




As Cloud Computing continues to evolve, tools like Terraform become indispensable for developers and engineers. They not only simplify the complexities associated with cloud infrastructure, but also ensure that these infrastructures are robust, secure, and agile. This guide serves as a testament to Terraform's capabilities, providing a blueprint for efficient and secure cloud-based web hosting.

Whether you're an experienced cloud professional or new to cloud infrastructure, the insights and practices outlined in this article offer valuable knowledge for leveraging AWS and Terraform to their fullest potential. The journey from setting up an AWS user with specific IAM policies to seeing a live website highlights the transformative power of combining AWS services with Terraform's automation process.

As we look towards future advancements in cloud technology, the integration of these tools and principles will undoubtedly play a key role in shaping efficient, secure, and scalable digital landscapes.



  Read about Multipart Upload with Amazon S3 in this article.

Udostępnij ten artykuł