Deploying helm108.com

So at this point I have:

  • A base snapshot on Digital Ocean that has my user and system-wide dependencies set up.
  • A deploy snapshot built from the base snapshot that is ready to receive and serve the projects running on the server.

Now I need to launch a Droplet using the deploy snapshot as its image, install and run each of my projects on that Droplet and then make them accessible via the configured domains.

Terraform

Hashicorp’s product for this step is Terraform. Terraform is used to create your infrastructure, from launching instances on cloud providers to configuring domains at your DNS and CDN providers.

My deployment process starts with running deploy.sh:

1
2
3
4
5
6
7
8
#!/usr/bin/env bash
while read secret; do
final="${secret/export /export TF_VAR_}"
eval "$final"
done <secrets.sh

export TF_VAR_DEPLOY_SNAPSHOT_ID=$(jq -r '.builds[-1].artifact_id' packer-deploy-manifest.json | awk -F':' '{print $2}')
terraform apply

This reads all of the secrets set up in secrets.sh and redefines them as new environment variables prefixed with TF_VAR_. You can reference environment variables in your Terraform configuration but it seems to demand that they have that prefix.

Once the vars are set up the script then extracts the deploy snapshot id from the deploy image manifest, and then it has everything it needs to start the Terraform run.

terraform apply

Running terraform apply without specifying a path to a file just looks for any Terraform configurations and runs them, so I have a digitalocean.tf file that contains my entire configuration. At the top of this file it references all of the environment variables that were defined in deploy.sh:

1
2
3
variable "DIGITAL_OCEAN_TOKEN" {}
variable "DEPLOY_SNAPSHOT_ID" {}
...

Next it loads the required providers:

1
2
3
4
5
6
7
8
9
10
# Configure the DigitalOcean Provider.
provider "digitalocean" {
token = "${var.DIGITAL_OCEAN_TOKEN}"
}

# Configure the CloudFlare provider.
provider "cloudflare" {
email = "${var.CLOUDFLARE_EMAIL}"
token = "${var.CLOUDFLARE_TOKEN}"
}

And now we’re ready to start building things.

Launching a Droplet

This config launches a Digital Ocean droplet, writes some variables to .env files for those processes to use when they launch, and then runs update_repos.sh to pull in, install and then run each of the repos that were set up in the previous step that built the deploy image.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
resource "digitalocean_droplet" "helm108" {
image = "${var.DEPLOY_SNAPSHOT_ID}"
name = "helm108"
region = "lon1"
size = "512mb"

provisioner "remote-exec" {
connection {
type = "ssh"
user = "helm108"
agent = true
}
inline = [
"echo 'MAWBOT_TELEGRAM_TOKEN=${var.MAWBOT_TELEGRAM_TOKEN}' >> /var/www/mawbot/.env",
"echo 'OPEN_WEATHER_TOKEN=${var.OPEN_WEATHER_TOKEN}' >> /var/www/mawbot/.env",
"/update_repos.sh",
]
}
}

It then creates a Digital Ocean Domain resource that points the given URL at the newly-created droplet:

1
2
3
4
resource "digitalocean_domain" "helm108" {
name = "www.helm108.com"
ip_address = "${digitalocean_droplet.helm108.ipv4_address}"
}

Finally it creates a Floating IP address to attach to that droplet so that I’m not presenting the droplet’s actual IP address to the outside world:

1
2
3
4
resource "digitalocean_floating_ip" "helm108" {
droplet_id = "${digitalocean_droplet.helm108.id}"
region = "${digitalocean_droplet.helm108.region}"
}

So now there is a droplet running on Digital Ocean that can be accessed from the internet. It’s ready to handle the domains that it expects to run, but as I use Cloudflare for their CDN and DNS services I need to update my domains in Cloudflare with the new Floating IP:

1
2
3
4
5
6
7
8
9
10
11
12
13
resource "cloudflare_record" "helm108_com" {
domain = "helm108.com"
name = "helm108.com"
value = "${digitalocean_floating_ip.helm108.ip_address}"
type = "A"
}
resource "cloudflare_record" "www_helm108_com" {
domain = "helm108.com"
name = "www.helm108.com"
value = "${digitalocean_floating_ip.helm108.ip_address}"
type = "A"
}
...

Done

So that’s a slightly higher-level than I’d have liked write-up of a bunch of work I finished a year and a half ago. My server has been surprisingly stable; I’m running a handful of projects on a 512mb Digital Ocean droplet and because only I use them that’s been more than enough resource-wise.

Switching to a system that is built entirely by code means that I have no qualms with destroying and rebuilding my server as I don’t ever shell in and edit things on the actual box. Instead I update the relevant config files and destroy and rebuild the whole thing. Another benefit is that if I ever found that the droplet that I’ve chosen is struggling resource-wise all it would take to bump the box up a level is to edit the Terraform config and re-build the server.

There are a lot of improvements that I could make to this but as everything has been chugging along happily for over a year I don’t feel compelled to. This is partially because I add new projects so infrequently that nothing bothers me enough to try to improve anything. Should I find myself becoming more productive that I might change, but until then I’m pretty happy with/not bothered enough by how everything works.