Skip to main content

Import existing Cloider resources into Terraform

Importing tells Terraform that a resource which already exists in your Cloider account (created in the dashboard, with the CLI, the API or an AI agent) is now managed by your configuration. Nothing is created or deleted: Terraform reads the resource from the API, records it in its state, and from then on plans changes against it. This guide shows how to find each resource's import ID, import with import blocks or the terraform import command, let Terraform write the configuration for you, and import many resources at once.

At a glance​

Ways to importimport blocks (Terraform 1.5 or later, OpenTofu 1.6 or later), or terraform import (any version)
Generate configurationterraform plan -generate-config-out=generated.tf with import blocks
Import manyimport blocks with for_each (Terraform 1.7 or later, OpenTofu 1.7 or later)
What can be importedEvery resource the provider has
What can'tThe registry project Cloider creates for your apps, and secrets Cloider shows only once (see below)

Before you begin​

  • The provider configured with a token for the account the resources are in, see Provider configuration. A Read token is enough to plan an import, applying it needs Read & Write.
  • The cloider CLI, to list the ids. You can also find them in the API's responses (hashed_id).
  • A Terraform state you want the resources in, ideally a remote backend.

Step 1: Find the import IDs​

Most resources are imported by their id. The CLI's -o wide output adds an ID column:

ResourceImport IDWhere to find itExample
cloider_clusterThe cluster's idcloider cluster list -o widex8f2kq
cloider_databaseThe database's idcloider db list -o widep0q9zm
cloider_bucketThe bucket's idcloider bucket list -o wideb7r2xw
cloider_bucket_keyThe key's idcloider bucket key list -o wide
cloider_registryThe project's idcloider registry list -o wider4t6yu
cloider_registry_tokenThe token's idcloider registry token list -o wide
cloider_domainThe domain namecloider domain listexample.com
cloider_dns_recordThe record's numeric idcloider dns list <domain> -o wide48213
cloider_target<domain>/<id>cloider target list <domain> -o wideexample.com/t4k9vq

For example:

cloider db list -o wide
Output
ID NAME ENGINE PLAN STATUS CREATED
p0q9zm orders-db PostgreSQL 18 Standard Ready 2026-09-02 14:12

Step 2: Write import blocks​

An import block names the resource address to import into (to) and the ID (id). Put them in their own file:

imports.tf
import {
to = cloider_cluster.production
id = "x8f2kq"
}

import {
to = cloider_database.orders
id = "p0q9zm"
}

import {
to = cloider_domain.example
id = "example.com"
}

Step 3: Let Terraform write the configuration​

Instead of writing the resource blocks by hand, ask Terraform to generate them from what it reads:

terraform plan -generate-config-out=generated.tf
Output
cloider_cluster.production: Preparing import... [id=x8f2kq]
cloider_database.orders: Preparing import... [id=p0q9zm]
cloider_domain.example: Preparing import... [id=example.com]
cloider_cluster.production: Refreshing state... [id=x8f2kq]
cloider_database.orders: Refreshing state... [id=p0q9zm]
cloider_domain.example: Refreshing state... [id=example.com]

Terraform will perform the following actions:

# cloider_cluster.production will be imported
# (config will be generated)
resource "cloider_cluster" "production" {
...
Plan: 3 to import, 0 to add, 0 to change, 0 to destroy.

Terraform has generated configuration and written it to generated.tf.

Open generated.tf and review it. It holds every argument with the value the resource has, for example:

generated.tf
resource "cloider_database" "orders" {
engine = "postgresql"
label = "orders-db"
name = "orders-db"
paused = false
plan = "standard"
ssl = true
trusted_sources = ["cluster:x8f2kq"]
user = "orders_db_user"
version = "18"
}

Tidy it into your own files: replace literal ids with references ("cluster:${cloider_cluster.production.id}"), drop arguments you are happy to leave at their defaults, and add lifecycle { prevent_destroy = true } where it matters.

OpenTofu

OpenTofu supports import blocks and -generate-config-out the same way: tofu plan -generate-config-out=generated.tf.

Step 4: Plan until there are no changes, then apply​

Run terraform plan again. A clean import shows only imports:

Output
Plan: 3 to import, 0 to add, 0 to change, 0 to destroy.

If the plan also shows changes, your configuration doesn't match the real resource yet. Change the configuration (not the resource) until only the imports are left. Pay particular attention to:

  • # forces replacement on a database or cluster. Never apply that: it would delete the resource you are importing. Usually version, user or name doesn't match.
  • Defaults that differ from the resource: trusted_sources (["*"]), ssl, plan (hobby) on databases, public and versioning on buckets, public, storage_limit_gi and immutable_tags on registry projects.

Then apply:

terraform apply
Output
cloider_cluster.production: Importing... [id=x8f2kq]
cloider_cluster.production: Import complete [id=x8f2kq]
cloider_database.orders: Importing... [id=p0q9zm]
cloider_database.orders: Import complete [id=p0q9zm]
cloider_domain.example: Importing... [id=example.com]
cloider_domain.example: Import complete [id=example.com]

Apply complete! Resources: 3 imported, 0 added, 0 changed, 0 destroyed.

Once applied, the import blocks have done their job and can be deleted, or kept as a record.

Import many resources at once with for_each​

In Terraform 1.7 or later, an import block takes for_each, to import into a resource that itself uses for_each:

dns.tf
locals {
dns_records = {
www = { id = "48214", type = "CNAME", values = ["acme-web.cloider.app."] }
api = { id = "48216", type = "CNAME", values = ["acme-api.cloider.app."] }
}
}

import {
for_each = local.dns_records
to = cloider_dns_record.this[each.key]
id = each.value.id
}

resource "cloider_dns_record" "this" {
for_each = local.dns_records

domain = "example.com"
type = each.value.type
name = each.key
values = each.value.values
ttl = 3600
}

Generate import blocks from the CLI​

The CLI's -o json prints the API's JSON, which jq can turn into import blocks. For every cluster and database in the account:

{
cloider cluster list -o json | jq -r '.[] | select(.status != "Terminated") |
"import {\n to = cloider_cluster.\(.cluster_name | gsub("-"; "_"))\n id = \"\(.hashed_id)\"\n}\n"'
cloider db list -o json | jq -r '.[] | select(.status != "Terminated") |
"import {\n to = cloider_database.\(.database_name | gsub("-"; "_"))\n id = \"\(.hashed_id)\"\n}\n"'
} > imports.tf

terraform plan -generate-config-out=generated.tf

Resource names in Terraform can't contain -, hence the gsub. Two clusters with the same name would give two blocks with the same address: rename one of them by hand.

Import with the terraform import command​

Before Terraform 1.5, or for a quick one-off, write the resource block first, then import one resource at a time:

main.tf
resource "cloider_cluster" "production" {
name = "production"
}
terraform import cloider_cluster.production x8f2kq
Output
cloider_cluster.production: Importing from ID "x8f2kq"...
cloider_cluster.production: Import prepared!
Prepared cloider_cluster for import
cloider_cluster.production: Refreshing state... [id=x8f2kq]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

terraform import writes to the state immediately, without a plan. Run terraform plan right after it and fix the configuration until it shows no changes.

What can't be imported, or imports without secrets​

ResourceWhat happens
The apps registry projectRefused with Can't manage the apps project. Cloider creates and removes it with your apps
A cloider_bucket_keyImports, but secret_key stays null: Cloider shows the secret only when a key is created
A cloider_registry_tokenImports, but secret stays null for the same reason. expires_in_days is worked out from the creation and expiry times
A cloider_domain you try to create insteadRefused with Domain already added, which tells you to import it
Apps, members, API tokensNot resources of the provider, see What the provider doesn't manage

When your configuration needs a key's or token's secret (to hand it to an app, say), create a new one with Terraform instead of importing the old one, switch everything over, then delete the old one.

A database's password and a cluster's kubeconfig can be read at any time, so imported databases and clusters have them.

Troubleshooting​

ProblemCauseFix
Cannot import non-existent remote objectWrong id, or the resource is in another accountCheck the ID column, and which token the provider uses
Plan shows # forces replacement after importname, version, user, engine or region differs from the resourceMake the configuration match. Don't apply until the plan has no replacement
Invalid target import idA target was imported by its id aloneUse <domain>/<id>
Invalid DNS record idA DNS record was imported by nameUse the numeric id from cloider dns list <domain> -o wide
Resource already managed by TerraformThe address is already in stateterraform state rm <address> first, or use another address

Next steps​

Frequently asked questions

Can I import Cloider resources created in the dashboard into Terraform?

Yes. Every provider resource can be imported: clusters, databases, buckets, bucket keys, registry projects and tokens, domains, DNS records and targets. Use an import block with the resource's id, or terraform import.

How do I find the ID to import a Cloider resource?

Run the CLI's list command with -o wide, which adds an ID column: cloider cluster list -o wide, cloider db list -o wide and so on. Domains are imported by name, and targets as <domain>/<id>.

Can Terraform write the configuration for imported Cloider resources?

Yes, with Terraform 1.5 or later: write import blocks, then run terraform plan -generate-config-out=generated.tf. Review the generated file before applying.

Why is secret_key empty after importing a bucket key?

Cloider shows bucket key secrets and registry token secrets only once, when they are created. Terraform never saw it, so it stays null. Create a new key with Terraform if you need the secret.

Does importing change or restart the resource?

No. Importing only reads the resource and records it in state. Only a later apply with a non-empty plan changes anything.