Hosting a website on AWS

---- views

The cloud resume challenge is a great option to start learning the cloud.

I want to learn AWS, so I thought I'd give it a try and the experience has been a blast! This is the first article of a series that will delve into my learnings during this challenge.

The first step of the challenge is to create a simple resume in CSS and HTML, host it on S3, serve it via Cloudfront and finally provide a friendly URL with Route53.

While it's challenging enough for a first step, I opted to spice things up a bit :)

Here is my version:

  • Build my whole website, including my resume
  • Build the infrastructure as code with CDK
  • Setup a simple pipeline for CI/CD

Getting started

It's considered best practice that infrastructure and runtime code live in the same package, so I created my repository with two folder:

  • website
  • infrastructure

I have a few requirements for my website:

  • redirect all http to https
  • the canonical domain is www, in a nutshell: redirect the non-www domain to the www domain.
  • clean urls, no *.html extension. For example, the resume url is https://www.benoitpaul.com/cv/, not https://www.benoitpaul.com/cv.html

Frontend: nextjs

Since I need a complete website and a blog, I'll create the website with Next.js.

For now a static website will do the trick. There's no official support for image optimization at build time, so I'll just need to it turn off

const nextConfig = {
  ...
  images: {
    unoptimized: true,
  },
};

Infrastructure as Code

To get started quickly, I first created an infrastructure with the AWS management console.

There are plenty of tutorials and YouTube videos about this, so that went well. I was able to host a simple html page in no time.

Next step, let's do it with code!

CDK

The simplest choice for me to create an infrastructure as code is to use the CDK. As a frontend developer I'm familiar with TypeScript, and it's a first class language for the CDK.

I created my own website CDK construct to abstract all the details.

There are a few things I learned along the way.

Domain and certificate

I had purchased my domain at Namecheap, so I created my Hosted Zone manually and copied the NS records in Namecheap.

Public buckets

My first version used private buckets and an origin access identity for Cloudfront to access the buckets, but that prevented to use the bucket as a website.

With the bucket website option turned off, I would have needed to forget the clean url, but really, who wants to use *.html extensions?

An option would be to upload the HTML files without an extension and make sure to set the content-type to text/html, but that's too hacky for me. AWS provides a website option for buckets, so let's use it!

All things considered, it's OK for my buckets to be public as there's no private information. If necessary, you can still use the website endpoint as the origin, with access restricted by a Referer header.

With that in place, the last thing I needed to get the clean url was to set the trailing slash option in NextJs

const nextConfig = {
  ...
  trailingSlash: true,
};

Redirecting non-www to www

To get the non-www redirecting to the canonical www domain, I had to create:

  • two buckets
  • two cloudfront distributions
  • 2 sets of AA and AAAA records

The canonical bucket - www.benoitpaul.com is set as a website with the index set to index.html. The naked domain bucket - benoitpaul.com is also set as a website, but with the redirect to www.benoitpaul.com

Then I set a cloudfront distribution for each bucket. Since the buckets are defined as websites, the CDK takes care of setting the cloudfront origin to the bucket website endpoint (instead of the REST endpoint)

Lastly, the AA and AAAA records are created for each cloudfront distribution

Here is a diagram of the website architecture:

Diagram of a website architecture in AWS

Pipeline as Code

The pipeline is gonna be super simple. It will be will triggered when there is a code change in the main branch.

There are two constructs we can use to create a pipeline:

In the end, I could have built the pipeline with either construct, but I opted for aws-cdk-lib.pipelines.CodePipeline since the AWS describes it as an opiniated construct library, and to give it a try first.

I created a stack dedicated to the pipeline, and defined the the three steps that made it up.

Step 1: get the source code

Since my code is in GitHub, and I want to trigger the pipeline when there is a change to the main branch, I create a connection to my repo, manually in the AWS management console

Step 2: build the code

When I create the pipeline, I set the synth property to a new ShellStep, and define the build steps.

It's as simple as navigating into the website directory, building the Next.js app, then navigating to the infrastructure and building the CDK app.

Step 3: deploying to production

The last step is to deploy to production. I added a stage to the pipeline to deploy the stack that was synthetized.

Conclusion

At the end of this chunk of the challenge, I have a complete website hosted on AWS, available with my domain name.

The final resume is online!