Building my Website-as-a-Service project

The first time I found out about Terraform was in 2019 during the third rotation of my graduate scheme. At the time, I hadn’t realized how powerful infrastructure as code was but now that I’ve had a good amount of exposure to these technologies, I can’t think of anything better! Spinning up infrastructure in minutes and it all just working (after multiple config changes because nothing ever works the first time 😂) is a dream!

One of the projects that I had worked on whilst on that rotation was what we called – “website as a service.” The idea was to give teams a chance to spin up their own static websites, backed by AWS’ Simple Storage Service (S3) using a pipeline. All they had to do was enter some parameters that correspond to the details of their website, for example, domain name.

Website as a service
My “website as a service” project using some magical tech

In my new company, I recently participated in a company-wide Hackathon. The team I joined had a similar idea that they had in mind, and it may be cheating but I decided to join their team to get my hands in Terraform again after not doing so for a while. With that said, although the solution was similar, it wasn’t exactly the same (as I had to plug things together that worked with our internal tech stacks) and I actually ended up learning some new things — especially on the Jenkins/writing groovy scripts front.

So for this blog post, I wanted to share the solution that I hacked together after reading various documentations and seeing what other Engineers had done (thank you, Google!) I won’t be going over Jenkins part in detail but will have some pointers for you to get started.

I’ll be re-using code that I’ve seen other Engineers implement – a list of original posts can be found at the end of this blog post.


Setting up

There are a few things that you need to make sure that you have on hand:

  1. An AWS account with AWS CLI access
  2. Terraform installed on your laptop (Download Terraform – Terraform by HashiCorp)

Set up your project

The best part of starting a project – getting set up and organised with your fresh motivation to do the thing. Relatable? Yeah, I feel ya.

mkdir website-terraform
cd website-terraform

Let’s also make sure that our Terraform version is up-to-date.
terraform version

Initialising Terraform

Coolio. We’re ready! Let’s get writing some Terraform. Let’s create some files!

terraform.tf
dist
    - index.html
    - style.css
    - about
        - about-me.html
AWS-modules
    - s3.tf
    - s3-iam.tf
    - route53.tf
    - variables.tf

In this case, you may want to just copy and paste the Terraform code which is completely fine if you want to get this set-up quickly.

But if you’re a newbie learning Terraform for the first time, I found that typing some Terraform for the first time line-by-line even though I had the code in front of me helped me understand the syntax better. I guess this is true for learning to code in general. Newbie learning hack! 😊

Let's get coding!
Let’s get coding!

In your code editor (my personal favourite is VS Code), open up your terraform.tf file. This is where we first create our first Terraform configuration! Build Infrastructure | Terraform – HashiCorp Learn Terraform is built up of various blocks, in this case, we have providers block that states the plugin we are going to use. In this case, it’s AWS because that is the cloud provider that will host all our resources. If you’re familiar with other cloud providers, you can use those too. The required providers are required for the latest version of Terraform.

In the provider block, you’ll see a profile and region key/value pair. This refers to which region you will be deploying your resources and which AWS credentials profile to use (usually you can leave this as default, but if you have multiple AWS accounts make sure that this points to the right one.

Remember that it is IMPORTANT not to hard-code any credentials here.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 2.70.0"
    }
  }
}

provider "aws" {
    profile = "default"
  region = var.aws_region
}

module "website" {
  source = "./.deploy/terraform/static-site"
  domain_name = var.domain_name
}

The module block links to all the resources that make up our static website.

In variables.tf, this is where all our variables will live that we reference across our Terraform files! They don’t need any defaults but if you already know what value you want every single time e.g. a region you definitely want to use for all your resources then it’s a good idea to set a default here. Otherwise, you can leave it blank. In our case, it’s best to leave it blank because this will later be parameterised in our Jenkins job.

variable "aws_region" {
  type = string
    default = "eu-west-1"
}

variable "domain_name" {
  type = string
}

Whilst we are here, let’s create a simple HTML website that also has a link to an “About Me” page.

<!DOCTYPE html>
<head>Welcome to my static website!</head>
<a href="/pages/about.html" >About me</a>

<p>This site was deployed via cool Terraform magic</p>
</html>

<html>
<h1>About Me</h1>
<p>I'm a page!</p>
</html>

Setting up your S3 Bucket

Resource blocks are used to define components of your infrastructure. In the documentation, it states that “a resource might be a physical or virtual component such as an EC2 instance, or it can be a logical resource such as a Heroku application. Resource blocks have two strings before the block: the resource type and the resource name.“

In our case, we’re creating an S3 bucket, IAM policy for the S3 bucket and Route53 resource. As you look through the code for creating the different parts of infrastructure as code, try and see if you can understand what the Terraform code is relating to.

Those who have some AWS knowledge may be able to see how a line relates to something you may have seen on the AWS Management Console. This is how I like to think of it when I’m writing my Terraform code!

s3.tf

  • Creates a bucket that corresponds to the domain name you input (linking back to the variables.tf file)
  • Adds a policy to the bucket and has an ACL that is set to public-read (so that anyone with the link can access it)
  • With the website {} block, this S3 bucket now knows that, “Oh, I’m going to be used as a website! Yay!” And maps the index_document in the root folder to index.html as well as the error_document (i.e. what happens when a user goes to your-website.com/random-made-up-page)
resource "aws_s3_bucket" "my-website" {
  bucket = "${var.domain_name}"
  acl = "public-read"
  policy = data.aws_iam_policy_document.my-website_policy.json
  website {
    index_document = "index.html"
    error_document = "error.html"
  }
}

s3-iam.tf

  • This creates the policy for your S3 bucket. You can read up on AWS policies here.
data "aws_iam_policy_document" "my-website_policy" {
  statement {
    actions = [
      "s3:GetObject"
    ]
    principals {
      identifiers = ["*"]
      type = "AWS"
    }
    resources = [
      "arn:aws:s3:::${var.domain_name}*"
    ]
  }
}

Setting up Route53

route53.tf

  • What could this Terraform code be creating, I wonder? 🤔 That’s right! A Route53 resource! Again, if you’re familiar with navigating the AWS Management Console, some of these fields may look familiar. In this case, we’re creating an Alias record that corresponds to the S3 generated URL from the bucket we created.
  • Bear in mind – you do need to purchase a domain for this to work.
  • Notice how I’ve used the domain name variable here again.
resource "aws_route53_record" "www" {
  name = "${var.domain_name}"
  type = "A"
  alias {
    name =  aws_s3_bucket.my-website_bucket.website_domain
    zone_id = aws_s3_bucket.my-website_bucket.hosted_zone_id
    evaluate_target_health = false
  }
}

Running a Terraform plan / apply

Now for the fun part! Let’s start initiating the Terraform and see all the code that you just wrote in action.

Some of key Terraform commands:

terraform init
This initiates the Terraform based on the config in terraform.tf

terraform plan
This command creates a plan of the infrastructure that you’re about to deploy. It’s a good idea to always run a plan to make sure that you see exactly what resources is going to be created on AWS.

terraform apply
If you’re all happy with what you see in the plan then go ahead and run an apply. This creates all the resources on AWS.

On your AWS Console, you should see all your resources built! Magic, right? 🪄

As easy as it was to create your infrastructure, you can also delete it all at once too with…

terraform destroy

🥳🎉🍾 Powerful, right? That is Infrastructure as Code for you! 🎉🍾🥳

It’s INFRASTRUCTURE as CooOODE

I’ve set this up on Gitpod so you can see the code straight away. Check it out here! You can also open this directly on Gitpod.

Using Jenkins – some pointers

Now I have to put my hand up and be honest here… writing groovy scripts isn’t my strong point. I also don’t enjoy using Jenkins if we’re being even more real over here. 😆 But as part of this project, Jenkins was in scope – after all, it wasn’t just for me to run it all locally but for it to benefit both technical and non-technical colleagues from other squads too.

Anyone else feel like this?
Anyone else feel like this?

I won’t dive into groovy in this blog post (there’s already quite a bit of Terraform here to get your head around) but here are some pointers to think about:

  • Parameterise the domain name so that it takes in input from users
  • A simple bash script that uses the AWS S3 CLI to run an AWS S3 sync
  • Or if your index.html or application build is not committed to a repo (e.g. GitHub or BitBucket) then potentially a way to upload and unzip your application build onto S3 via a File Upload parameter.

Other things to think about

The solution above doesn’t cover using HTTPS, but it’s wise to do so. You could add Cloudfront to front your S3 website! Give it a go. 😀

Woohoo! What a wild ride, right? I hope that this inspires you to dabble in some Terraform. I think that this project was a fantastic way for me to get my head around Infrastructure as Code and other Cloud native technologies to make playing around with AWS much more streamlined and fun. ☁️

Let me know if this was useful to you, happy playing around in da Clouds! 🛠


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.