How to deploy your own website on AWS
Tags: aws, devops, webdev, beginners
Published: 2024-05-28 03:36:21

> Originally published on [rolfstreefkerk.com](https://rolfstreefkerk.com/posts/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](https://x.com/rolfstreefkerk) to keep updated on the latest articles on AWS and more.

## You will need the following to get started

1. **a static site**, I recommend one of these frameworks (and I've used):
	- [Hugo](https://gohugo.io/) 
		- existing [themes](https://themes.gohugo.io/) will get you a website quick, such that you only have to modify color schemes and layouts.
	- or [Astro](https://astro.build/); if you’d like to integrate React, VueJS etc. code as well.
		- use their themes page [here](https://astro.build/themes/) to get a starting point. 
2. **an [AWS account](https://aws.amazon.com/)**, which requires a credit card to setup.
3. **a domain**, wherever you registered it.
	- In this how-to I use [Porkbun](porkbun.com) as my favorite registrar.
4. **a computer with**;
	- [Terraform](https://www.terraform.io/)/[OpenTofu](https://opentofu.org/) installed. We use Terraform in this article.
	- [AWS CLI](https://aws.amazon.com/cli/) installed with profile configured you want to use for your website deployment.
	- [Git](https://git-scm.com/downloads) command line tooling.
	- your code editor of choice, I use [VSCode](https://code.visualstudio.com/).
5. **a [GitHub](https://github.com/) account** so you can fork my example repository.
6. *(optional) email inbox provider*, I use [Migadu](migadu.com).
## 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.

![Deploy your website to AWS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40hkfi68ukl8m6vw3y02.png)

## Setup your Domain on AWS

Login to your AWS Console.

1. Go to Route53, after you’ve logged in, and navigate to `Hosted zones`.
2. Create your hosted zone and enter your website domain; `yourwebsite.com`
3. 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.

![Terraform hosted zone id](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ydaop4rxoper3xj7xt1.png)

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](https://github.com/rpstreef/terraform-static-site) 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`

![Migadu BIND records](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1mfczsfjwddthnluqs9d.png)

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

![Route53 Hosted zone file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dj7gfedvyf4egixw2s2c.png)

- 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`.

```hcl
# 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`.

```hcl
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](https://www.csoonline.com/article/569685/dnssec-explained-why-you-might-want-to-implement-it-on-your-domain.html) 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`

![Route53: DNSSec](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zrbuax3y4n65hjr0pm5v.png)

- 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:

![Porkbun.com DNSSec](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yavg03ukeye7ienuuswp.png)

- 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:

```bash
#! /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](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions).
#### 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!

```yaml
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:

![GitHub Secrets configuration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uddfhnlxicco614wxx72.png)

- 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.

![GitHub issues, comment to deploy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4srw32r69d97j7za1dj.png)

- (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](https://docs.github.com/en/actions/using-workflows/triggering-a-workflow).

## 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](https://x.com/rolfstreefkerk) 

Appreciate your time and till the next one!