How to deploy your own website on AWS

How to deploy your own website on AWS

Take full control of your website, and following along with our how-to guide.

Benefits of building and deploying a website from scratch:

Own the code and control it as you see fit
Learn AWS and how to deploy a website to AWS S3
Understand DNS and Route53
How to use DevOps to solve automation issues

Read on to get started.

Follow me on Twitter to keep updated on the latest articles on AWS and more.

You will need the following to get started

a static site, I recommend one of these frameworks (and I’ve used):

Hugo

existing themes will get you a website quick, such that you only have to modify color schemes and layouts.

or Astro; if you’d like to integrate React, VueJS etc. code as well.

use their themes page here to get a starting point.

an AWS account, which requires a credit card to setup.

a domain, wherever you registered it.

In this how-to I use Porkbun as my favorite registrar.

a computer with;

Terraform/OpenTofu installed. We use Terraform in this article.

AWS CLI installed with profile configured you want to use for your website deployment.

Git command line tooling.
your code editor of choice, I use VSCode.

a GitHub account so you can fork my example repository.

(optional) email inbox provider, I use Migadu.
## What are we creating today?

We are creating the following services and configurations:

AWS S3 bucket to send your website source files to;
AWS CloudFront distribution that will cache, optimize website delivery globally to your audience.
AWS Route53 for your;

Email service records with DNSSec configuration,
You can then hookup a newsletter service like ConvertKit.com
Name Server Configuration for your domain; yourwebsite.com

and the CloudFront distribution to optimize your website hosting.

GitHub Actions for a CI/CD pipeline, deploying your website on command within a minute.

Setup your Domain on AWS

Login to your AWS Console.

Go to Route53, after you’ve logged in, and navigate to Hosted zones.
Create your hosted zone and enter your website domain; yourwebsite.com

Make a note of the Hosted zone ID, We’ll use that in the next step for Terraform to automate all the Route53 records to the correct Domain name.

If you choose to automate it using Terraform;

export the Name Servers from your domain registrar (Porkbun, etc.).
add the hosted zone resource configuration into my example Terraform module and hook it up to all the related resources requiring the Hosted zone id.

(Optional) Email hosting

If you like to setup an email hosting solution, I use migadu.com, keep the Route53 website open.

We’ll import additional configuration text blocks into Route53 to make your domain work with the inbox service.

In Mail inbox service, there is a DNS Configuration panel.
Get the BIND records output, copy/paste the text of all the DNS records.

If you require automatic mail server discovery for your Email;
Check for these strings in the provided DNS records; _autodiscover or autoconfig

Then in AWS Route53, for your hosted zone; Import zone file, and copy paste the lines of text in that dialog box.

Now you can add your new email inbox in your mail apps.

If you have _autodiscover and / or autoconfig DNS records included, you can;

go to your email app,
add a new inbox using; email and password.
Finished, inbox added without further configuration required.

Otherwise, take a note of your mail inbox service SMTP and IMAP server configurations.

Automating your AWS account setup with Terraform

Now that we have the Domain in place, and the Mail inbox (optional), we can configure the actual site deployment.

Create a new project by Forking: https://github.com/rpstreef/terraform-yourwebsite.com

This is a template that will use Terraform modules from another Git repository; https://github.com/rpstreef/terraform-static-site

What does this template create?

This template will create the following set of resources;

S3 bucket for Terraform state
S3 bucket for yourwebsite.com

S3 CORS configuration for ConvertKit.com , this will allow CORS between ConvertKit JavaScript and your domain without warnings.

ACM Certificate for SSL, *.yourwebsite.com, and the ACM validation records for Route53 for auto-renewal of SSL.
Route53 A, and AAAA records (IPv6)
Route53 DNSSec,

only the first step! The second step must be done manually with your Domain Registrar.

Lambda function for redirects to index, ensures you have nice URL’s.

E.g. https://yourwebsite.com/contact instead of https://yourwebsite.com/contact/index.html

CloudFront for caching, and web-page speed optimization, and SSL secured.

How to adjust the template?

To make the template fit for your website.

Do the following

Change these lines in the terraform.tfvars file :

where you read yourdomain.com,
and your hosted_zone_id for yourdomain.com.
check 404 response at the bottom of the file to see if that matches up with your website structure. Additionally HTTP response codes can be added as blocks; {}.

If you need additionally CORS settings, add an extra rule in the same way as f.convertkit.com.

# General
environment = “prod”
region = “us-east-1”
project = “yourdomain.com”

# use tags to track your spend on AWS, seperate by ‘product’ for instance.
tags = {
environment = “production”
terraform = true
product = “yourdomain.com”
}

# Which config line used in .aws/config
aws_profile = “yourdomain-profile”

# Route53
hosted_zone_id = “Z000000000”

# www.yourdomain.com
product_name = “yourdomain” # avoid to use `.`, this cause an error.
bucket_name = “yourdomain.com” # your site is deployed here.

# S3 bucket CORS settings:
bucket_cors = {
rule1 = {
allowed_headers = [“*”]
allowed_methods = [“GET”, “PUT”, “POST”]
allowed_origins = [“https://f.convertkit.com”]
expose_headers = [“ETag”]
max_age_seconds = 3000
}
}

domain_names = [“yourdomain.com”, “www.yourdomain.com”]

custom_error_responses = [{
error_code = 404
error_caching_min_ttl = 10
response_code = 200
response_page_path = “/404.html”
}]

Make sure the configuration in project-state.tf file is correct;

check the bucket name,
and the AWS profile name used, e.g. yourwebsite-profile.

locals {
projects_state_bucket_name = “tfstate-yourwebsite.com”
}

provider “aws” {
region = “us-east-1”
profile = “yourwebsite-profile”
}

terraform {
# First we need a local state
backend “local” {
}

# After terraform apply, switch to remote S3 terraform state
/*backend “s3” {
bucket = “tfstate-yourwebsite”
key = “terraform.tfstate”
region = “us-east-1”
profile = “yourwebsite-profile”

encrypt = true
acl = “private”
}*/
}

If all the configuration checks out;

run terraform init, this will download the dependent modules.
then; terraform apply > yes

When it’s finished deploying, make note of the variables in the output. We’ll need them later on. To retrieve these later, type; terraform output in the ./environments/production directory.

Which one came first? The chicken or the egg?

When finished, we need to adjust the project-state.tf file:

Place the backend “local” block in comments.
Remove the comments from the backend “s3” block.
Migrate the state from local to S3:

terraform init -migrate-state
type: yes to copy state from local to s3.

Now it’s fully deployed and we have saved our Terraform state to AWS S3, it’s no longer on your disk. You can remove those tfstate files if you like.

Establishing DNSSec “Chain of Trust”

The benefit of DNSSec is the establishment of the “chain of trust”.

That means, it is verified that;

You own the domain,
when you navigate to that domain, the information is coming from your servers and not from someone else’s server (e.g. hackers etc.)

If you’d like to learn more about DNSSec, this article is a good primer

Now to finalize DNSSec configuration, you will have to manually modify the Domain registrar information.

First, get the required DS records for DNSSec; View information to create DS record

Then, in the next screen click; Establish a Chain of Trust.

You will see a table outlining configuration items.

If you did not register your domain on Route53, click Another Domain registrar

On Porkbun, my domain registrar, the screen looks like this:

Enter the following at the dsData block; on the left is the Porkbun input field name, on the right as the value I will place the name used at Route53:

Key Tag: Key tag

DS Data Algorithm: Signing algorithm type

Digest Type: Digest algorithm type

Digest: Digest

If you have a different registrar, you’ll need to review their documentation, it may be slightly different.

How to check your configuration works?

Finally, use this online tool; https://dnssec-debugger.verisignlabs.com/ to check your domain, if you’re getting all green check-marks.

If they’re all green, It means your chain of trust has been successfully established!

Now we have a DNSSec secured domain configuration with an S3 static hosted site via CloudFront with SSL.

Performant
Cheap
and Secure.

Upload your website

We can use a local deployment setup with the AWS CLI, or via GitHub Actions.

Local deployment with a script

Depending on your system (Linux, Windows, Mac), you may need to alter this script.

On Linux, we can automate your website deployment as follows using this bash script:

#! /bin/bash

npm run build

aws s3 sync dist s3://yourwebsite.com –profile yourwebsite-profile

aws cloudfront create-invalidation –distribution-id <CloudFront Distr. Id> –paths “/*” –profile yourwebsite-profile

Make sure to;

replace npm run build for the script that generates your static website build.
replace dist in the aws s3 sync dist, if your website build is in another folder.
replace <CloudFront Distr. Id> with your CloudFront distribution id.

you can find it in the outputs after terraform apply has finished; cloudfront_distribution_id

GitHub Actions

If you like to use automation instead, it’s very easy and cheap to setup.

What does this cost anyway?

Plan
Storage
Minutes (per month)

GitHub Free
500 MB
2,000

GitHub Pro
1 GB
3,000

You can deploy quite a few times before you hit the Pro ceiling in terms of Minutes per month:

The storage size is based on your repository size which, for most, will be very hard to reach.

Operating system
Minute multiplier

Linux
1

Windows
2

We choose a Linux build environment, specifically ubuntu-latest, to get the most out of our free minutes.

Check out more about GitHub Action pricing here.

How does it work?

To deploy using GitHub Actions, do the following:

First, create a new file in your website’s GitHub repository at .github/workflows/deploy-on-comment.yml.

Add the following code to the file:

Note; I’m assuming your website is Node (v20) based. Adapt where needed!

name: Deploy on Comment
on:
issue_comment:
types: [created, edited]
push:
branches:
main

jobs:
deploy:
runs-on: ubuntu-latest
steps:
name: Checkout code
uses: actions/checkout@v3

name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 20′

name: Install dependencies
run: npm install

name: Build website
run: npm run build

name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1

name: Sync build output with S3 bucket
run: aws s3 sync ./dist s3://your-s3-bucket-name

name: Invalidate CloudFront cache
run: aws cloudfront create-invalidation –distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} –paths “/*”

There’s several secret variables that need to be created on GitHub, coming from the Terraform output we received earlier:

AWS_ACCESS_KEY_ID:

AWS_SECRET_ACCESS_KEY:

CLOUDFRONT_DISTRIBUTION_ID

If you need to look up what these are again, navigate to your terraform-yourwebsite.com git repository and then;

cd ./environments/production
terraform output

Input them at the following location in GitHub:

You can now create an issue that details the updates on your website for example.
For each comment that is added, the deployment will start.

You can follow the deployment steps taken and the logs in the Actions tab.

(Optional) In case you’d want to change the GitHub Actions to use a Pull request instead, you can modify that in the deploy script.
> For more alternative triggers, check out the GitHub Actions documentation.

Your website is online!

Now when you go to your web URL; yourwebsite.com, everything should be up and running.

What we have build;

(Optional) Email hosting with Migadu (or choose any that you have); e.g. hello@yourwebsite.com

You can connect this to your ConvertKit.com mailing list for example.

Your own personal domain that is DNSSec secured.

You’ll be certain no hackers can hi-jack your domain.

Your static Website on [[AWS]] using AWS S3.

Free web-hosting!

CloudFront Content Delivery Network (CDN), enabling:

SSL protected website. Form submits are all encrypted by default.
Increased performance in load speeds, latency across the globe.
URL rewrites for static websites. No index.html will be displayed when navigating.
and redirects for 404 not found pages. Your visitors will see the 404.html page instead of an error text message.

Questions? Let’s discuss!

What do you struggle with on AWS?
Did you have issues with deploying on AWS?
How would you do it?

Let me know down in the comments or on Twitter

Appreciate your time and till the next one!