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:

  1. From the Lightsail Dashboard, connect to your Lightsail instance via the Connect using SSH button.
  2. 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!

Leave a Reply

Your email address will not be published. Required fields are marked *