How to deploy your own website on AWS

2024-5-27 on Monday
1892 words
10 minutes

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.

You will need the following to get started

  1. 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.
  2. an AWS account, which requires a credit card to setup.
  3. a domain, wherever you registered it.
    • In this how-to I use Porkbun as my favorite registrar.
  4. 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.
  5. a GitHub account so you can fork my example repository.
  6. (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 static site 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
    • Name Server Configuration for your domain;
    • and the CloudFront distribution to optimize your website hosting.
  • GitHub Actions for a CI/CD pipeline, deploying your website on command within a minute.
Static site on AWS

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;
  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.
Route53: Get hosted zone id

(Optional step) 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, keep the Route53 website open.

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

  1. In Mail inbox service, there is a DNS Configuration panel.
  2. 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: get BIND txt output
  1. Then in AWS Route53, for your hosted zone; Import zone file, and copy paste the lines of text in that dialog box.
Route53: Import zone file
  1. 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:

This is a template that will use Terraform modules from another Git repository;

What does this template create?

This template will create the following set of resources;

  • S3 bucket for Terraform state
  • S3 bucket for
    • S3 CORS configuration for , this will allow CORS between ConvertKit JavaScript and your domain without warnings.
  • ACM Certificate for SSL, *, 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.
  • 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

  1. Change these lines in the terraform.tfvars file :
    • where you read,
    • and your hosted_zone_id for
    • 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

# General
environment = "prod"
region = "us-east-1"
project = ""

# use tags to track your spend on AWS, seperate by 'product' for instance.
tags = {
	environment = "production"
	terraform = true
	product = ""

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

# Route53
hosted_zone_id = "Z000000000"

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

# S3 bucket CORS settings:
bucket_cors = {
	rule1 = {
		allowed_headers = ["*"]
		allowed_methods = ["GET", "PUT", "POST"]
		allowed_origins = [""]
		expose_headers = ["ETag"]
		max_age_seconds = 3000

domain_names = ["", ""]

custom_error_responses = [{
	error_code = 404
	error_caching_min_ttl = 10
	response_code = 200
	response_page_path = "/404.html"
  1. Make sure the configuration in file is correct;
    • check the bucket name,
    • and the AWS profile name used, e.g. yourwebsite-profile.
locals {
	projects_state_bucket_name = ""

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"
  1. If all the configuration checks out;

    • run terraform init, this will download the dependent modules.
    • then; terraform apply > yes
  2. 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?

  1. When finished, we need to adjust the 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.

  1. First, go to AWS Route53, get the required DS records for DNSSec -- View information to create DS record
Route53: DNSSec info
  1. 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: DNSSec settings
  1. 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?

  1. Finally, use this online tool; 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:// --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?

PlanStorageMinutes (per month)
GitHub Free500 MB2,000
GitHub Pro1 GB3,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 systemMinute multiplier

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:

  1. First, create a new file in your website’s GitHub repository at .github/workflows/deploy-on-comment.yml.
  2. Add the following code to the file:

Please note, I’m assuming your website is Node (v20) based. Adapt where needed!

name: Deploy on Comment
    types: [created, edited]
      - main

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

      - name: Set up Node.js
        uses: actions/setup-node@v3
          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
          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:

  1. If you need to look up what these are again, navigate to your git repository and then;

    • cd ./environments/production
    • terraform output
  2. Input them at the following location in GitHub:

Github: Secrets settings
  1. 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.
Github: Issues example
  1. You can follow the deployment steps taken and the logs in the Actions tab.

  2. (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;, everything should be up and running.

What we have build;

  • (Optional) Email hosting with Migadu (or choose any that you have); e.g.
  • Your own personal domain that is DNSSec secured.
    • You’ll be certain no hackers can hi-jack your domain.
  • Your Website on AWS with S3.
    • Free web-hosting!
  • CloudFront
    • 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.

Please let me know on Twitter if you have any questions or shoot me a mail!

I’m always interested to hear your thoughts on the article, improvements that can be made, or anything related.

Appreciate your time and till the next one!

Title:How to deploy your own website on AWS

Author:Rolf Streefkerk

Article link: [copy]

Last Modified:

© 2024 Rolf Streefkerk. All rights reserved.

Join Our Newsletter

Get weekly insights on health and IT career growth. Join our community of successful professionals.

    Join The Health & IT Insider for actionable tips and exclusive content.