top of page
  • Writer's pictureEmily T. Burak

Ubuntu with Docker Terraform Provisioning on DigitalOcean





I've had the need recently to run Dockerized scripts 24/7 cheaply, for the purpose of running Discord bots. After some research, DigitalOcean looked like a good fit for this use case. Not content to use the UI and customize a server as an infra geek, I set about provisioning Ubuntu with Docker via. Terraform, my favorite Infrastructure As Code tool. Using IaC has several benefits, such as removing the potential for manual error should the server need to be recreated, or allowing the provisioning of multiple instances without manually modifying them all. After looking through documentation and tutorials, I've created a repo HERE to create a DigitalOcean droplet running Ubuntu 18.04 and installing Docker as well as implementing some DO best practices for server provisioning through cloud-init. The repo consists primarily of two TF files, main.tf and outputs.tf, as well as the secret sauce, a cloud-init-data.yaml file that runs on startup of the machine. The main.tf file sets the provider, creates an SSH key resource in DigitalOcean to SSH into the server, then provisions a Ubuntu 18.04 droplet in the NYC1 region using that SSH key and the cloud-init-data.yaml file as user data. Oh yeah, monitoring is enabled too, because it's free on DigitalOcean, and why not?


main.tf


terraform {
  required_version = ">= 1.0.0"

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

provider "digitalocean" {}

# Create a new SSH key
resource "digitalocean_ssh_key" "tfsshkey" {
  name       = "Tf Test"
  public_key = file("./tf-digitalocean.pub")
}

# Create droplet using SSH key and cloud-config data
resource "digitalocean_droplet" "tfdroplet" {
  image     = "ubuntu-18-04-x64"
  name      = "test-tf-droplet"
  region    = "nyc1"
  size      = "s-1vcpu-1gb"
  user_data = file("cloud-init-data.yaml")
  ssh_keys  = [digitalocean_ssh_key.tfsshkey.fingerprint]
  monitoring = true
}




The cloud-init-data.yaml file is a bit more complex, so I'll dig deeper into it.


cloud-init-data.yaml


#cloud-config

# Add non-default user with password sudoless and ssh key
users:
  - default
  - name: user
    shell: /bin/bash
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: users, admin
    ssh_authorized_keys:
      #- Your SSH public key(tf-digitalocean.pub) here!

# Modify sshhd_config to disallow root login, restart ssh, install and run Docker
runcmd:
  - sed -i -e '/^PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config
  - sed -i -e '$aAllowUsers user' /etc/ssh/sshd_config
  - sudo systemctl restart ssh.service
  - sudo ufw allow OpenSSH
  - sudo ufw enable
  - sudo apt update && sudo apt upgrade -y
  - sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
  - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
  - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
  - sudo apt update
  - sudo apt install -y docker-ce
  - sudo systemctl start docker

Firstly, the user data file creates a non-default user with sudo access (as we'll be disabling root login after that, to jump ahead a bit), disabling password access for security reasons and instead using an SSH key that the user will have to provision.


Then, we do the aforementioned disabling of root login, allow the user to SSH in, restart sshd to get that to stick, and set up a firewall with UFW(uncomplicated firewall) that allows in OpenSSH and enable that. Finally, we install and run Docker based on the documentation for Ubuntu installation. and run it.


Note that the user data script is run after setting up the instance, and so it'll take a moment to run. One thing that got hammered into me that I can forget when writing this was the importance of the -y flag to bypass user input in scripting. It's a maxim to do the Thing manually first, then translate it to a script, and that translation includes steps like removing the necessity of user input that can be missed if you're slamming out a script.


Outputs.tf is pretty simple in comparison, just outputting the public IP of the droplet instance for SSHing in so you can minimize using the console:


outputs.tf


output "ip_address" {
  value       = digitalocean_droplet.tfdroplet.ipv4_address
  description = "The public IP address of your droplet."
}

On SSH Key Generation:


You'll want to, before running the Terraform commands, generate an SSH key named "tf-digitalocean"(or change the keyname in the TF, I suppose):


ssh-keygen -t rsa -C "your-email-here" -f ./tf-digitalocean

Is what I personally used, but if you don't have or can't get ssh-keygen, you can look into some of the alternatives out there. Then insert the public SSH key (tf-digitalocean.pub) into the ssh_authorized_keys fled in cloud-init-data.yaml. This will allow you to set up access to actually get into the droplet.


How to run:


Simple, the usual Terraform workflow:

1. cd into the appropriate folder

2. terraform init

3. terraform fmt (I like this one even if it's just prettying things up)

4. terraform validate

5. terraform plan and make sure it looks right

6. terraform apply , follow the prompts, and off you go to setting up your droplet, and ssh in!


Next steps and takeaways:

There are some steps I'd like to take with this little project in the future should I have time and capacity, namely automating the provisioning of Docker with Ansible, moving to Ubuntu 20.04 with the April 2023 end of support of 18.04 for Ubuntu, automating a better process for SSH keygen/handling, and adding more security through a firewall and figuring out DO VPCs in Terraform. I'd also use the CLI where possible for DO, though this setup requires very little to be done in the UI as-is.


Please note as some takeaways that I found DO pretty easy to work with, including the TF provider, after some poking around. Cloud-init was really the stumbling block, it can be hard to debug and the cloud-config docs are particularly unhelpful.


References


Finally, as usual, leave comments here or contact me on LinkedIn (I have a Twitter you can find by poking around, but rarely use it besides to share gripes and blog posts) if you have any questions or ways to improve this article/repo. Thanks for reading and keep on swimming through exploring DigitalOcean and Terraform!



34 views0 comments

Recent Posts

See All
bottom of page