Getting started with Terraform

Terraform is a program that can be used to build your cloud-based infrastructure based off of configuration files that you write. It’s a part of what is referred to as “Infrastructure as code (Iac)”.

Instead of going into various cloud provider UI dashboards and clicking around to build your resources, Terraform can do all that provisioning for you. It uses the cloud provider APIs behind the scenes — you just write exactly the infrastructure that you want to end up with at the end.

In this guide, we will provision a simple Digital Ocean Server (a Droplet in Digital Ocean parlance) using Terraform from our local terminal.

If you don’t yet have a Digital Ocean account, feel free to use my referral link to set one up. With that link you’ll get $200 in credit to use over 60 days.

Setting up Terraform in 4 steps

1 :: Install terraform

Terraform is available to install from pretty much all package repositories out there.

Installing it should be as simple as running a one-line command in your terminal.

2 :: Configure any required cloud provider API tokens

In order to let the Terraform program make changes to your cloud provider account, you will need to set up API tokens and tell Terraform where to find them.

In this post I’ll only be setting up a single one for Digital Ocean.

3 :: Write your main.tf configuration file

A single main.tf file will be enough to get you something working.

Add all of your needed resources / infrastructure in it.

4 :: Run the apply command

By running the terraform apply command against your main.tf file, you can turn your empty cloud infrastructure into a working setup.

Step 1 :: Install Terraform

Terraform’s documentation details the numerous ways of getting it installed across operating systems.

I use Arch Linux and so install it like so:

Bash
sudo pacman -Sy terraform

You can check it is installed and discoverable on your system by checking the version you have installed:

Bash
terraform -v

# My Output
Terraform v1.6.4
on linux_amd64

Now create an empty directory, which will be your “terraform project”. It doesn’t matter what you call the folder.

Then inside that file create a file called main.tf. We’ll come back to this file a little later.

Step 2 :: Configure any required cloud provider API tokens

Head to your Digital Ocean API Tokens dashboard and click “Generate New Token”. Give it a name, choose an expiry and make sure you click the “write” permission option. Click “generate token”.

There are a number of ways we can tell Terraform what our Digital Ocean API Token is:

  • Obviously we could hard code it for the purposes of just getting it running while learning, though I wouldn’t recommend this approach even in testing.
  • Another is to use Terraform-specific environment variables set on your system. This has been my approach in the past. However, I came to realize how unsafe this was as every program you install has the potential to read from your environment variable.
  • A third way is to pass it as a parameter when calling the apply command.

I will be opting for that third option, but I don’t want to have that token saved in my history or have to pass it in everytime I want to run a Terraform command.

So my solution is to write a small wrapper bash script that will read the contents of a file in my home directory (with my token in) and pass it as an argument to the Terraform apply command.

Creating a wrapper bash script to safely pass secret token to command

Create a file in your home directory called “terraform-test”. You can call it anything, just remember to reference it correctly when using it later in the guide.

Inside that file, paste only the API token that you got from your Digital Ocean API dashboard. Then save the file and close it.

Open a new file in the root of your Terraform project and add the following contents:

Bash
#!/usr/bin/bash

terraform "$@" -var "do_token=$(cat ~/terraform-test)"

Save that file as “myterraformwrapper”. (You can call it whatever you want, I use “myterraformwrapper” as an example).

Also make sure to give it executable permissions by running the following command against it:

Bash
chmod o+x myterraformwrapper

The myterraformwrapper script does the following:

  1. Calls the terraform command.
  2. Any arguments you pass to myterraformwrapper get put in the place of "$@"
  3. Appends on to the command, the -var flag and sets the do_token parameter to the contents of the terraform-test file you created previously.

This means:

Bash
./myterraformwrapper apply

… behind the scenes, becomes…

Bash
terraform apply -var "do_token=CONTENTS_OF_YOUR_DO_TOKEN"

This means that you are not having to keep passing your Digital Ocean token in for every command, and you wont end up accidentally leaking the token inside your shell’s env variables.

We will use that file later in this guide.

Step 3 :: Write your main.tf configuration file

For this example, everything will be kept in a single file called main.tf. When you start working on bigger infrastructure plans, there is nothing stopping you from splitting out your configuration into multiple, single-purpose files.

YAML
terraform {
    required_providers {
        digitalocean = {
            source = "digitalocean/digitalocean"
            version = "~> 2.0"
        }
    }
}

variable "do_token" {}

provider "digitalocean" {
  token = var.do_token
}

resource "digitalocean_droplet" "droplet" {
  image    = "ubuntu-22-04-x64"
  name     = "terraform-test"
  region   = "lon1"
  size     = "s-1vcpu-1gb"
}

terraform block

At the top of the file is the terraform block. This sets up the various providers that we want to work with for building out our infrastructure. In this example we only need the digital ocean one.

variable declarations

Variable declarations can be used to keep sensitive information out of out configuration — and thus source control later, as well as making our configuration more reusable.

Each of the variables that our configuration needs to run must be defined as a variable like above. You can define variables in a few different ways, but here I have opted for the simplest.

We can see that all our configuration needs is a do_token value passed to it.

provider setups

Each of the providers that we declare in our terraform block will probably need some kind of setup — such as an api token like our Digital Ocean example.

For us we can see that the setting up of Digital Ocean’s provider needs only a token, which we are passing it from the variable that we will pass in via the cli command.

resource declarations

We then declare the “resources” that we want Terraform to create for us in our Digital Ocean account. In this case we just want it to create a single small droplet as a proof of concept.

The values I have passed to the digitalocean_droplet resource, would be great examples of where to use variables, potentially even with default placeholder values.

I have hard coded the values here for brevity.

Step 4 :: Run the apply command

Before running apply for the first time, we first need to initialize the project:

Bash
terraform init
# You should see some feedback starting with this:
Terraform has been successfully initialized!

You can also run terraform plan before the apply command to see what Terraform will be provisioning for you. However, when running terraform apply, it shows you the plan and asks for explicit confirmation before building anything. So I rarely use plan.

If you run terraform apply, it will prompt you for any variables that your main.tf requires — in our case the do_token variable. We could type it / paste it in every time we want to run a command. But a more elegant solution would be to use that custom bash script we created earlier.

Assuming that bash script is in our current directory — the Terraform project folder — run the following:

Bash
./myterraformwrapper apply

This should display to you what it is planning to provision in your Digital Ocean account — a single Droplet.

Type the word “yes” and hit enter.

You should now see it giving you a status update every 10 seconds, ending in confirmation of the droplet being created.

If you hard back over to your Digital Ocean account dashboard, you should see that new droplet sitting there.

Step 5 :: Bonus: destroying resources.

Just as Terraform can be used to create those resources, it can also be used to destroy them too. It goes without saying that you should always be mindful of just what you are destroying, but in this example we are just playing with a test droplet.

Run the following to destroy your newly-created droplet:

Bash
./myterraformwrapper destroy

Again, it will first show you what it is planning to change in your account — the destruction of that single droplet.

Type “yes” and hit enter to accept.

Next Steps

I love playing with Terraform, and will be sharing anything that I learn along my journey on my website.

You could start working through Terraform’s documentation to get a taste of what it can do for you.

You can even take a look at its excellent registry to see all of the providers that are available. Maybe even dig deep into the Digital Ocean provider documentation and see all of the available resources you could play with.

Just be careful how much you are creating and when testing don’t forget to run the destroy command when you’re done. The whole point of storing your infrastructure as code is that it is dead simple to provision and destroy it all.

Just don’t get leaving test resources up and potentially running yourself a huge bill.

Setting up a Digital Ocean droplet for a Lupo website with Terraform

Overview of this guide

My Terraform Repository used in this guide

Terraform is a program that enables you to set up all of your cloud-based infrastructure with configuration files. This is opposed to the traditional way of logging into a cloud provider’s dashboard and manually clicking buttons and setting up things yourself.

This is known as “Infrastructure as Code”.

It can be intimidating to get started, but my aim with this guide is to get you to the point of being able to deploy a single server on Digital Ocean, along with some surrounding items like a DNS A record and an ssh key for remote access.

This guide assumes that you have a Digital Ocean account and that you also have your domain and nameservers setup to point to Digital Ocean.

You can then build upon those foundations and work on building out your own desired infrastructures.

The Terraform Flow

As a brief outline, here is what will happen when working with terraform, and will hopefully give you a broad picture from which I can fill in the blanks below.

  • Firstly we write a configuration file that defines the infrastructure that we want.
  • Then we need to set up any access tokens, ssh keys and terraform variables. Basically anything that our Terraform configuration needs to be able to complete its task.
  • Finally we run the terraform plan command to test our infrastructure configuration, and then terraform apply to make it all live.

Installing the Terraform program

Terraform has installation instructions, but you may be able to find it with your package manager.

Here I am installing it on Arch Linux, by the way, with pacman

Bash
sudo pacman -S terraform

Setting the required variables

The configuration file for the infrastructure I am using requires only a single variable from outside. That is the do_token.

This is created manually in the API section of the Digital Ocean dashboard. Create yours and keep its value to hand for usage later.

Terraform accepts variables in a number of ways. I opt to save my tokens in my local password manager, and then use them when prompted by the terraform command. This is slightly more long-winding than just setting a terraform-specific env in your bashrc. However, I recently learned off rwxrob how much of a bad idea that is.

Creating an ssh key

In the main.tf file, I could have set the ssh public key path to my existing one. However, I thought I’d create a key pair specific for my website deployment.

Bash
ssh-keygen -t rsa

I give it a different name so as to not override my standard id_rsa one. I call it id_rsa.davidpeachme just so I know which is my website server one at a glance.

Describing your desired infrastructure with code

Terraform uses a declaritive language, as opposed to imperetive.

What this means for you, is that you write configuration files that describe the state that you want your infrastructure to be in. For example if you want a single server, you just add the server spec in your configuration and Terraform will work out how best to create it for you.

You dont need to be concerned with the nitty gritty of how it is achieved.

I have a real-life example that will show you exactly what a minimal configuration can look like.

Clone / fork the repository for my website server.

Explaination of my terraform repository

YAML
terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

variable "do_token" {}

# Variables whose values are defined in ./terraform.tfvars
variable "domain_name" {}
variable "droplet_image" {}
variable "droplet_name" {}
variable "droplet_region" {}
variable "droplet_size" {}
variable "ssh_key_name" {}
variable "ssh_local_path" {}

provider "digitalocean" {
  token = var.do_token
}

The first block tells terraform which providers I want to use. Providers are essentially the third-party APIs that I am going to interact with.

Since I’m only creating a Digital Ocean droplet, and a couple of surrounding resources, I only need the digitalocean/digitalocean provider.

The second block above tells terraform that it should expect – and require – a single variable to be able to run. This is the Digital Ocean Access Token that was obtained above in the previous section, from the Digital Ocean dashboard.

Following that are the variables that I have defined myself in the ./terraform.tfvars file. That tfvars file would normally be kept out of a public repository. However, I kept it in so that you could hopefully just fork my repo and change those values for your own usage.

The bottom block is the setting up of the provider. Basically just passing the access token into the provider so that it can perform the necessary API calls it needs to.

YAML
resource "digitalocean_ssh_key" "ssh_key" {
  name       = var.ssh_key_name
  public_key = file(var.ssh_local_path)
}

Here is the first resource that I am telling terraform to create. Its taking a public key on my local filesystem and sending it to Digital Ocean.

This is needed for ssh access to the server once it is ready. However, it is added to the root account on the server.

I use Ansible for setting up the server with the required programs once Terraform has built it. So this ssh key is actually used by Ansible to gain access to do its thing.

I will have a separate guide soon on how I use ansible to set my server up ready to host my static website.

YAML
resource "digitalocean_droplet" "droplet" {
  image    = var.droplet_image
  name     = var.droplet_name
  region   = var.droplet_region
  size     = var.droplet_size
  ssh_keys = [digitalocean_ssh_key.ssh_key.fingerprint]
}

Here is the meat of the infrastructure – the droplet itself. I am telling it what operating system image I want to use; what size and region I want; and am telling it to make use of the ssh key I added in the previous block.

YAML
data "digitalocean_domain" "domain" {
  name = var.domain_name
}

This block is a little different. Here I am using the data property to grab information about something that already exists in my Digital Ocean account.

I have already set up my domain in Digital Ocean’s networking area.

This is the overarching domain itself – not the specific A record that will point to the server.

The reason i’m doing it this way, is because I have got mailbox settings and TXT records that are working, so i dont want them to be potentially torn down and re-created with the rest of my infrastructure if I ever run terraform destroy.

YAML
resource "digitalocean_record" "record" {
  domain = data.digitalocean_domain.domain.id
  type   = "A"
  name   = "@"
  ttl    = 60
  value  = "${digitalocean_droplet.droplet.ipv4_address}"
}

The final block creates the actual A record with my existing domain settings.

It uses the domain id given back by the data block i defined above, and the ip address of the created droplet for the A record value.

Testing and Running the config to create the infrastructure

If you now go into the root of your terraform project and run the following command, you should see it displays a write up of what it intends to create:

Bash
terraform plan

If the output looks okay to you, then type the following command and enter “yes” when it asks you:

Bash
terraform apply

This should create the three items of infrastructure we have defined.

Next Step

Next we need to set that server up with the required software needed to run a static html website.

I will be doing this with a program called Ansible.

I’ll be writing up those steps in a zet very soon.