Cloady Docs

Terraform Provider

Declare workspaces, applications, variables and domains as infrastructure as code.

Cloady has a Terraform provider on the public registry, so workspaces, applications, environment variables and custom domains can be declared as code and reviewed, versioned and rolled out like the rest of your infrastructure.

terraform {
  required_providers {
    cloady = {
      source  = "cloady/cloady"
      version = "~> 0.1"
    }
  }
}

provider "cloady" {} # reads CLOADY_TOKEN

resource "cloady_app" "api" {
  workspace = "acme"
  name      = "API"
  region    = "eu1"
  git       = { repository_url = "https://github.com/acme/api" }
}

output "url" {
  value = cloady_app.api.endpoints[0].url
}

terraform init installs it from the registry. Nothing else to set up.

Authenticate

Create a token under Account → API tokens and export it:

export CLOADY_TOKEN=cldy_xxxxxxxxxxxxxxxx

The provider reads CLOADY_TOKEN by default; set token on the provider block to override it, and base_url (or CLOADY_CONTROL_PLANE_URL) to point at a Cloady installation other than https://cloady.com.

Creating workspaces and changing billing need a full-scope token. Everything else works with deploy, and reads work with read. Your workspace role still applies on top: a full token can't delete in a workspace where you're a viewer.

Resources

ResourceManages
cloady_appAn application from a Git repository or the catalog
cloady_workspaceA workspace
cloady_variableOne application environment variable
cloady_domainA custom domain on an application

Two data sources read existing state without owning it: cloady_workspace, and cloady_regions for the regions you can deploy to, including which ones accept apps from a workspace that pays nothing.

The full attribute reference lives on the Terraform Registry.

A complete stack

data "cloady_regions" "all" {}

resource "cloady_workspace" "acme" {
  slug = "acme"
  name = "Acme"
}

resource "cloady_app" "db" {
  workspace = cloady_workspace.acme.slug
  name      = "Postgres"
  region    = "eu1"
  catalog   = "postgres"

  volume_sizes = { data = 20 }

  lifecycle {
    prevent_destroy = true
  }
}

resource "cloady_app" "api" {
  workspace    = cloady_workspace.acme.slug
  name         = "API"
  region       = "eu1"
  cpu_scale    = 2
  memory_scale = 2

  git = {
    repository_url = "https://github.com/acme/api"
    branch         = "main"
    auto_deploy    = true
  }
}

resource "cloady_variable" "database_url" {
  workspace = cloady_workspace.acme.slug
  app       = cloady_app.api.slug
  region    = cloady_app.api.region
  key       = "DATABASE_URL"
  value     = var.database_url
  is_secret = true
}

resource "cloady_domain" "api" {
  workspace  = cloady_workspace.acme.slug
  app        = cloady_app.api.slug
  region     = cloady_app.api.region
  host       = "api.acme.com"
  is_primary = true
}

Each application runs in one region and one environment, and its name must be unique within that workspace and environment. environment defaults to production wherever it's accepted; set preview or development for the others. Variable keys are SCREAMING_SNAKE_CASE.

Read this before your first apply

Terraform's model is "make reality match the configuration", and it will delete and recreate a resource whenever an attribute it can't change is changed. On an application holding data, a recreate is data loss.

Attribute changes on cloady_app split into two outcomes: name, branch, scale and volume sizes are patched in place, while region, environment, workspace, an explicit slug and the Git repository URL destroy the application and create a new one, deleting its data unless prevent_destroy blocks the plancloady_app — what the next apply does with a changed attributeChanges in placename branchcpu_scale memory_scalevolume_sizes (grow only)slug follows the name unless you set itForces destroy + createregion environment workspaceslug (once set explicitly)git.repository_urlfixed when the application is createdprevent_destroy = trueplan fails hereotherwiseUpdated in placesame application, same dataDestroyed, then created freshthe application's data is deleted with it
  • Destroying an application deletes its data, permanently. Its volumes go with it and there is no undo. Put prevent_destroy on anything holding state, as the Postgres app above does, and on the workspace — destroying a workspace destroys every application in it.
  • Some attributes force replacement. An application is created in one region, in one environment, in one workspace, from one source, and stays there. Changing region, environment, workspace or the Git repository URL is a destroy followed by a create: empty data, a new address, downtime.
  • Renaming happens in place — never destroy an application to rename it. name, branch, cpu_scale, memory_scale, volume_sizes and auto_deploy are all patched on the existing app. The one cost of a rename is the address: an app answers at <name>-<code>-<workspace>.cloady.io, for example my-blog-k7q2v9ze-acme.cloady.io, and that follows the name, so the old address stops working. Custom domains keep pointing at the app.
  • Volumes only grow. Lowering a value in volume_sizes is rejected at plan time. To go smaller, back up and create a new application.
  • Variable changes reach the app on its next deploy. An apply that only touches cloady_variable restarts nothing; follow it with cloady redeploy acme api if the app needs the value now.
  • Variable values are stored in Terraform state in plaintext, including secrets. is_secret hides the value in Cloady; it does not encrypt your state file. Use a remote backend with encryption at rest, and prefer var. inputs over literals.
  • On cloady_domain, only redirect_to changes in place. Changing host or is_primary replaces the record; the app keeps running, but the new host needs its DNS pointed at Cloady before HTTPS works on it.
  • A successful apply is not a healthy application. Create returns once the deploy starts, so status reports what Cloady saw at that moment, not readiness. Watch the deploy in the dashboard or with the CLI.

Scale is a multiplier over what the application asks for, not an absolute size: cpu_scale = 2 doubles it. Values run from 0.5 to 4 in steps of 0.25, and everything running in the workspace — every region, every environment — counts toward what the workspace pays.

While the applied stack fits the workspace's free allowance nothing is charged. Beyond it, the card on file is charged for the difference as the apply runs; with no card, or a declined one, Cloady answers payment_required and Terraform stops. Add a card under Workspace settings → Billing and re-run the apply — previewWorkspaceSubscription prices a change first if you want the number before you apply.

Paid capacity needs a card on your account, added under Account → Billing.

Import existing applications

Everything created in the dashboard can be adopted without recreating it. The import ID is the resource's address, slash-separated:

terraform import cloady_workspace.acme acme
terraform import cloady_app.api        acme/api/production/eu1
terraform import cloady_variable.db    acme/api/production/eu1/<variable-id>
terraform import cloady_domain.api     acme/api/production/eu1/<domain-id>

Variable and domain ids come from listVars and listAppDomains in the API, or from the CLI.

How it relates to the other interfaces

The provider calls the same REST API as the dashboard, the CLI, the SDKs and the MCP server, under the same scope and role checks. It differs in what it is for: the CLI and MCP server perform actions, while Terraform converges on a declared end state and will undo drift on the next apply.

The apply loop: terraform plan refreshes live state from the Cloady REST API, diffs it against main.tf, and apply writes the declared state back — so a dashboard or CLI edit to a managed resource is overwritten on the next applyrefresh: read live statemain.tfdeclared stateterraform plandeclared vs. liveapplyCloady REST APIlive stateout of bandDashboard or CLI editon a managed resourceSuch an edit is drift: plan sees it, apply writes main.tf back over it.

That makes it a poor fit for one-off operations. Restarting a service, running a command in a container, rolling back a deploy or reading logs are actions with no desired state to converge on, and they stay with the CLI and the API.

On this page