We now have an AWS Lightsail instance running a WordPress site. Next, we’ll setup DNS so we don’t have to use the static IP to access the site. There are multiple ways we can go about setting up a domain with AWS Lightsail, but when I started this project I chose to use a .dev
domain and AWS doesn’t support that top-level domain. So in this post, I’ll talk about how I registered the domain with Google, manage the domain in AWS Route53, and mapped www.fishbits.dev
to my AWS Lightsail instance with Terraform!
Domain Setup
The side project bug hit me last year and I ended up buying the fishbits.dev
domain. There were some things I didn’t realize about the .dev
top-level domain, one of which is AWS doesn’t support it. That meant I couldn’t use AWS to purchase it and went through Google Domains to buy it. I have a couple other domains registered through Google, so it wasn’t that big of a deal. However, since AWS didn’t support that top-level domain, I also found out I couldn’t use the AWS Lightsail features that could manage my domain for me. I prefer to manage my domains with Route53 anyway, so I wasn’t that bothered by this bump in the road. Additionally, this generated an opportunity for me to do something that most people probably haven’t done before!
The first step is to register your domain. Once you do, the registrar will create name servers for your domain. The name servers are responsible for translating your DNS records into routable IP addresses. In my case, Google’s name servers would initially be responsible for routing. However, I don’t want Google to be in charge, I want to manage my domain via AWS Route53.
The next step of this process is to go to the AWS Console and navigate to Route53. Here, we will want to create a new Hosted Zone. After you create a hosted zone, AWS will create a set of name servers for you automatically. There should be 4 values, copy those values and navigate back to your Google Domain dashboard. There is an option to use Custom Name Servers. Add each value from the AWS name servers into the Custom Name Server entries in Google. Now it’s time to wait. You’ve told the registrar to point to Route53 for name resolution and it will take up to 48 hours for that message to propagate through the DNS cache.
Terraform
The next step is to setup Route53 with Terraform. Since we created the Route53 Hosted Zone manually, we’ll define the zone in Terraform and import it. Then we’ll setup the CNAME and ALIAS records that we’ll use to route to our Lightsail instance.
All code can be found at https://github.com/fishst1k/fishbits. We’ll be building off our previous Terraform from bit2. The first thing we need to do is create our Route53
module.
├── README.md
├── main.tf
├── modules
│ ├── lightsail
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ └── route53
│ ├── main.tf
│ └── variables.tf
├── terraform.tfvars
└── variables.tf
Let’s start by defining our input variables in the variables.tf
:
# modules/route53/variables.tf
variable "zone_name" {
type = string
description = "The route53 zone name."
}
variable "records" {
type = list(object({
name = string
type = string
ttl = number
records = list(string)
}))
}
The zone_name
is the name of the zone you created manually. This will be used to import the Hosted Zone into our state. The records
variable is a little more complex, it’s a list of objects that define a Route53 record. Next, let’s create the module that will use these variables in main.tf
:
# modules/route53/main.tf
resource "aws_route53_zone" "route53" {
name = var.zone_name
}
resource "aws_route53_record" "route53" {
for_each = {for each in var.records: each.name => each}
zone_id = aws_route53_zone.route53.zone_id
name = each.value.name
type = each.value.type
ttl = each.value.ttl
records = each.value.records
}
I want my Terraform to manage the state of my hosted zone, so I defined the aws_route53_zone
as a resource
and we’ll manually import that. Next, the aws_route53_record
uses a for_each
to iterate through our list of objects and create/manage our records.
We can now update our top-level module, import our new Route53 module, and put the Terraform to work.
# ./main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.64.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "us-west-2"
}
module "lightsail" {
source = "./modules/lightsail"
lightsail_instance_name = var.lightsail_instance_name
lightsail_availability_zone = var.lightsail_availability_zone
lightsail_ip_address_type = var.lightsail_ip_address_type
lightsail_blueprint = var.lightsail_blueprint
lightsail_bundle_id = var.lightsail_bundle_id
lightsail_static_ip_name = var.lightsail_static_ip_name
}
module "s3_bucket_tfstate" {
source = "./modules/s3"
bucket_name = var.bucket_name
user_role_arn = var.user_role_arn
tags = {
Name = var.bucket_name
Environment = var.environment
}
}
module "route53" {
source = "./modules/route53"
zone_name = "fishbits.dev"
records = [
{
name = "fishbits.dev"
type = "A"
ttl = 300
records = [module.lightsail.static_ip]
},
{
name = "www.fishbits.dev"
type = "CNAME"
ttl = 300
records = ["fishbits.dev"]
}
]
}
Here we set our values to be used in the child module. Before we can use this though, we’ll need to import the current state, primarily our Route53 Hosted Zone. Now, we could have used a data
declaration instead of a resource
, but I intend to manage my Route53 zone in code so that is the reasoning behind importing the state. To import a aws_route53_zone
we just need to look at the provider documentation. We can see that it requires the zone id
of the Hosted Zone. From the top level of the Terraform code, execute the following:
$ terraform import module.route53.aws_route53_zone.route53 <zone id>
This will import the current state into Terraform and allow us to move on using the terraform plan/apply
commands. When you are ready, go ahead and plan
and apply
your Terraform.
Certificate Management
Now that our domain is in place and the terraform is applied, we are almost done. The last step is to generate a certificate which allows us to establish a secure HTTPS connection to our Lightsail site. AWS has a certificate manager and if we were to front our Lightsail site with an AWS LoadBalancer it would be very easy to create, manage, and apply our cert via Terraform. However, this is a simple site and we are doing things in a simple manner, so we don’t have multiple WordPress servers running that we need to load balance. Therefore, WordPress provides a mechanism that will allow us to generate and apply a certificate using Let’s Encrypt! So this next step is a bit manual:
- From the Lightsail Dashboard, connect to your Lightsail instance via the
Connect using SSH
button.
- Run the bitnami
bncert
tool – instructions here.
That’s it! The bncert
tool will also setup a cron that will automatically refresh your cert! This is good because Let’s Encrypt certs are only valid for 60 days.
Full Sail Ahead!
If you made it this far, congratulations, you have a functioning WordPress site running on AWS Lightsail! There is one additional bit I’d like to share next time though. Currently, our Terraform state is stored locally to the machine we’ve been working on. Next time, we’ll walk through migrating that state to an S3 bucket!