Automate hosting a simple web app on Amazon S3 using Terraform
Tags: aws, s3, terraform, devops
Published: 2023-06-13 11:42:50

Hello! I finally started writing blogs and to start with here's a little blog from the tools that I've been learning to use lately.

AUTOMATION - cool isn't it? (if you're trying to crawl into DevOps like me)

### Thumb rule

_The most basic and important rule that I learned in this journey, is that one must know how to do 'it' manually, before writing scripts to automate 'it'._

The repository to follow along with the blog:

{% embed https://github.com/abhishekpawl/tf-scorekeeper-s3 %}

So, let's divide the blog into two parts: the manual way and the automatic way.

## Manual

So, let's first walk through the process of hosting a web app on S3 manually.

1. Login into your AWS management console (obviously using your IAM user 😅).
    
2. Search for the S3 management console from the services and visit it.
    
3. Create a bucket.
    
    1. Give in your bucket name.
        
    2. Choose your AWS region, although S3 is a global service.
        
    3. Uncheck **Block *all* public access**.
        
    4. Create it.
        
4. Get into the bucket (click on it).
    
5. Upload the ***index.html*** object (file).
    
6. Under the **Properties** tab, scroll down to the bottom and enable ***Static website hosting***.
    
7. Still, the website won't be accessible through the public URL. Only the authorized IAM user (i.e. you) will be able to access it using the pre-signed URL. But, you had earlier unchecked **Block *all* public access**, right?
    
8. So, now you need to update the S3 bucket policy so that anyone can access this web app. Under the **Permissions** tab, you need to **Edit** the bucket policy.
    
9. We'll use the interactive **Policy generator**.
    
    1. Type of policy: **S3 bucket policy**
        
    2. Statement:
        
        1. Effect: **Allow**
            
        2. Principal: **\***
            
        3. AWS Service: **Amazon S3**
            
        4. Actions: **GetObject**
            
        5. Amazon Resource Name (ARN): **&lt;bucket-name&gt;/\*** (you would need this policy for all the objects in our bucket, right?)
            
        6. Click on **Add statement**.
            
    3. Click on **Generate Policy**.
        
    4. Copy the policy in JSON format to the clipboard.
        
10. Go back and paste the policy in the **Bucket policy** field.
    

Now that you know how to do it manually, let's move over to automating it. Automating the whole process using Terraform is pretty simple if you know how to use the docs and all you need to automate is the manual steps. HAHA!

## Let's automate 'it'

Firstly, for automating this whole process from our local computer, you first need to tell AWS that the script is being run by authorized personnel so that AWS allows for the management of its resources. For this purpose, you need to create access keys for your IAM user.

Now, let's move on to writing the scripts.

Firstly, you need to ask Terraform to download plugins for AWS by providing the provider configurations.

Write a file *provider.tf*.

```plaintext
# provider.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.2.0"
    }
  }
}

provider "aws" {
  region     = var.region
  access_key = var.access_key
  secret_key = var.secret_key
}
```

Here, *var* represents that the *region*, *access\_key* and *secret\_key* are defined as variables, for which you write the *variable.tf* file.

```plaintext
# variable.tf
variable "region" {
  type = string
}

variable "access_key" {
  type = string
}

variable "secret_key" {
  type = string
}
```

You have defined the variables but haven't declared them yet. To declare the variables, you write the *terraform.tfvars* file.

```plaintext
# terraform.tfvars
region = "ap-south-1"
```

Where are *access\_key* and *secret\_key*?

Well, it's never a good practice to write out your *access\_key* and *secret\_key* in public. So, you may define them as environment variables as follows, in the terminal. If we define our environment variables with the prefix *TF\_VAR\_* then Terraform automatically reads them.

`export TF_VAR_access_key=XXXXXXXXXXXXXXXXXXXX`

`export TF_VAR_secret_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`

Now that, you have configured the provider information, run `terraform init` to start with Terraform (downloading the required plugins).

Following, the manual steps, the first thing you have next is to create an S3 bucket, or rather, automate creating an S3 bucket. Write a configuration file *bucket.tf*.

```plaintext
# bucket.tf
resource "aws_s3_bucket" "tf_bucket" {
  bucket = "tf-scorekeeper-v1"
}
```

Now, you need to automate the process of unchecking **Block *all* public access** by writing the configuration file *bucket-public-access.tf* for it.

```plaintext
# bucket-public-access.tf
resource "aws_s3_bucket_public_access_block" "unblock_public_access" {
  bucket = aws_s3_bucket.tf_bucket.id

  block_public_policy     = false
  block_public_acls       = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}
```

Now, for a change, first configure the bucket at once. So write the config file *bucket-policy.tf* to configure the bucket policy, which was the last step while doing all of it manually.

```plaintext
# bucket-policy.tf
resource "aws_s3_bucket_policy" "tf_public_read_access" {
  bucket = aws_s3_bucket.tf_bucket.id
  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
	    "Principal": "*",
      "Action": [ "s3:GetObject" ],
      "Resource": [
        "${aws_s3_bucket.test-bucket.arn}",
        "${aws_s3_bucket.test-bucket.arn}/*"
      ]
    }
  ]
}
EOF
}
```

Add in the HTML file *index.html* that will serve as the web app. The file is quite long, so skipping it here, but it is provided in the GitHub repository.

Now, you need to automate the steps to upload the object i.e. the *index.html* file. For this, write the *object.tf* file.

```plaintext
# object.tf
resource "aws_s3_object" "object" {
  bucket       = aws_s3_bucket.tf_bucket.id
  key          = "index.html"
  source       = "index.html"
  content_type = "text/html"
}
```

Now, you need to automate the step of enabling the *Static website hosting* for S3. So, you write the *website.tf* config file.

```plaintext
# website.tf
resource "aws_s3_bucket_website_configuration" "tf_website" {
  bucket = aws_s3_bucket.tf_bucket.id

  index_document {
    suffix = "index.html"
  }
}
```

Now, it's time to run all the terraform commands.

1. Run `terraform fmt` to apply proper formatting to the config files.
    
2. Run `terraform plan` to check what Terraform is planning to perform on running the apply command. It should show something like
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1686652565777/b446c72f-a210-44f3-aea0-963f22605541.png align="center")
    
3. Finally, run `terraform apply --auto-approve` to apply the changes and ***AUTOMATE*** the whole process.
    
4. T.B.: In case of an AccessDenied error, run the `terraform apply --auto-approve` command once again and you're done.
    

Now, you may check your web app.

Visit the S3 management console, click on your bucket and under the **Properties** tab, scroll down to the *Static website hosting* section and visit the URL. Note, it's not the pre-signed one. You'll see your web app, like mine below (8-2 haha).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1686653122560/fd6190d9-d027-4e3f-ac4d-e52b58954039.png align="center")

Well, I'm still trying to figure out ways to tackle the silly error at the end. I have gone through the docs repeatedly, Stack Overflow, Reddit and other discussion forums but haven't found any other solution yet.

As of now, I have destroyed my resources by running the `terraform destroy --auto-approve` command, which you may also want to do. :/

Hey! Finally, **THANKS** ❤️ for going through my FIRST blog and yes, if you have a solution to the silly little error at the end, please share it with me. :D