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
- a static site, I recommend one of these frameworks (and I've used):
- 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;
- 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 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
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.
(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 https://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
orautoconfig
- 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.
- 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
foryourdomain.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;
{}
.
- where you read
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
- run
-
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
toS3
:terraform init -migrate-state
- type:
yes
to copy state from local to s3.
- Place the
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, go to AWS Route53, 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 atRoute53
:- Key Tag --
Key tag
- DS Data Algorithm --
Signing algorithm type
- Digest Type --
Digest algorithm type
- Digest --
Digest
- Key Tag --
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 theaws 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
- you can find it in the outputs after
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:
Please 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 https://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 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!