Originally published on rolfstreefkerk.com
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):
- 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 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
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, 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:
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!
Top comments (11)
Hi Rolf Streefkerk,
Top, very nice !
Thanks for sharing
I need this setup often, there has to be an easier way than investing an hour or so on this 🤔
I managed to write a Github action that goes all this with minimal dependencies, blog repo
Yes you could host on github actions the terraform script and run everything based on a git action.
This article was meant as a starting poiint that you can adjust as you see fit with your workflow.
Thanks for reading!
Very comprehensive and detailed article!
Can you specify the cost of the caused cloud resources? How much does it cost for a month?
This would really depend on your traffic, but lets say for any starting website with a few thousand viewers a month you're looking at;
Domain (yearly one time fee, for a .com ~ 10 USD) = ~10 USD = 0.83 USD
Route 53 Hosted zone ; $0.50 per month for the first 25 hosted zones = 0.5 USD
S3 5GB of Amazon S3 storage in the S3 Standard storage class; 20,000 GET Requests; 2,000 PUT, COPY, POST, or LIST Requests; and 100 GB of Data Transfer Out each month. Free storage probably, otherwise very low cost storage. Traffic beyond free tier it's 0.005 USD for 1000 in requests, 0.0004 USD for 1000 out requests.
Cloudfront you're likely operating in the free tier, including SSL, request in-out
aws.amazon.com/cloudfront/pricing/
Total: 1.33 USD per month incl. ".com" domain name for most small to medium use-cases.
Looks pretty straightforward for a static website 🙃
what do you mean exactly?
I’m assuming the commenter above is referring to the vast number of steps required to setup a static website on AWS. There are actually easier ways with fewer steps, for instance using AWS Amplify.
The upside to the approach you present here though is that you explain many of the details that a solution like Amplify hides. If someone just wants to deploy a static site without knowing much about the details, Amplify is probably what they want. But if someone wants to understand the steps involved so they can grow to more complicated deploys or even move to other cloud providers, then I think your article is a nice resource. :)
Yes i believe you've summarized it perfectly.
This is a "vehicle" for learning, and at the same time if you prefer control over what is deployed exactly you will have that with Terraform over something like Ampllify that hides much of this.
Thanks for your time
Thanks for this, the one thing that would hold me back would be the risk of very high AWS fees as a consequence of some error with the code and so other external factor.
You can verify all resources deployed in the github repo's I've provided.
github.com/rpstreef/terraform-stat...
In my opinion;
You make a valid point with regards to AWS cost tracking.
In my opinion, what you can do is:
Add Budget Alerts to your AWS Account and set several of them with different value amounts to track your overal account spend.
Use this Terraform module to implement them:
github.com/cloudposse/terraform-aw...
Alternatively, use the AWS Console UI to set it in the Budget screen.
I hope that helps
Some comments may only be visible to logged-in visitors. Sign in to view all comments.