In this tutorial we will be creating a Kubernetes Cluster on Civo using Terraform and use the local provider to save the kubeconfig to a local file.

Once we have our Infrastructure up and running, we will deploy a Go web application to our cluster.


You will require the following to follow this tutorial:


We require a Civo API Key to authenticate terraform with Civo, by heading to your security section from your account.

I will be saving the API Key to my environment as:

$ export TF_VAR_civo_api_key="x"


First we create our which will include the providers that we will be using to create our Kubernetes cluster, which will be civo and local to download the kubeconfig and write it to a local file:

terraform {
  required_providers {
    civo = {
      source = "civo/civo"
      version = "0.10.9"
    local = {
      source = "hashicorp/local"
      version = "2.1.0"

provider "civo" {
  token = var.civo_api_key

provider "local" {}

Next our where we will define all our user-defined values for our cluster:

variable "civo_api_key" {
  type        = string
  description = "civo api key to authenticate"

variable "civo_cluster_name" {
  type        = string
  description = "civo kubernetes cluster name"
  default     = "ruan"

variable "civo_region" {
  type        = string
  description = "civo region to use"
  default     = "LON1"

variable "civo_cluster_applications" {
  type        = string
  description = "applications to deploy on kubernetes cluster"
  default     = "Traefik"

variable "civo_cluster_nodes" {
  type        = number
  description = "kubernetes nodes count"
  default     = 1

variable "civo_nodes_size" {
  type        = string
  description = "instance size to use for nodes"
  default     = "g3.k3s.xsmall"

Our which will be our code to define the civo kubernetes cluster and the local file that we will write to our current working directory $PWD/kubeconfig.civo which will include the kubeconfig that we will get from the civo_kubernetes_cluster resource:

resource "civo_kubernetes_cluster" "ruan" {
    name              = var.civo_cluster_name
    region            = var.civo_region
    applications      = var.civo_cluster_applications
    num_target_nodes  = var.civo_cluster_nodes
    target_nodes_size = var.civo_nodes_size

resource "local_file" "kubeconfig" {
    content   = civo_kubernetes_cluster.ruan.kubeconfig
    filename  = "${path.module}/kubeconfig.civo"

And lastly our which will output information about our kubernetes cluster:

output "cluster_name" {
  value =

output "applications" {
  value = civo_kubernetes_cluster.ruan.applications

output "kubernetes_version" {
  value = civo_kubernetes_cluster.ruan.kubernetes_version

output "api_endpoint" {
  value = civo_kubernetes_cluster.ruan.api_endpoint

output "dns_entry" {
  value = civo_kubernetes_cluster.ruan.dns_entry

Infrastructure Deployment

First we need to initialize terraform by downloading the providers:

$ terraform init

Initializing the backend...
Terraform has been successfully initialized!

To see what terraform will be creating, run a plan:

$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # civo_kubernetes_cluster.ruan will be created
  + resource "civo_kubernetes_cluster" "ruan" {
      + api_endpoint           = (known after apply)
      + applications           = "Traefik"
      + created_at             = (known after apply)
      + dns_entry              = (known after apply)
      + id                     = (known after apply)
      + installed_applications = (known after apply)
      + instances              = (known after apply)
      + kubeconfig             = (known after apply)
      + kubernetes_version     = (known after apply)
      + master_ip              = (known after apply)
      + name                   = "ruan"
      + network_id             = (known after apply)
      + num_target_nodes       = 1
      + pools                  = (known after apply)
      + ready                  = (known after apply)
      + region                 = "LON1"
      + status                 = (known after apply)
      + target_nodes_size      = "g3.k3s.xsmall"

  # local_file.kubeconfig will be created
  + resource "local_file" "kubeconfig" {
      + content              = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "./kubeconfig.civo"
      + id                   = (known after apply)

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

Changes to Outputs:
  + api_endpoint       = (known after apply)
  + applications       = "Traefik"
  + cluster_name       = "ruan"
  + dns_entry          = (known after apply)
  + kubernetes_version = (known after apply)

Once your are happy with what terraform will be creating, run apply:

$ terraform apply -auto-approve
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.


api_endpoint = "https://x.x.x.x:6443"
applications = "Traefik"
cluster_name = "ruan"
dns_entry = ""
kubernetes_version = "1.20.0-k3s1"

In our current directory, you will notice that our kubeconfig has been saved to kubeconfig.civo:

$ ls | grep kubeconfig

Set the file to our KUBECONFIG environment variable:

$ export KUBECONFIG=$PWD/kubeconfig.civo

Interact with the Kubernetes Cluster

List the nodes:

$ kubectl get nodes --output wide
k3s-ruan-471f4933-node-pool-4696   Ready    <none>   16m   v1.20.2+k3s1   Ubuntu 20.04.2 LTS   5.4.0-80-generic   containerd://1.4.3-k3s1

Software Deployment

Let's deploy a basic web application to our cluster, which is a Go webapp which returns the hostname from where the pod is running from.

If you follow along step-by-step, just make sure to replace the cluster id:

apiVersion: v1
kind: Service
  name: hostname-service
    app: hostname
    - protocol: TCP
      port: 8000
      targetPort: 8000
      name: web
# for k3s => v1.19
kind: Ingress
  name: hostname-ingress
  annotations: "false"
  - host:
      - pathType: Prefix
        path: "/"
            name: hostname-service
              number: 8000
apiVersion: apps/v1
kind: Deployment
    app: hostname
  name: hostname
  replicas: 3
      app: hostname
        app: hostname
      - name: hostname
        image: ruanbekker/hostname:latest
        imagePullPolicy: IfNotPresent
        - containerPort: 8000
          name: http
          protocol: TCP

Deploy the application to the cluster:

$ kubectl apply -f app.yml
service/hostname-service created created
deployment.apps/hostname created

After a couple of seconds, we can get the status of our deployment and getting the output of our ingress:

$ kubectl get ingress,deployment,pods --output wide
NAME                                         CLASS    HOSTS                                                        ADDRESS        PORTS   AGE   <none>   80      6m14s

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES                       SELECTOR
deployment.apps/hostname   3/3     3            3           6m14s   hostname     ruanbekker/hostname:latest   app=hostname

NAME                           READY   STATUS    RESTARTS   AGE     IP          NODE                               NOMINATED NODE   READINESS GATES
pod/hostname-5c5978694-7kpg4   1/1     Running   0          6m14s   k3s-ruan-471f4933-node-pool-f6b1   <none>           <none>
pod/hostname-5c5978694-t7rql   1/1     Running   0          6m14s   k3s-ruan-471f4933-node-pool-f6b1   <none>           <none>
pod/hostname-5c5978694-tbvwl   1/1     Running   0          6m14s   k3s-ruan-471f4933-node-pool-f6b1   <none>           <none>

Testing the Application

As we can see our deployment is in it's desired state, we can test our application by making a http request to it:

$ curl
Hostname: hostname-5c5978694-7kpg4

Tear Down

To destroy the cluster, use terraform destroy:

$ terraform destroy -auto-approve

And remove the Civo API Key from your environment:

$ unset TF_VAR_civo_api_key


The container image:

The Civo provider:

Source code for this demonstration:

Video Walkthrough of using the Civo Provider by Saiyam Pathak


Thanks for reading, if you like my content, check out my website or follow me at @ruanbekker on Twitter.