How I built this site
May 10, 2022
I always wanted a blog where I could:
- Write with markdown
- Hit a button and have it deploy
- Maintaining full control over the backend without installing Wordpress
Here’s how I made this using Terraform, AWS, Cloudflare, and Gatsby.
1. Create vendor accounts
- Buy a domain
- Setup AWS account
- Create cloudflare account
You’ll need the following API keys:
- AWS Access Key
- AWS Secret Key
- Cloudflare API Key
- Github API Key
2. Initialize Env Variables
Create a .env
file. (Don’t forget to .gitignore
!)
GITHUB_TOKEN = {FILL_IN_HERE}
DOMAIN = {FILL_IN_HERE}
AWS_ACCESS_KEY = {FILL_IN_HERE}
AWS_SECRET_KEY = {FILL_IN_HERE}
CLOUDFLARE_EMAIL = {FILL_IN_HERE}
CLOUDFLARE_API_KEY = {FILL_IN_HERE}
GITHUB_TOKEN = {FILL_IN_HERE}
3. Create a github repo
With a github token, terraform can spin up a repo.
provider "github" {
token = var.GITHUB_TOKEN
}
resource "github_repository" "brianwestphal" {
name = "brianwestphal.com"
description = "My personal blog"
visibility = "private"
}
4. Ship it!
Run the following script to deploy. (This assumes you use direnv)
#!/bin/bash
terraform get
terraform init -upgrade
eval "$(direnv export bash)"
export TF_VAR_DOMAIN=$DOMAIN
export TF_VAR_AWS_ACCESS_KEY=$AWS_ACCESS_KEY
export TF_VAR_AWS_SECRET_KEY=$AWS_SECRET_KEY
export TF_VAR_CLOUDFLARE_EMAIL=$CLOUDFLARE_EMAIL
export TF_VAR_CLOUDFLARE_API_KEY=$CLOUDFLARE_API_KEY
export TF_VAR_GITHUB_TOKEN=$GITHUB_TOKEN
terraform refresh
terraform plan
terraform apply -auto-approve
5. Install Gatsby
https://www.gatsbyjs.com/docs/quick-start/
6. Create static s3 website
Write a blog post (this should be the fun part).
7. Write some Terraform
This TF code will spin up our domain, HTTPS routing as well as post the static files from Gatsby to s3 to be served at your domain.
Starting with setting up our terraform repository
locals {
s3_bucket = var.DOMAIN
domain = var.DOMAIN
}
# This configures s3 as our remote backend so we can use terraform across
# any machine.
terraform {
backend "s3" {
bucket = "{S3_BUCKET to store site data}"
key = "brianwestphal.com"
region = "us-east-1"
encrypt = true
}
}
Next we’ll generate the AWS resources.
provider "aws" {
region = "us-west-1"
access_key = var.AWS_ACCESS_KEY
secret_key = var.AWS_SECRET_KEY
}
resource "aws_s3_bucket" "static_site_bucket" {
bucket = local.s3_bucket
acl = "public-read"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::${local.domain}/*"
]
}
]
}
EOF
website {
index_document = "index.html"
error_document = "index.html"
}
}
And push the site files to s3.
resource "null_resource" "build-frontend" {
triggers = {
always_run = timestamp()
}
provisioner "local-exec" {
when = create
command = "npm run build"
working_dir = ".."
interpreter = ["/bin/bash", "-c"]
}
}
resource "null_resource" "upload_to_s3" {
triggers = {
always_run = timestamp()
}
provisioner "local-exec" {
working_dir = ".."
command = "aws s3 cp --recursive public/ s3://${aws_s3_bucket.static_site_bucket.bucket} --exclude 'assets/*'"
}
depends_on = [null_resource.build-frontend]
}
Then create our domain with HTTPS
provider "cloudflare" {
email = var.CLOUDFLARE_EMAIL
api_key = var.CLOUDFLARE_API_KEY
}
resource "cloudflare_zone" "main" {
zone = local.domain
lifecycle {
prevent_destroy = true
}
}
resource "cloudflare_record" "prod_root" {
type = "CNAME"
name = local.domain
zone_id = cloudflare_zone.main.id
value = aws_s3_bucket.static_site_bucket.website_endpoint
ttl = 1
proxied = true
}
resource "cloudflare_record" "www" {
type = "CNAME"
name = "www"
zone_id = cloudflare_zone.main.id
value = local.domain
ttl = 1
proxied = true
lifecycle {
prevent_destroy = true
}
}
8. Ship it!
Run the same deploy script from above. Now your edits will be live. Happy blogging.