diff --git a/.gitlab-ci/terraform.yml b/.gitlab-ci/terraform.yml
index bd5adb4d4cc9b3b867f0c14fb1427d431b2954db..7484efe2e29f2c44a15160b2b7ab5e98aec07a4e 100644
--- a/.gitlab-ci/terraform.yml
+++ b/.gitlab-ci/terraform.yml
@@ -108,6 +108,13 @@ tf-0.13.x-validate-vsphere:
     PROVIDER: vsphere
     CLUSTER: $CI_COMMIT_REF_NAME
 
+tf-0.13.x-validate-upcloud:
+  extends: .terraform_validate
+  variables:
+    TF_VERSION: 0.13.5
+    PROVIDER: upcloud
+    CLUSTER: $CI_COMMIT_REF_NAME
+
 tf-0.14.x-validate-openstack:
   extends: .terraform_validate
   variables:
@@ -142,6 +149,13 @@ tf-0.14.x-validate-vsphere:
     PROVIDER: vsphere
     CLUSTER: $CI_COMMIT_REF_NAME
 
+tf-0.14.x-validate-upcloud:
+  extends: .terraform_validate
+  variables:
+    TF_VERSION: 0.14.3
+    PROVIDER: upcloud
+    CLUSTER: $CI_COMMIT_REF_NAME
+
 # tf-packet-ubuntu16-default:
 #   extends: .terraform_apply
 #   variables:
diff --git a/contrib/terraform/upcloud/README.md b/contrib/terraform/upcloud/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..dcce236fed14458014f1d0c619e27fbb461f2ac6
--- /dev/null
+++ b/contrib/terraform/upcloud/README.md
@@ -0,0 +1,106 @@
+# Kubernetes on UpCloud with Terraform
+
+Provision a Kubernetes cluster on [UpCloud](https://upcloud.com/) using Terraform and Kubespray
+
+## Overview
+
+The setup looks like following
+
+```text
+   Kubernetes cluster
++-----------------------+
+|   +--------------+    |
+|   | +--------------+  |
+|   | |              |  |
+|   | | Master/etcd  |  |
+|   | | node(s)      |  |
+|   +-+              |  |
+|     +--------------+  |
+|           ^           |
+|           |           |
+|           v           |
+|   +--------------+    |
+|   | +--------------+  |
+|   | |              |  |
+|   | |    Worker    |  |
+|   | |    node(s)   |  |
+|   +-+              |  |
+|     +--------------+  |
++-----------------------+
+```
+
+## Requirements
+
+* Terraform 0.13.0 or newer
+
+## Quickstart
+
+NOTE: Assumes you are at the root of the kubespray repo.
+
+For authentication in your  cluster you can use the environment variables.
+
+```bash
+export TF_VAR_UPCLOUD_USERNAME=username
+export TF_VAR_UPCLOUD_PASSWORD=password
+```
+
+To allow API access to your UpCloud account, you need to allow API connections by visiting [Account-page](https://hub.upcloud.com/account) in your UpCloud Hub.
+
+Copy the cluster configuration file.
+
+```bash
+CLUSTER=my-upcloud-cluster
+cp -r inventory/sample inventory/$CLUSTER
+cp contrib/terraform/upcloud/cluster-settings.tfvars inventory/$CLUSTER/
+export ANSIBLE_CONFIG=ansible.cfg
+cd inventory/$CLUSTER
+```
+
+Edit  `cluster-settings.tfvars`  to match your requirement.
+
+Run Terraform to create the infrastructure.
+
+```bash
+terraform init ../../contrib/terraform/upcloud
+terraform apply --var-file cluster-settings.tfvars \
+    -state=tfstate-$CLUSTER.tfstate \
+     ../../contrib/terraform/upcloud/
+```
+
+You should now have a inventory file named `inventory.ini` that you can use with kubespray.
+You can use the inventory file with kubespray to set up a cluster.
+
+It is a good idea to check that you have basic SSH connectivity to the nodes. You can do that by:
+
+```bash
+ansible -i inventory.ini -m ping all
+```
+
+You can setup Kubernetes with kubespray using the generated inventory:
+
+```bash
+ansible-playbook -i inventory.ini ../../cluster.yml -b -v
+```
+
+## Teardown
+
+You can teardown your infrastructure using the following Terraform command:
+
+```bash
+terraform destroy --var-file cluster-settings.tfvars \
+      -state=tfstate-$CLUSTER.tfstate \
+      ../../contrib/terraform/upcloud/
+```
+
+## Variables
+
+* `hostname`: A valid domain name, e.g. example.com. The maximum length is 128 characters.
+* `template_name`: The name or UUID  of a base image
+* `username`: a user to access the nodes
+* `ssh_public_keys`: List of public SSH keys to install on all machines
+* `zone`: The zone where to run the cluster
+* `machines`: Machines to provision. Key of this object will be used as the name of the machine
+  * `node_type`: The role of this node *(master|worker)*
+  * `cpu`: number of cpu cores
+  * `mem`: memory size in MB
+  * `disk_size`: The size of the storage in GB
diff --git a/contrib/terraform/upcloud/cluster-settings.tfvars b/contrib/terraform/upcloud/cluster-settings.tfvars
new file mode 100644
index 0000000000000000000000000000000000000000..08bf5dacf31a9b2dff833bda7354720d27922058
--- /dev/null
+++ b/contrib/terraform/upcloud/cluster-settings.tfvars
@@ -0,0 +1,58 @@
+
+# See: https://developers.upcloud.com/1.3/5-zones/
+zone     = "fi-hel1"
+username = "ubuntu"
+
+inventory_file = "inventory.ini"
+
+#  A valid domain name, e.g. host.example.com. The maximum length is 128 characters.
+hostname = "example.com"
+
+#  Set the operating system using UUID or exact name
+template_name = "Ubuntu Server 20.04 LTS (Focal Fossa)"
+
+ssh_public_keys = [
+  # Put your public SSH key here
+  "ssh-rsa public key 1",
+  "ssh-rsa public key 2",
+]
+
+#check list of available plan https://developers.upcloud.com/1.3/7-plans/
+machines = {
+  "master-0" : {
+    "node_type" : "master",
+    #number of cpu cores
+    "cpu" : "2",
+    #memory size in MB
+    "mem" : "4096"
+    # The size of the storage in GB
+    "disk_size" : 250
+  },
+  "worker-0" : {
+    "node_type" : "worker",
+    #number of cpu cores
+    "cpu" : "2",
+    #memory size in MB
+    "mem" : "4096"
+    # The size of the storage in GB
+    "disk_size" : 250
+  },
+  "worker-1" : {
+    "node_type" : "worker",
+    #number of cpu cores
+    "cpu" : "2",
+    #memory size in MB
+    "mem" : "4096"
+    # The size of the storage in GB
+    "disk_size" : 250
+  },
+  "worker-2" : {
+    "node_type" : "worker",
+    #number of cpu cores
+    "cpu" : "2",
+    #memory size in MB
+    "mem" : "4096"
+    # The size of the storage in GB
+    "disk_size" : 250
+  }
+}
diff --git a/contrib/terraform/upcloud/main.tf b/contrib/terraform/upcloud/main.tf
new file mode 100644
index 0000000000000000000000000000000000000000..8ddd46542e969845285ba201c4899652be272c7c
--- /dev/null
+++ b/contrib/terraform/upcloud/main.tf
@@ -0,0 +1,55 @@
+
+terraform {
+  required_version = ">= 0.13.0"
+}
+provider "upcloud" {
+  # Your UpCloud credentials are read from  environment variables:
+  username = var.UPCLOUD_USERNAME
+  password = var.UPCLOUD_PASSWORD
+}
+
+module "kubernetes" {
+  source = "./modules/kubernetes-cluster"
+
+  zone     = var.zone
+  hostname = var.hostname
+
+  template_name = var.template_name
+  username      = var.username
+
+  machines = var.machines
+
+  ssh_public_keys = var.ssh_public_keys
+}
+
+#
+# Generate ansible inventory
+#
+
+data "template_file" "inventory" {
+  template = file("${path.module}/templates/inventory.tpl")
+
+  vars = {
+    connection_strings_master = join("\n", formatlist("%s ansible_user=ubuntu ansible_host=%s etcd_member_name=etcd%d",
+      keys(module.kubernetes.master_ip),
+      values(module.kubernetes.master_ip),
+    range(1, length(module.kubernetes.master_ip) + 1)))
+    connection_strings_worker = join("\n", formatlist("%s ansible_user=ubuntu ansible_host=%s",
+      keys(module.kubernetes.worker_ip),
+    values(module.kubernetes.worker_ip)))
+    list_master = join("\n", formatlist("%s",
+    keys(module.kubernetes.master_ip)))
+    list_worker = join("\n", formatlist("%s",
+    keys(module.kubernetes.worker_ip)))
+  }
+}
+
+resource "null_resource" "inventories" {
+  provisioner "local-exec" {
+    command = "echo '${data.template_file.inventory.rendered}' > ${var.inventory_file}"
+  }
+
+  triggers = {
+    template = data.template_file.inventory.rendered
+  }
+}
diff --git a/contrib/terraform/upcloud/modules/kubernetes-cluster/main.tf b/contrib/terraform/upcloud/modules/kubernetes-cluster/main.tf
new file mode 100644
index 0000000000000000000000000000000000000000..6a79af720f6d14e4c984064e59e2c774d0caddc9
--- /dev/null
+++ b/contrib/terraform/upcloud/modules/kubernetes-cluster/main.tf
@@ -0,0 +1,66 @@
+
+resource "upcloud_server" "master" {
+  for_each = {
+    for name, machine in var.machines :
+    name => machine
+    if machine.node_type == "master"
+  }
+
+  hostname    = "${each.key}.${var.hostname}"
+  cpu            = each.value.cpu
+  mem       = each.value.mem
+  zone            = var.zone
+
+  template {
+  storage = var.template_name
+  size = each.value.disk_size
+  }
+
+  # Network interfaces
+ network_interface {
+   type = "public"
+ }
+
+ network_interface {
+   type = "utility"
+ }
+ # Include at least one public SSH key
+ login {
+   user = var.username
+   keys = var.ssh_public_keys
+   create_password = false
+
+ }
+
+}
+
+
+resource "upcloud_server" "worker" {
+  for_each = {
+    for name, machine in var.machines :
+    name => machine
+    if machine.node_type == "worker"
+  }
+
+  hostname    = "${each.key}.${var.hostname}"
+  cpu            = each.value.cpu
+  mem       = each.value.mem
+  zone            = var.zone
+
+  template {
+  storage = var.template_name
+  size = each.value.disk_size
+  }
+
+  # Network interfaces
+ network_interface {
+   type = "public"
+ }
+
+ # Include at least one public SSH key
+ login {
+   user = var.username
+   keys = var.ssh_public_keys
+   create_password = false
+ }
+}
diff --git a/contrib/terraform/upcloud/modules/kubernetes-cluster/output.tf b/contrib/terraform/upcloud/modules/kubernetes-cluster/output.tf
new file mode 100644
index 0000000000000000000000000000000000000000..2661ac0136cf4b162a713e8ed24707cddd0a3b90
--- /dev/null
+++ b/contrib/terraform/upcloud/modules/kubernetes-cluster/output.tf
@@ -0,0 +1,14 @@
+
+output "master_ip" {
+  value = {
+    for instance in upcloud_server.master :
+    instance.hostname => instance.network_interface[0].ip_address
+  }
+}
+
+output "worker_ip" {
+  value = {
+    for instance in upcloud_server.worker :
+    instance.hostname => instance.network_interface[0].ip_address
+  }
+}
diff --git a/contrib/terraform/upcloud/modules/kubernetes-cluster/variables.tf b/contrib/terraform/upcloud/modules/kubernetes-cluster/variables.tf
new file mode 100644
index 0000000000000000000000000000000000000000..5b130ad10785faca6b455ec49d6ed88549a245d7
--- /dev/null
+++ b/contrib/terraform/upcloud/modules/kubernetes-cluster/variables.tf
@@ -0,0 +1,25 @@
+variable "zone" {
+  type = string
+}
+
+variable "hostname"{
+ default ="example.com"
+}
+
+variable "template_name"{}
+
+variable "username"{}
+
+variable "machines" {
+  description = "Cluster machines"
+  type = map(object({
+    node_type = string
+    cpu      = string
+    mem      = string
+    disk_size =  number
+  }))
+}
+
+variable "ssh_public_keys" {
+  type = list(string)
+}
diff --git a/contrib/terraform/upcloud/modules/kubernetes-cluster/versions.tf b/contrib/terraform/upcloud/modules/kubernetes-cluster/versions.tf
new file mode 100644
index 0000000000000000000000000000000000000000..ffe5d32b106d39d15f029daded05a181810f694a
--- /dev/null
+++ b/contrib/terraform/upcloud/modules/kubernetes-cluster/versions.tf
@@ -0,0 +1,10 @@
+
+terraform {
+  required_providers {
+    upcloud = {
+      source = "UpCloudLtd/upcloud"
+      version = "~>2.0.0"
+    }
+  }
+  required_version = ">= 0.13"
+}
diff --git a/contrib/terraform/upcloud/output.tf b/contrib/terraform/upcloud/output.tf
new file mode 100644
index 0000000000000000000000000000000000000000..f269c755ee64f57a8fea5f91f499c13d13cbdf13
--- /dev/null
+++ b/contrib/terraform/upcloud/output.tf
@@ -0,0 +1,8 @@
+
+output "master_ip" {
+  value = module.kubernetes.master_ip
+}
+
+output "worker_ip" {
+  value = module.kubernetes.worker_ip
+}
diff --git a/contrib/terraform/upcloud/sample-inventory/cluster.tfvars b/contrib/terraform/upcloud/sample-inventory/cluster.tfvars
new file mode 100644
index 0000000000000000000000000000000000000000..0a324045ec3ce8cd3b2c95141cfd9b42784411fa
--- /dev/null
+++ b/contrib/terraform/upcloud/sample-inventory/cluster.tfvars
@@ -0,0 +1,56 @@
+# See: https://developers.upcloud.com/1.3/5-zones/
+zone     = "fi-hel1"
+username = "ubuntu"
+
+inventory_file = "inventory.ini"
+
+#  A valid domain name, e.g. host.example.com. The maximum length is 128 characters.
+hostname = "example.com"
+
+#  Set the operating system using UUID or exact name
+template_name = "Ubuntu Server 20.04 LTS (Focal Fossa)"
+ssh_public_keys = [
+  # Put your public SSH key here
+  "ssh-rsa I-did-not-read-the-docs",
+  "ssh-rsa I-did-not-read-the-docs 2",
+]
+
+check list of available plan https://developers.upcloud.com/1.3/7-plans/
+machines = {
+  "master-0" : {
+    "node_type" : "master",
+    #number of cpu cores
+    "cpu" : "2",
+    #memory size in MB
+    "mem" : "4096"
+    # The size of the storage in GB
+    "disk_size" : 250
+  },
+  "worker-0" : {
+    "node_type" : "worker",
+    #number of cpu cores
+    "cpu" : "2",
+    #memory size in MB
+    "mem" : "4096"
+    # The size of the storage in GB
+    "disk_size" : 250
+  },
+  "worker-1" : {
+    "node_type" : "worker",
+    #number of cpu cores
+    "cpu" : "2",
+    #memory size in MB
+    "mem" : "4096"
+    # The size of the storage in GB
+    "disk_size" : 250
+  },
+  "worker-2" : {
+    "node_type" : "worker",
+    #number of cpu cores
+    "cpu" : "2",
+    #memory size in MB
+    "mem" : "4096"
+    # The size of the storage in GB
+    "disk_size" : 250
+  }
+}
diff --git a/contrib/terraform/upcloud/sample-inventory/group_vars b/contrib/terraform/upcloud/sample-inventory/group_vars
new file mode 120000
index 0000000000000000000000000000000000000000..0d5106205132e5ebdc5993e433121e66aed7d36e
--- /dev/null
+++ b/contrib/terraform/upcloud/sample-inventory/group_vars
@@ -0,0 +1 @@
+../../../../inventory/sample/group_vars/
\ No newline at end of file
diff --git a/contrib/terraform/upcloud/templates/inventory.tpl b/contrib/terraform/upcloud/templates/inventory.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..26d65e67ba6d3dadb87cb7ce7e71549c4dd1432a
--- /dev/null
+++ b/contrib/terraform/upcloud/templates/inventory.tpl
@@ -0,0 +1,17 @@
+
+[all]
+${connection_strings_master}
+${connection_strings_worker}
+
+[kube-master]
+${list_master}
+
+[etcd]
+${list_master}
+
+[kube-node]
+${list_worker}
+
+[k8s-cluster:children]
+kube-master
+kube-node
diff --git a/contrib/terraform/upcloud/variables.tf b/contrib/terraform/upcloud/variables.tf
new file mode 100644
index 0000000000000000000000000000000000000000..60941d57266115718b8de2689973a5f5ec3ad41d
--- /dev/null
+++ b/contrib/terraform/upcloud/variables.tf
@@ -0,0 +1,35 @@
+
+variable "zone" {
+  description = "The zone where to run the cluster"
+}
+
+variable "hostname" {
+  default = "example.com"
+}
+
+variable "template_name" {}
+
+variable "username" {}
+
+variable "machines" {
+  description = "Cluster machines"
+  type = map(object({
+    node_type = string
+    cpu       = string
+    mem       = string
+    disk_size = number
+  }))
+}
+
+variable "ssh_public_keys" {
+  description = "List of public SSH keys which are injected into the VMs."
+  type        = list(string)
+}
+
+variable "inventory_file" {
+  description = "Where to store the generated inventory file"
+}
+
+variable "UPCLOUD_USERNAME" {}
+
+variable "UPCLOUD_PASSWORD" {}
diff --git a/contrib/terraform/upcloud/versions.tf b/contrib/terraform/upcloud/versions.tf
new file mode 100644
index 0000000000000000000000000000000000000000..c910898796ad8cd16926cbffe8622b3697cabf52
--- /dev/null
+++ b/contrib/terraform/upcloud/versions.tf
@@ -0,0 +1,10 @@
+
+terraform {
+  required_providers {
+    upcloud = {
+      source  = "UpCloudLtd/upcloud"
+      version = "~>2.0.0"
+    }
+  }
+  required_version = ">= 0.13"
+}