Photo by Emile Perron on Unsplash

tl;dr

variable do_api_token {}

provider digitalocean {
  token = var.do_api_token
}

data digitalocean_ssh_key ssh_key {
  name = "mysshkey"
}

resource digitalocean_droplet ghost { 
  image = "ghost-18-04"
  name = "prod-ghost-blog-nyc1"
  region = "nyc1"
  size = "s-1vcpu-1gb"
  ipv6 = true
  monitoring = true
  ssh_keys = [data.digitalocean_ssh_key.ssh_key.id]

  tags = ["prod"]
}

resource digitalocean_firewall web {
  name = "prod-web"

  droplet_ids = [digitalocean_droplet.ghost.id]

  inbound_rule {
    protocol         = "tcp"
    port_range       = "22"
    source_addresses = ["255.255.255.255/32"]
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "80"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "443"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol              = "tcp"
    port_range            = "1-65535"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol              = "udp"
    port_range            = "1-65535"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }
}

output droplet_ip_address {
  value = digitalocean_droplet.ghost.ipv4_address
}

Cost

$5 per month to pay for the smallest Droplet available.

Prerequisites

  1. Terraform v. 0.12+
  2. A DigitalOcean account
  3. A DigitalOcean API token
  4. An SSH key is recommended but not strictly required. Follow the documentation here for creating the key pair on your machine and adding the public key to DigitalOcean. Follow Add Keys to a DO Account for this exercise.

Provisioning

To get started we need to setup the Terraform DigitalOcean provider.

variable do_api_token {}

provider digitalocean {
  token = var.do_api_token
}

We shouldn't be putting API tokens into our repositories so we'll leave it as a variable that can get filled in at runtime. Run export TF_VAR_do_api_token=<yourApiToken> to make it available to Terraform.

Now run terraform init to download the DigitalOcean provider.

SSH Key

Earlier we created an SSH key and uploaded it to DigitalOcean. We'll now pull that key into Terraform using a Data Source.

data digitalocean_ssh_key ssh_key {
  name = "mysshkey"
}

If you forgot your SSH key name and want a quick way to get it, you can run the following `curl`.

curl -X GET \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TF_VAR_do_api_token" \
  "https://api.digitalocean.com/v2/account/keys" 

Droplet

To create the Droplet, we'll use the digitalocean_droplet resource. This resource has a lot of possible attributes all documented here but we'll only use a few of them.

resource digitalocean_droplet ghost { 
  image = "ghost-18-04"
  name = "prod-ghost-blog-nyc1"
  region = "nyc1"
  size = "s-1vcpu-1gb"
  ipv6 = true
  monitoring = true
  ssh_keys = [data.digitalocean_ssh_key.jaredready.id]

  tags = ["prod"]
}

Firewall

It's not wise to leave ports on a server open to the internet unless necessary. The only necessary ports for the public internet to have access to are 80 and 443 for HTTP and HTTPS, respectively. We'll also open up port 22 for SSH but restrict it to just your IP address.

To see your public IP address run curl ifconfig.me.

resource digitalocean_firewall web {
  name = "prod-web"

  droplet_ids = [digitalocean_droplet.ghost.id]

  inbound_rule {
    protocol         = "tcp"
    port_range       = "22"
    # Replace with your IP address
    source_addresses = ["255.255.255.255/32"]
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "80"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "443"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol              = "tcp"
    port_range            = "1-65535"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol              = "udp"
    port_range            = "1-65535"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }
}

This piece of the config is longer but it should be pretty simple to understand.

The inbound_rules determine what kind of traffic can initiate connections with the Droplet. In this case we're allowing TCP ports 22, 80, and 443 from sources 0.0.0.0/0 and ::/0.

The outbound_rules determine what kind of outbound traffic the Droplet is allowed to initiate, and here we're allowing it to initiate all outbound traffic.

Note that DigitalOcean Firewalls are stateful which means that allowed inbound or outbound traffic will have its appropriate response traffic allowed regardless of the rules.

Output

We need one output which will give us the public IP address of the Droplet.

output droplet_ip_address {
  value = digitalocean_droplet.ghost.ipv4_address
}

Apply

At this point you should be able to run terraform apply to deploy the Droplet. You should see output that looks similar to this.

data.digitalocean_ssh_key.ssh_key: Refreshing state...

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # digitalocean_droplet.ghost will be created
    + resource "digitalocean_droplet" "ghost" {
      ... redacted for space
    }

  # digitalocean_firewall.web will be created
  + resource "digitalocean_firewall" "web" {
      ... redacted for space
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions in workspace "test"?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.
  
  Enter a value: 

You can safely type yes to continue if you see this. In a few seconds the Droplet and the Firewall should be provisioned and you'll see your new Droplet's IP address.

Outputs:

droplet_ip_address = 255.255.255.255

Connection

You can now connect to your Droplet.

ssh root@255.255.255.255

If you saved your SSH key pair somewhere other than the default, you'll need to run this instead.

ssh -i /path/to/keypair/id_rsa root@255.255.255.255

You can find the repository for all of this here.