Build S3 Static Website and CloudFront Using Terraform and Gitlab
In this article, I’m guiding you through how we could set up an S3 static website and CloudFront using Terraform and Gitlab.
We focus on how to implement IaC (Terraform) and use Gitlab CI as our Continuous Integration tool.
What is Infrastructure as Code (IaC)
Infrastructure as Code (IaC) manages and provides an infrastructure through code instead of through manual processes.
With IaC, configuration files containing your infrastructure specifications are created, making it easier to edit and distribute configurations. Terraform is one of the Infrastructure as Code (IaC) tools developed by HashiCorp.
Terraform is one way to build and manage the infrastructure in the cloud platform such as Azure, AWS, GCP, etc.
If it’s your first time hearing about IaC or Infrastructure as Code, we can refer to this link to get you comfortable.
Terraform file structure
All the Terraform files locate within the terraform folder.
Please start with the first file, backend.tf
, which defines where Terraform stores its state data files.
In our case, we will use Gitlab as our Terraform state storage.
The contents of the backend.tf
terraform {
backend "http" {
}
}
variables.tf
and version.tf
are where input variables and Terraform provider constraint are defined.
The contents of the variables.tf
variable "region" {
type = string
default = "ap-southeast-1"
}
variable "s3_name" {
type = string
}
The contents of the version.tf
terraform {
required_providers {
aws = {
version = "~> 4.34.0"
}
}
}
Note: The region is set to ap-southeast-1 (Singapore) as it’s my closest region which varies from your location. You can find a list of AWS regions here. : https://howto.lintel.in/list-of-aws-regions-and-availability-zones
s3.tf
is where S3 and its ACL and bucket policy are defined.
The contents of the s3.tf
resource "aws_s3_bucket" "this" {
bucket = var.s3_name
}
resource "aws_s3_bucket_acl" "this" {
bucket = aws_s3_bucket.this.id
acl = "private"
}
resource "aws_s3_bucket_website_configuration" "this" {
bucket = aws_s3_bucket.this.bucket
index_document {
suffix = "index.html"
}
error_document {
key = "index.html"
}
}
resource "aws_s3_bucket_policy" "this" {
bucket = aws_s3_bucket.this.id
policy = jsonencode({
Version = "2012-10-17"
Id = "AllowGetObjects"
Statement = [
{
Sid = "AllowPublic"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.this.arn}/**"
}
]
})
}
cloudfront.tf
is where AWS CloudFront and its default caching behaviour are defined. The important thing is that it uses S3, defined earlier as its default origin.
The contents of the cloudfront.tf
locals {
s3_origin_id = "${var.s3_name}-origin"
s3_domain_name = "${var.s3_name}.s3-website-${var.region}.amazonaws.com"
}
resource "aws_cloudfront_distribution" "this" {
enabled = true
origin {
origin_id = local.s3_origin_id
domain_name = local.s3_domain_name
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1"]
}
}
default_cache_behavior {
target_origin_id = local.s3_origin_id
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
forwarded_values {
query_string = true
cookies {
forward = "all"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 0
max_ttl = 0
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
price_class = "PriceClass_200"
}
Gitlab CI/CD
Now we got Terraform files in place. Next, to provision the resources, Terraform will need CI/CD tools to do this particular job.
Gitlab CI is a robust CI/CD system with many built-in templates that we use to integrate with Terraform. The important life cycle of IaC (Infrastructure as Code) are:
- Initialize (Validate)
- Plan (Build)
- Apply (Deploy)
- Destroy
Gitlab has come with built-in templates for such IaC life cycle events. And the best fit template is Terraform/Base.gitlab-ci.yml
that we’re going to use.
A regular Gitlab YAML file would come with the list of templates, stages, and some user-defined variables
include:
- template: Terraform/Base.gitlab-ci.yml
stages:
- validate
- build
- deploy
- cleanup
variables:
TF_STATE_NAME: dev-tf-state
TF_CACHE_KEY: dev-tf-cache
TF_ROOT: terraform # the terraform files are in this subdirectory. set TF_ROOT accordingly if yours are different
The rest of the Gitlab jobs are:
validate job
validate: # validate stage
extends: .terraform:validate
build job
build:
extends: .terraform:build
script:
- cd "${TF_ROOT}"
- gitlab-terraform plan
-var "s3_name=${S3_NAME}"
- gitlab-terraform plan-json
deploy job
deploy:
extends: .terraform:deploy
environment:
name: $TF_STATE_NAME
on_stop: destroy
dependencies:
- build
rules:
- when: always
cleanup job
destroy:
extends: .terraform:destroy
environment:
name: $TF_STATE_NAME
action: stop
script:
- cd "${TF_ROOT}"
- gitlab-terraform destroy
-var "s3_name=${S3_NAME}"
Gitlab CI/CD variables
For CI to authenticate and authorize the provisioning of cloud resources, we need AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
Detail on how to get your AWS access key ID and secret access key https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html
Hence CI variables needed for this particular Terraform are: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and S3_NAME (since Amazon bucket names must be unique across all AWS accounts in all the AWS Regions within a partition)
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- S3_NAME
Input all required variables in Gitlab
Gitlab Pipeline UI
The most exciting part is to see what is accomplished in the Gitlab Pipeline portal itself.
If everything works properly, we see the stages of the pipeline run successfully.
AWS Console
Amazon S3
The Amazon S3 Console will have a newly provisioned bucket listed here.
Create a hello-world index page
Let’s create a simple index.html page that will show a greeting message.
index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
<style>
h1 {
text-align: center;
}
p {
text-align: center;
}
</style>
</head>
<body>
<h1>Hello, greeting from the Cloud...</h1>
<p>This is your very first static page</p>
</body>
</html>
Create the index.html with the above HTML code, then upload it to the Amazon S3 bucket just created. Go into the bucket and drag the index.html file into the console.
Then click the Upload button.
Amazon CloudFront
The Amazon S3 Console will have an AWS CloudFront just created.
S3 Static Website
Congratulations. You made it. :) By visiting CloudFront URL, we can view our static website made by the sample index.html.
You can find the complete source code on the public Gitlab Repo.
Peace!