Skip to main content

Get started with the Cloider Terraform provider

This tutorial takes you from nothing to a running Kubernetes cluster and a PostgreSQL database on Cloider, created with Terraform (or OpenTofu). You install the tool, give the Cloider provider an API token, write a small configuration, then run terraform init, plan and apply, use the outputs, change the database, and delete everything with terraform destroy. It takes about ten minutes, most of it waiting for the cluster.

Before you begin​

  • A Cloider account. See Create an account. The configuration on this page creates one cluster and one database, which fits in the free trial.
  • A way to give Terraform an API token: either the cloider CLI, logged in with cloider login, or a Read & Write API token from Access Control, API Tokens in the dashboard.
  • A terminal. kubectl is optional, for the step that connects to the cluster (see Connect with kubectl).

Step 1: Install Terraform or OpenTofu​

The provider works with Terraform 1.0 or later and OpenTofu 1.6 or later. Any of those versions can follow this tutorial, but installing the newest release is recommended (import blocks, used in Import existing resources, need Terraform 1.5 or later).

With Homebrew:

# Terraform
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# or OpenTofu
brew install opentofu

Check that it works. The command prints the installed version and your platform:

terraform version # or: tofu version
Using OpenTofu?

Every command on this page works with OpenTofu: type tofu where it says terraform, for example tofu init and tofu apply. The configuration files are the same.

Step 2: Give the provider an API token​

The provider needs a Cloider API token to act on your account. Pick one of these:

If you use the cloider CLI, log in once. The provider reads the token the CLI saved, so you don't have to handle it at all:

cloider login

The browser opens a page where you choose the account and Read & Write access. See CLI authentication for details.

Don't write the token into a .tf file

A token in a configuration file ends up in git and in every copy of the code. Use cloider login, an environment variable or a token file instead. All the options are on Provider configuration.

Step 3: Write the configuration​

Create an empty folder for the project and three files in it. Terraform reads every .tf file in the folder, so how you split them is up to you. This split is a common convention.

mkdir cloider-staging && cd cloider-staging

versions.tf says which provider to download:

versions.tf
terraform {
required_version = ">= 1.0"

required_providers {
cloider = {
source = "cloider/cloider"
version = "~> 0.1"
}
}
}

main.tf declares the provider and the resources. The cluster is called staging, and the database orders-db only accepts connections from that cluster:

main.tf
# Takes the token from CLOIDER_TOKEN, or from `cloider login`
provider "cloider" {}

# A serverless Kubernetes cluster
resource "cloider_cluster" "staging" {
name = "staging"
}

# A PostgreSQL database that only the cluster may connect to
resource "cloider_database" "orders" {
name = "orders-db"
engine = "postgresql"
plan = "hobby"
trusted_sources = ["cluster:${cloider_cluster.staging.id}"]
}

outputs.tf prints what you need to connect. Values marked sensitive are hidden in the terminal and shown only when you ask for them by name:

outputs.tf
output "kubeconfig" {
description = "terraform output -raw kubeconfig > kubeconfig.yaml"
value = cloider_cluster.staging.kube_config.raw
sensitive = true
}

output "database_host" {
description = "The host the cluster reaches the database at"
value = cloider_database.orders.private_host
}

output "database_port" {
value = cloider_database.orders.port
}

output "database_user" {
value = cloider_database.orders.user
}

output "database_password" {
value = cloider_database.orders.password
sensitive = true
}

A few things to notice:

  • cloider_cluster.staging.id refers to the cluster's id, which only exists after the cluster is created. Terraform sees that reference and creates the cluster before the database.
  • trusted_sources would default to ["*"] (anywhere). Here it lists only the cluster. See Trusted sources.
  • private_host is the host for clients running inside Cloider, such as pods in the cluster. Your laptop uses host instead. See Connect to a database.

Step 4: Initialize the folder​

terraform init downloads the Cloider provider from the registry into .terraform/ and writes .terraform.lock.hcl:

terraform init
Output
Initializing the backend...
Initializing provider plugins...
- Finding cloider/cloider versions matching "~> 0.1"...
- Installing cloider/cloider v0.1.x...
- Installed cloider/cloider v0.1.x (self-signed, key ID XXXXXXXXXXXXXXXX)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

v0.1.x is the newest 0.1 release on the day you run it. Commit .terraform.lock.hcl with your .tf files, and add .terraform/ and *.tfstate* to .gitignore.

Step 5: Preview the changes​

terraform plan checks the token, reads your account and shows what it would do, without changing anything:

terraform plan
Output
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:

# cloider_cluster.staging will be created
+ resource "cloider_cluster" "staging" {
+ created_at = (known after apply)
+ id = (known after apply)
+ kube_config = (sensitive value)
+ label = (known after apply)
+ name = "staging"
+ paused = false
+ status = (known after apply)
+ version = (known after apply)
}

# cloider_database.orders will be created
+ resource "cloider_database" "orders" {
+ ca_certificate = (sensitive value)
+ created_at = (known after apply)
+ engine = "postgresql"
+ host = (known after apply)
+ id = (known after apply)
+ label = (known after apply)
+ name = "orders-db"
+ password = (sensitive value)
+ paused = false
+ plan = "hobby"
+ port = (known after apply)
+ private_host = (known after apply)
+ ssl = (known after apply)
+ status = (known after apply)
+ trusted_sources = [
+ (known after apply),
]
+ user = (known after apply)
+ version = (known after apply)
}

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

Changes to Outputs:
+ database_host = (known after apply)
+ database_password = (sensitive value)
+ database_port = (known after apply)
+ database_user = (known after apply)
+ kubeconfig = (sensitive value)

(known after apply) marks values that Cloider decides when it creates the resource: the id, the host names, the password, and defaults such as the newest Kubernetes and PostgreSQL versions.

Step 6: Create the resources​

terraform apply shows the same plan and asks for confirmation. Type yes:

terraform apply
Output
...
Plan: 2 to add, 0 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

cloider_cluster.staging: Creating...
cloider_cluster.staging: Still creating... [10s elapsed]
cloider_cluster.staging: Still creating... [20s elapsed]
...
cloider_cluster.staging: Creation complete after 2m41s [id=x8f2kq]
cloider_database.orders: Creating...
cloider_database.orders: Still creating... [10s elapsed]
...
cloider_database.orders: Creation complete after 1m37s [id=p0q9zm]

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

Outputs:

database_host = "orders-db-p0q9zm.db.cloider.internal"
database_password = <sensitive>
database_port = 20417
database_user = "orders_db_user"
kubeconfig = <sensitive>

The provider creates the cluster and waits until its status is Ready, then does the same for the database. Each wait is limited to 20 minutes, see timeouts.

Terraform has now written terraform.tfstate in the folder. It records the ids of what it created, and also the database password and the cluster's kubeconfig, so treat it like a password. For anything beyond a test, keep state in a remote backend such as a Cloider bucket.

Step 7: Use the outputs​

Print every output, or one by name. -raw prints a string without quotes, which is what you want for files and scripts:

terraform output
terraform output -raw database_password

Save the kubeconfig and talk to the cluster with kubectl:

terraform output -raw kubeconfig > kubeconfig.yaml
chmod 600 kubeconfig.yaml
KUBECONFIG=./kubeconfig.yaml kubectl get namespaces
Output
NAME STATUS AGE
default Active 4m
kube-node-lease Active 4m
kube-public Active 4m
kube-system Active 4m

The resources are ordinary Cloider resources, so you also see them in the dashboard and the CLI:

cloider cluster list
cloider db get orders-db

From here, a common next step is to deploy into the cluster from the same configuration with the kubernetes and helm providers, passing the database credentials as a Kubernetes Secret. The Kubernetes and Helm guide shows how.

Step 8: Change something​

Terraform's job is to keep your account matching the configuration. Change the database's plan from hobby to starter in main.tf:

main.tf
resource "cloider_database" "orders" {
name = "orders-db"
engine = "postgresql"
plan = "starter"
trusted_sources = ["cluster:${cloider_cluster.staging.id}"]
}
terraform plan
Output
cloider_cluster.staging: Refreshing state... [id=x8f2kq]
cloider_database.orders: Refreshing state... [id=p0q9zm]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
~ update in-place

Terraform will perform the following actions:

# cloider_database.orders will be updated in-place
~ resource "cloider_database" "orders" {
~ ca_certificate = (sensitive value)
id = "p0q9zm"
name = "orders-db"
~ plan = "hobby" -> "starter"
~ status = "Ready" -> (known after apply)
# (13 unchanged attributes hidden)
}

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

update in-place means the database is resized and keeps its data. It restarts once, and terraform apply waits until it is Ready again. Some changes can't be made in place and replace the resource instead (delete it and create a new one): for example a smaller plan, another engine version, or a new name. Terraform marks those -/+ destroy and then create replacement and adds # forces replacement next to the attribute. Always read the plan before typing yes. Every resource page lists which attributes force a replacement, for example cloider_database.

Apply the change with terraform apply, or put plan back to hobby and skip it.

Step 9: Destroy everything​

When you are done, delete what the configuration created:

terraform destroy
Output
cloider_cluster.staging: Refreshing state... [id=x8f2kq]
cloider_database.orders: Refreshing state... [id=p0q9zm]

Terraform will perform the following actions:

# cloider_cluster.staging will be destroyed
- resource "cloider_cluster" "staging" {
- id = "x8f2kq" -> null
- name = "staging" -> null
# (6 unchanged attributes hidden)
}

# cloider_database.orders will be destroyed
- resource "cloider_database" "orders" {
- engine = "postgresql" -> null
- id = "p0q9zm" -> null
- name = "orders-db" -> null
# (14 unchanged attributes hidden)
}

Plan: 0 to add, 0 to change, 2 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

cloider_database.orders: Destroying... [id=p0q9zm]
cloider_database.orders: Still destroying... [id=p0q9zm, 10s elapsed]
cloider_database.orders: Destruction complete after 24s
cloider_cluster.staging: Destroying... [id=x8f2kq]
cloider_cluster.staging: Still destroying... [id=x8f2kq, 10s elapsed]
...
cloider_cluster.staging: Destruction complete after 1m3s

Destroy complete! Resources: 2 destroyed.

Terraform deletes the database first, because it depends on the cluster, and waits for each to be gone. Deleting is permanent: the database's data and everything in the cluster are deleted with them. Billing stops when the resources are deleted.

Verify​

  • terraform apply ends with Apply complete! Resources: 2 added.
  • cloider cluster list and cloider db list show staging and orders-db with the status Ready, as does the dashboard.
  • KUBECONFIG=./kubeconfig.yaml kubectl get namespaces lists the cluster's namespaces.
  • A second terraform plan right after the apply says No changes. Your infrastructure matches the configuration.

Troubleshooting​

ErrorCauseFix
No Cloider API token with no API token: set token or CLOIDER_TOKEN, or run 'cloider login'The provider found no token in any of its sourcesRun cloider login, or export CLOIDER_TOKEN=... in the same terminal
Cloider refused the API tokenThe token is mistyped, revoked or expiredCreate a new token, or run cloider login again
Can't create the cluster with Free plan users can not create more than one cluster. UPGRADE NOW!The trial allows one cluster, database, bucket and registry project at a timeDelete the other one, or upgrade. See Limits and quotas
This API token is read only! when applying, while plan worksThe token is a Read tokenUse a Read & Write token for apply and destroy
Cluster staging didn't become ready with gave up waiting, raise the timeout with a timeouts blockThe cluster took longer than the 20 minute defaultAdd timeouts = { create = "40m" }. The cluster stays in state as tainted, so the next apply replaces it, or run terraform untaint cloider_cluster.staging once it is Ready
Failed to query available provider packages during initTypo in source, or no access to the registryUse source = "cloider/cloider" and check that the machine can reach registry.terraform.io (or registry.opentofu.org with OpenTofu)

Next steps​

Frequently asked questions

How long does terraform apply take for a Cloider cluster?

A few minutes. The provider creates the cluster and waits until its status is Ready, checking every 5 seconds, for up to 20 minutes by default. A database usually takes a minute or two.

Can I follow this tutorial on the free trial?

Yes. It creates one Kubernetes cluster and one database, which is what the trial allows at a time. Destroy them before creating others.

Where does Terraform keep the database password?

In the state file, terraform.tfstate, together with the cluster's kubeconfig. Outputs marked sensitive are hidden in the terminal but stored in plain text in the state. Keep the state private, ideally in a remote backend such as a Cloider bucket.

Does terraform destroy delete my data?

Yes. Destroying a cloider_database deletes the database and its data, and destroying a cloider_cluster deletes everything running in it. Use lifecycle { prevent_destroy = true } on resources that must never be deleted by Terraform.

Do I need the cloider CLI to use the Terraform provider?

No. The provider only needs an API token, for example in the CLOIDER_TOKEN environment variable. The CLI is a convenience: after cloider login the provider can use the token the CLI saved.