Terraform is a simple yet powerful open source infrastructure management tool developed by HashiCorp. It allows you to safely and predictably manage your infrastructure by codifying APIs into declarative configuration files.

Terraform logo

In this guide, we will show you how to install the required software and get started with Terraform on UpCloud. Below you can find the instructions suitable for most Linux distributions but Terraform is also available for download on macOS and Windows.

Contents

Installing Terraform

Terraform works as a command line utility that communicates with the supported services via APIs. Installing Terraform on your computer provides you with all the tools you need to manage your infrastructure in the cloud.

Start by getting Terraform from their download page.

Select and download the appropriate package for your system.

Extract the binaries to a suitable location, such as /usr/local/bin and make sure it is included in your PATH environmental variable. For example with the command below.

sudo unzip terraform_0.11.8_linux_amd64.zip -d /usr/local/bin

Test that Terraform is accessible by checking for the version number in a terminal with the command underneath.

terraform -v
Terraform v0.11.8

That is it for Terraform itself. Next, continue below with the instructions for installing the prerequisites and the UpCloud provider plugin for Terraform.

Installing prerequisites

Our plugin was begun by community developers to integrate UpCloud as a cloud provider on Terraform. The now officially supported provider plugin is available on GitHub. Downloading and compiling the plugin from source requires the Go language and Git packages.

Start by downloading Go with the next command.

curl -O https://storage.googleapis.com/golang/go1.11.linux-amd64.tar.gz

Extract the package to the /urs/local directory.

sudo tar -C /usr/local -xzf go1.11.linux-amd64.tar.gz

Next, create a folder for the Go language files, for example, in your home directory with the following command.

mkdir -p ~/go/bin

To use the Go command line utility, you need to set up a few environmental variables. Add the following to your profile, or adjust the paths depending on where you created the Go directory.

echo 'export GOPATH=$HOME/go' | tee -a ~/.bashrc
echo 'export GOBIN=$GOPATH/bin' | tee -a ~/.bashrc
echo 'export PATH=$PATH:/usr/local/go/bin:$GOBIN' | tee -a ~/.bashrc

Deploying servers to your UpCloud account requires you to have your username and password safely stored in your environmental variables as well. Use the commands below to include an account name and password in your profile. Replace the username and password with your UpCloud account username and password.

echo 'export UPCLOUD_USERNAME=username' | tee -a ~/.bashrc
echo 'export UPCLOUD_PASSWORD=password' | tee -a ~/.bashrc

Also, check that your account has API access enabled for your username under My account and User accounts.

Then reload the profile to apply the new additions.

source ~/.bashrc

You should now test that you are able to use the Go commands, for example, by check the installed version number.

go version
go version go1.11 linux/amd64

With Go configured and working, check that you also have Git installed.

git --version
git version 2.17.1

Install it, if missing, for example on Ubuntu or Debian with the following command.

sudo apt install git

Great! The pre-requirements are then all set up, continue on with the install of the UpCloud plugin for Terraform.

Installing UpCloud provider plugin from source

First, download the plugin from the GitHub using Go with the command below.

go get github.com/UpCloudLtd/terraform-provider-upcloud

Next, compile the plugin with the following install command.

go install github.com/UpCloudLtd/terraform-provider-upcloud

Then create a directory for the plugins in your home folder.

mkdir -p $HOME/.terraform.d/plugins

Finally, create a symbolic link from the compiled binary in the Go path to the plugins folder with the next command.

ln -s $GOPATH/bin/terraform-provider-upcloud $HOME/.terraform.d/plugins/terraform-provider-upcloud

Alternatively, you can also simply copy the provider plugin to the Terraform plugins folder. However, using a symbolic link will allow easy updates using just the same Go get and install commands as instructed above in the future.

The Terraform installation for UpCloud is then all set. You are now ready to start planning your first Terraform deployment. Continue on with the rest of the guide to learn how to create and deploy Terraform plans.

Planning infrastructure with Terraform

Defining infrastructure as code brings many advantages such as simple editing, reviewing, and versioning, as well as easy sharing amongst team members.

Create a new directory for your Terraform project and change into it.

mkdir -p ~/terraform/base && cd ~/terraform/base

Each Terraform project is organised in their own directories. When invoking any command that loads the Terraform configuration, Terraform loads all configuration files within the working directory in an alphabetical order. This can be important to note when configuring resources that might be dependent on one another.

Now to start with, every Terraform project directory needs to be initialised, do this by running the command below.

terraform init

Terraform sets up the directory to support deploying plans.

Then, create a build plan named for example server1.tf in your Terraform directory.

touch server1.tf

Open the file in your favourite text editor then include a provider segment for UpCloud and any number of resources as described in the example plan below.

Replace the ssh-rsa public key in the login segment with your public SSH key and path to your private SSH key in the connection settings.

provider "upcloud" {
    # Your UpCloud credentials are read from the environment variables
    # export UPCLOUD_USERNAME="Username for Upcloud API user"
    # export UPCLOUD_PASSWORD="Password for Upcloud API user"
}

resource "upcloud_server" "server1" {
    # System hostname
    hostname = "terraform.example.com"

    # Availability zone
    zone = "nl-ams1"
    
    # Number of CPUs and memory in MB
    plan = "1xCPU-1GB"
    
    storage_devices = [
        {
            # OS root disk size
            size = 25
            action = "clone"

            # Template UUID for Ubuntu 18.04
            storage = "01000000-0000-4000-8000-000030080200"
            tier = "maxiops"
        }
    ]

    # Include at least one public SSH key
    login {
        user = "root"
        keys = [
            "ssh-rsa public key"
        ]
    }

    # Configuring connection details
    connection {
        host = "${upcloud_server.server1.ipv4_address}"
        type = "ssh"
        user = "root"
        private_key = "${file("/home/user/.ssh/rsa_private_key")}"
    }

    # Remotely executing a command on the server
    provisioner "remote-exec" {
        inline = [
            "echo 'Hello world!'"
        ]
    }
}

Once done, just save the file and you are good to go.

If you don’t have an SSH key at hand, check out our quick guide about using SSH keys for authentication to generate a key pair for Terraform.

Deploying your configuration

Once you’ve defined your infrastructure plan, next you might want to deploy it. Terraform provides easy to use commands for safely and predictably deploying resources and applying changes to them.

First, verify your build plan with the following command.

terraform plan

This generates an execution plan that shows what actions will be taken when the plan is applied. It includes the server configuration, login details, storage settings, and the deployment zone as seen in the example underneath.

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:

  + upcloud_server.server1
      id:                                 <computed>
      cpu:                                <computed>
      hostname:                           "terraform.example.com"
      ipv4:                               "true"
      ipv4_address:                       <computed>
      ipv4_address_private:               <computed>
      ipv6:                               "true"
      ipv6_address:                       <computed>
      login.#:                            "1"
      login.2874636856.create_password:   "false"
      login.2874636856.keys.#:            "1"
      login.2874636856.keys.0:            "ssh-rsa xxx"
      login.2874636856.password_delivery: "none"
      login.2874636856.user:              "root"
      mem:                                <computed>
      plan:                               "1xCPU-1GB"
      private_networking:                 "true"
      storage_devices.#:                  "1"
      storage_devices.0.action:           "clone"
      storage_devices.0.address:          <computed>
      storage_devices.0.id:               <computed>
      storage_devices.0.size:             "25"
      storage_devices.0.storage:          "01000000-0000-4000-8000-000030080200"
      storage_devices.0.tier:             "maxiops"
      storage_devices.0.title:            <computed>
      title:                              <computed>
      zone:                               "nl-ams1"

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

The values shown as <computed> are assigned at deployment and cannot be determined ahead of time. They can be queried once the server is online.

Next, deploy the configuration by executing the plan with the command below.

terraform apply

Reply yes when asked to confirm the deployment. Example output is shown below.

upcloud_server.server1: Creating...
  cpu:                                "" => <computed>
  hostname:                           "" => "terraform.example.com"
  ...
  zone:                               "" => "nl-ams1"
upcloud_server.server1: Still creating... (10s elapsed)
upcloud_server.server1: Still creating... (20s elapsed)
upcloud_server.server1: Still creating... (30s elapsed)
upcloud_server.server1: Still creating... (40s elapsed)
upcloud_server.server1: Provisioning with 'remote-exec'...
upcloud_server.server1 (remote-exec): Connecting to remote host via SSH...
upcloud_server.server1 (remote-exec):   Host: 94.237.44.147
upcloud_server.server1 (remote-exec):   User: root
upcloud_server.server1 (remote-exec):   Password: false
upcloud_server.server1 (remote-exec):   Private key: true
upcloud_server.server1 (remote-exec):   SSH Agent: true
upcloud_server.server1 (remote-exec):   Checking Host Key: false
upcloud_server.server1 (remote-exec): Connected!
upcloud_server.server1 (remote-exec): Hello world!
upcloud_server.server1: Creation complete after 48s (ID: 002366a2-0d47-43c2-aea0-8e9c16c50a2d)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

You’ll notice that much of the output from the apply command looks the same as planning so some of the above is truncated for brevity. Terraform performs syntax verifications again in an effort to spare you from deployment errors that could take precious time to roll back.

Separating plans and applies reduces mistakes and uncertainty at scale. Plans show operators what would happen upon apply command to execute changes.

Managing resources

When you need to make changes to your infrastructure, simply update the configuration file and apply the changes. As the configurations change, Terraform determines what is different and creates an incremental execution plan to perform the updates.

Open your Terraform plan in an editor and find the server configuration plan. Change the plan to increase the resources allocated to your server. You can see the available preconfigured plans at your UpCloud control panel.

resource "upcloud_server" "server1" {
    # System hostname
    hostname = "terraform.example.com"

    # Availability zone
    zone = "nl-ams1"
    
    # Number of CPUs and memory in MB
    plan = "1xCPU-2GB"
...
}

Save the file with the changes, then verify your build plan again.

terraform plan

Example output below.

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

upcloud_server.server1: Refreshing state... (ID: 002366a2-0d47-43c2-aea0-8e9c16c50a2d)

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  ~ upcloud_server.server1
      plan: "1xCPU-1GB" => "1xCPU-2GB"


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

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

Finally, apply the change to see the results. Reply yes again to confirm when requested.

terraform apply
upcloud_server.server1: Refreshing state... (ID: 002366a2-0d47-43c2-aea0-8e9c16c50a2d)

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  ~ upcloud_server.server1
      plan: "1xCPU-1GB" => "1xCPU-2GB"

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

upcloud_server.server1: Modifying... (ID: 002366a2-0d47-43c2-aea0-8e9c16c50a2d)
  plan: "1xCPU-1GB" => "1xCPU-2GB"
upcloud_server.server1: Still modifying... (ID: 002366a2-0d47-43c2-aea0-8e9c16c50a2d, 10s elapsed)
upcloud_server.server1: Still modifying... (ID: 002366a2-0d47-43c2-aea0-8e9c16c50a2d, 20s elapsed)
upcloud_server.server1: Still modifying... (ID: 002366a2-0d47-43c2-aea0-8e9c16c50a2d, 30s elapsed)
upcloud_server.server1: Still modifying... (ID: 002366a2-0d47-43c2-aea0-8e9c16c50a2d, 40s elapsed)
upcloud_server.server1: Still modifying... (ID: 002366a2-0d47-43c2-aea0-8e9c16c50a2d, 50s elapsed)
upcloud_server.server1: Modifications complete after 57s (ID: 002366a2-0d47-43c2-aea0-8e9c16c50a2d)

You will see Terraform modify the server resources according to the differences between the server’s current state and the new plan.

The same way you could decrease the resources allocated to your cloud server by changing the plan back to 1xCPU-1GB. However, note that this does not automatically resize the disk. As while increasing the disk is simple, decreasing storage is not quite straightforward. We recommend keeping your storage small if you wish to vertically scale the server and retain the preconfigured pricing.

When you are done with the test server, it can be deleted using the command underneath.

terraform destroy

Check that the action about to be taken is correct and confirm the command by entering yes just as with previous apply commands.

Summary

Great job completing this guide! You should now have some resources and the basic knowledge to start building upon. This is but an introduction to Terraform which many advanced features. Check out the Terraform documentation to learn more.