Template to setup a linode server with DNS and HTTPS

use terraform to coordinate stuff

Published January 23, 2020

scripting terraform linode dns

I use DNSimple for domain management, and I’ve been playing around with a bunch of different cloud providers and deployment setups. So I wanted to make it easier to give these machines names rather than clicking through the control panel all the time. Lets walk through how to use terraform, DNSimple, and linode to provision and new machine and give it a name on the internet, and then create a webserver on it to which encrypts traffic.

This is really a base template for easily spinning up simple sites.

  1. Get a domain and host it on DNSimple
  2. Sign up for ISP, in this case Linode
  3. Get API token for linode
  4. Terraform up your server and get it’s ip address
  5. Get api tokens from DNSimple
  6. Terraform up DNSimple to point to your ip address
  7. Setup nginx with letsencrypt to have secure hosting

Setting up linode

Create a new linode account if you don’t have one. Then go to your Linode Profile Token Page and create a personal access token. Again copy it some where secure and then set it in the current environment. After this everything will be scripted and automated.

1
export LINODE_TOKEN=asdfasdf

In order to pass variables into terraform, you need to prefix them with TF_VAR_. Lets do that now.

1
export TF_VAR_linode_token=$LINODE_TOKEN

In this script, we are setting up a Debian linode server with our local ssh keys installed. (It installs the public key found in ~/.ssh/id_rsa.pub.)

linode.tf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
variable "linode_token" {}

provider "linode" {
  token = var.linode_token
}

# Setup the ssh key from the local machine
resource "linode_sshkey" "key" {
  label = "sshkey"
  ssh_key = chomp(file("~/.ssh/id_rsa.pub"))
}

# Create a server
resource "linode_instance" "web" {
  image = "linode/debian10"
  label = "Web"
  group = "Terraform"
  region = "us-east"
  type = "g6-standard-1"
  authorized_keys    = ["${linode_sshkey.key.ssh_key}"]
  # Leave the root password unset to keep it random
#  root_pass = "YOUR_ROOT_PASSWORD"
}

output "server_ip" {
  value = "${linode_instance.web.ip_address}"
}

Running terraform init will validate this file and make sure that the right plugins are installed. You can then set up the server but running terraform apply.

Once it’s up, you can test connecting to it using

1
ssh root@$(terraform output server_ip)

Get a DNSimple token

I already use DNSimple to host my domains, so that’s what I’m going to use here. DNSimple is cool but many places offer domains and I have no special insight as to why one is better than the other. Hosting the domain at your cloud provider is probably preferable if you are committed to one, but I host things all over the place.

Log in to your DNSimple Account Page and create a new account access token. Go to the Account tab, then on the left select Automation

1
export DNS_TOKEN=asdfasdfasdf

Now we can test out the token and look for our account id, which is displayed on the page but why not just verify that things are looking good.

1
curl https://api.dnsimple.com/v2/whoami -H "Authorization: Bearer ${DNS_TOKEN}" | jq .

Which should make something like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "data": {
    "user": null,
    "account": {
      "id": 7008,
      "email": "wschenk@gmail.com",
      "plan_identifier": "gold-v1-yearly",
      "created_at": "2012-06-07T11:47:25Z",
      "updated_at": "2020-01-04T18:11:05Z"
    }
  }
}

You can set and environment variable automatically with

1
export DNS_ACCOUNT_ID=$(curl https://api.dnsimple.com/v2/whoami -H "Authorization: Bearer ${DNS_TOKEN}"|jq .data.account.id)

To get a list of domains

1
curl https://api.dnsimple.com/v2/${DNS_ACCOUNT_ID}/domains -H "Authorization: Bearer ${DNS_TOKEN}"|jq .

Now that we have a working token and account id, we can use terraform to setup a name that points to our new server.

1
2
3
export TF_VAR_dns_token=$DNS_TOKEN
export TF_VAR_dns_account_id=$DNS_ACCOUNT_ID
export TF_VAR_dns_domain=willschenk.com

dnsimple.tf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
variable "dns_token" {}
variable "dns_account_id" {}
variable "dns_domain" {}

provider "dnsimple" {
  token = var.dns_token
  account = var.dns_account_id
}

resource "dnsimple_record" "web" {
  domain = var.dns_domain
  name   = "web"
  value  = linode_instance.web.ip_address
  type   = "A"
  ttl    = 3600
}

Then run terraform init to download the DNSimple provisioner, and terraform apply to set the web address of the TF_VAR_dns_domain domain to the public IP that linode gave you.

Terraform is also smart enough to order the dependacies, so if you setup everything from scratch it will setup the server first in order to get the IP address that it needs to setup the domain record. Nifty.

Setting up the server

Next we are going to run a script over SSH to do the provisioning of the Debian instance. I think that this is easier that using packer or some other tool, since we only have a few commands that need to run. We should setup the script so that it can run multiple times without any ill effect. The trick here is that if we really change things, we should backup the data on the server and completely redeploy everything from scratch. You don’t want to manually login to the server to make changes really at any point.

This is what we’re going to do:

  1. Set the Fully Qualified Domain Name
  2. Update all system packages (this can take a while the first run)
  3. Install nginx
  4. Install certbot from it’s repository
  5. Run the certbot command, which will prompt you to fill out information.

setup.bash

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash

FQDN=web.${TF_VAR_dns_domain}

# We copy this script over and the run it so that you are able to
# interact with the script using your local machine

echo Copying setup script over
ssh root@$(terraform output server_ip) -T "cat > /tmp/setup.bash;chmod +x /tmp/setup.bash" <<EOF
echo Setting hostname to ${FQDN}
hostnamectl set-hostname ${FQDN}

echo \n Running apt-get update and upgrade
apt-get update && apt-get upgrade -y

echo \n Installing nginx
apt-get install -y nginx

echo \n Adding certbot app repository
add-apt-repository ppa:certbot/certbot

echo \n Install certbot
apt install -y python-certbot-nginx

echo \n Setup certbot for nginx for the domain ${FQDN}
certbot --nginx -d ${FQDN}
EOF

echo Running script
ssh root@$(terraform output server_ip) "bash /tmp/setup.bash"

Run this with bash setup.bash and it will copy itself over to the remote server and run the setup scripts. It may take a minute or two for the remote server to be up and accepting ssh connections.

I set the server to redirect everything to HTTPS. With this baseline you can dump in your static files, add subdomains, or do whatever you want.

Now you have a server

You need a domain name to get HTTPS, and there are a lot of services that require that. This is a simple template to get you up and running. From here you can expirement and shut it down, or you can use this as a base to build something else up. (This post started because I wanted to play around with dokku and I got distracted setting up a server and domain. Now I can spin something up quicky and play, and if I don’t want to keep anything I can do terraform destroy and it all goes away.

I find that it’s actually nice to use scripts to set things up and tear things down. A lot of these blog posts are actually me going through the steps of setting things up over and over, and recreating the process a number of times to make sure that I understand how it works.

References

  1. https://www.linode.com/docs/applications/configuration-management/how-to-build-your-infrastructure-using-terraform-and-linode/
  2. https://www.terraform.io/docs/providers/linode/r/sshkey.html
  3. https://www.terraform.io/docs/providers/dnsimple/r/record.html
  4. https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-18-04
  5. https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx

Previously

dominictarr/scalable-secure-scuttlebutt

2020-01-22

Next

De La Soulviet

2020-01-24

howto

Previously

Terraform and Packer with Digital Ocean Automate all the things

2019-12-24

Next

Playing with tailwindcss An excersize in minimilism

2020-02-01