Tutorials How to get started with Terraform

How to get started with Terraform

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.

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.13.4_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.13.4

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.15.3.linux-amd64.tar.gz

Extract the package to the /usr/local directory.

sudo tar -C /usr/local -xzf go1.15.3.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

We recommend you to create a new workspace member for API access. Find out more about how to do this at our API tutorial.

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.15.3 linux/amd64

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

git --version
git version 2.25.1

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

sudo apt install git

You will also need the make utility program to build the local registry for the UpCloud Terraform module. Check that you have it installed.

make -v
GNU Make 4.2.1
...

Or install it if missing.

sudo apt install make

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

With the release of Terraform 0.13.0, the discovery of a locally built provider binaries changed. These changes have been made to allow all providers to be discovered from public and provider registries.

The UpCloud includes a new target to build the provider binary into the right location for discovery. To do so, go to the UpCloud Terraform provider source directory.

cd $GOPATH/src/github.com/UpCloudLtd/terraform-provider-upcloud

Then use the following make command to allow you to run Terraform with the provider locally.

make build_0_13
==> Checking that code complies with gofmt requirements...
go build -o ~/.terraform.d/plugins/registry.upcloud.com/upcloud/upcloud/0.1.0/linux_amd64/terraform-provider-upcloud_v0.1.0

The build process also creates a directory for the plugins in your home folder at $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 alphabetical order. This is important to remember when configuring resources that might be dependent on one another.

To start with using the UpCloud Terraform module requires you to declare the following configuration block. A standard name for a file with the following HCL is version.tf. Create the file with the below command then open it for edit with your preferred text editor.

touch version.tf

Next, add the following to the file.

terraform {
  required_providers {
    upcloud = {
      source = "registry.upcloud.com/upcloud/upcloud"
    }
  }
  required_version = ">= 0.13"
}

Afterwards, save the file and exit the editor. With the required modules in place, you can get started.

Now, to begin a new configuration, 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. You should see something like the example output below.

Initializing the backend...

Initializing provider plugins...
- Finding latest version of registry.upcloud.com/upcloud/upcloud...
- Installing registry.upcloud.com/upcloud/upcloud v0.1.0...
- Installed registry.upcloud.com/upcloud/upcloud v0.1.0 (unauthenticated)

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, we recommend adding version constraints in a required_providers block
in your configuration, with the constraint strings suggested below.

* registry.upcloud.com/upcloud/upcloud: version = "~> 0.1.0"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Then, create a new 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 GB
  plan = "1xCPU-1GB"

  storage_devices {
    # System storage device size
    size = 25

    # Template UUID for Ubuntu 20.04
    storage = "01000000-0000-4000-8000-000030200200"

    # Storage device typeC
    tier   = "maxiops"
    action = "clone"
  }

  # Network interfaces
  network_interface {
    type = "public"
  }

  network_interface {
    type = "utility"
  }

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

  # Configuring connection details
  connection {
    # The server public IP address
    host        = self.network_interface[0].ip_address
    type        = "ssh"
    user        = "root"
    private_key = file("~/.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.

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.
------------------------------------------------------------------------
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 will be created
  + resource "upcloud_server" "server1" {
      + cpu                  = (known after apply)
      + hostname             = "terraform.example.com"
      + id                   = (known after apply)
      + mem                  = (known after apply)
      + plan                 = "1xCPU-1GB"
      + title                = (known after apply)
      + zone                 = "nl-ams1"

      + login {
          + create_password   = true
          + keys              = [
              + "ssh-rsa public key",
            ]
          + password_delivery = "email"
          + user              = "root"
        }

      + network_interface {
          + bootable            = (known after apply)
          + ip_address          = (known after apply)
          + ip_address_family   = "IPv4"
          + ip_address_floating = (known after apply)
          + mac_address         = (known after apply)
          + network             = (known after apply)
          + source_ip_filtering = (known after apply)
          + type                = "public"
        }
      + network_interface {
          + bootable            = (known after apply)
          + ip_address          = (known after apply)
          + ip_address_family   = "IPv4"
          + ip_address_floating = (known after apply)
          + mac_address         = (known after apply)
          + network             = (known after apply)
          + source_ip_filtering = (known after apply)
          + type                = "utility"

      + storage_devices {
          + action  = "clone"
          + address = (known after apply)
          + id      = (known after apply)
          + size    = 25
          + storage = "01000000-0000-4000-8000-000030200200"
          + tier    = "maxiops"
          + title   = (known after apply)
          + type    = "disk"
        }
    }

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...
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.45.99
upcloud_server.server1 (remote-exec):   User: root
upcloud_server.server1 (remote-exec):   Password: true
upcloud_server.server1 (remote-exec):   Private key: true
upcloud_server.server1 (remote-exec):   Certificate: false
upcloud_server.server1 (remote-exec):   SSH Agent: true
upcloud_server.server1 (remote-exec):   Checking Host Key: false
upcloud_server.server1: Still creating... [50s elapsed]
upcloud_server.server1 (remote-exec): Connected!
upcloud_server.server1 (remote-exec): Hello world!
upcloud_server.server1: Creation complete after 50s [id=00bafe39-800b-45a4-83f1-460aff563cf5]

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 are 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.

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=00bafe39-800b-45a4-83f1-460aff563cf5]
------------------------------------------------------------------------
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 will be updated in-place
  ~ resource "upcloud_server" "server1" {
        cpu                  = 1
        hostname             = "terraform.example.com"
        id                   = "00bafe39-800b-45a4-83f1-460aff563cf5"
        mem                  = 1024
      ~ plan                 = "1xCPU-1GB" -> "1xCPU-2GB"
        title                = "terraform.example.com (managed by terraform)"
        zone                 = "nl-ams1"
...

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

terraform apply
upcloud_server.server1: Refreshing state... [id=00bafe39-800b-45a4-83f1-460aff563cf5]

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 will be updated in-place
  ~ resource "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=00bafe39-800b-45a4-83f1-460aff563cf5]
upcloud_server.server1: Still modifying... [id=00bafe39-800b-45a4-83f1-460aff563cf5, 10s elapsed]
upcloud_server.server1: Still modifying... [id=00bafe39-800b-45a4-83f1-460aff563cf5, 20s elapsed]
upcloud_server.server1: Still modifying... [id=00bafe39-800b-45a4-83f1-460aff563cf5, 30s elapsed]
upcloud_server.server1: Modifications complete after 40s [id=00bafe39-800b-45a4-83f1-460aff563cf5]

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
upcloud_server.server1: Refreshing state... [id=00bafe39-800b-45a4-83f1-460aff563cf5]

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

Terraform will perform the following actions:

  # upcloud_server.server1 will be destroyed
  - resource "upcloud_server" "server1" {
      - cpu                  = 1 -> null
      ...
    }

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

upcloud_server.server1: Destroying... [id=00bafe39-800b-45a4-83f1-460aff563cf5]
upcloud_server.server1: Still destroying... [id=00bafe39-800b-45a4-83f1-460aff563cf5, 10s elapsed]
upcloud_server.server1: Destruction complete after 19s

Destroy complete! Resources: 1 destroyed.

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.

Editor-in-chief and Technical writer at UpCloud since 2015. Cloud enthusiast writing about server technology and software.

7 thoughts on “How to get started with Terraform

  1. I spent quite a while trying to figure out why I kept getting error saying “An argument named “storage_devices” is not expected here.” while using the example above. It turns out syntax in the example “storage_devices = [{ … }]” does not seem to work, but it should be “storage_devices { … }” to add one block device.

    1. Hi Sami, thanks for the comment and apologies for the headache. Terraform’s latest major upgrade to 0.12 made some changes to how the configuration is defined. We’ve updated the tutorial to reflect the changes.

  2. where can I find the full documentation? I see the “firewall”, “backup” are mentioned in the source code, but they’re not mentioned here.

    1. Hi there, thanks for the question. We are still working on providing more extensive documentation, but for the moment, you can find examples of firewall and backup usage at our GitHub repository.

  3. When using the first example I get “Required attribute is not set”. Looking at the example in the github repo instead I figured out that “network_interface” needs to be added.

    The connection block also gets a error that ipv4_address isn’t a supported attribute.

    1. Hi Claes, thanks for the comment. We’ve been updating our Terraform module to support their version 0.13 which has brought a few changes on how the resources are defined. Unfortunately, the tutorial is a little behind on the update but we are working on that so stay tuned for a better explanation. In the meanwhile, as you noticed, the network interfaces now need to be declared in the resource block which also changes how the IP address variables are called. The following example shows how to use these with the latest version:

        network_interface {
          type = "public"
        }
        connection {
          host        = self.network_interface[0].ip_address
          type        = "ssh"
          user        = "root"
          private_key = file(~/.ssh/id_rsa)
        }
    2. The tutorial has now been updated for the latest versions of each component. Should be clear sailing ahead!

Leave a Reply

Your email address will not be published. Required fields are marked *

Locations

Helsinki (HQ)

In the capital city of Finland, you will find our headquarters, and our first data centre. This is where we handle most of our development and innovation.

London

London was our second office to open, and a important step in introducing UpCloud to the world. Here our amazing staff can help you with both sales and support, in addition to host tons of interesting meetups.

Singapore

Singapore was our 3rd office to be opened, and enjoys one of most engaged and fastest growing user bases we have ever seen.

Seattle

Seattle is our 4th and latest office to be opened, and our way to reach out across the pond to our many users in the Americas.