Andrew Mercer
on this page

Provider docs: https://registry.terraform.io/providers/elastic/ec/latest/docs/resources/deployment

1. Create an Elastic Cloud API key

In cloud.elastic.co → Organization → API keys → Create API key:

  • Name: terraform
  • Expiration: as short as you can tolerate
  • Roles: cloud resource access, with Editor on the deployments Terraform will manage

Copy the key (it's shown once) and export it as a Terraform variable:

export TF_VAR_ec_api_key="[ api_key ]"

2. Minimal configuration

# versions.tf
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    ec = {
      source  = "elastic/ec"
      version = "~> 0.12"
    }
  }
}

provider "ec" {
  apikey = var.ec_api_key
}
# variables.tf
variable "ec_api_key" {
  type        = string
  description = "Elastic Cloud API key"
  sensitive   = true
}

3. Get the deployment ID

cloud.elastic.co → [ deployment ] → Manage → Deployment ID.

Import with the deployment ID, not the Elasticsearch cluster ID. On some older deployments the two are identical, which is why importing with the cluster ID sometimes appears to work. On newer deployments they differ, and the cluster ID fails.

4. Import

Option A: import block (Terraform 1.5+, preferred)

This lets Terraform write the resource configuration for you:

# import.tf
import {
  to = ec_deployment.this
  id = "[ deployment_id ]"
}
terraform init
terraform plan -generate-config-out=generated.tf

Review generated.tf, move it into place, and remove any computed-only attributes the plan complains about. Then run terraform apply, which should report 1 imported, 0 changed. After that, delete import.tf.

Option B: CLI import

Write a stub resource first:

# deployment.tf
resource "ec_deployment" "this" {
  region = "[ region ]"   # e.g. azure-canadacentral, gcp-us-central1, aws-us-east-1
}
terraform import ec_deployment.this [ deployment_id ]
terraform state list            # => ec_deployment.this
terraform state show ec_deployment.this

Then copy the values from state show into the resource block until terraform plan shows no changes.

5. Example: a small deployment

resource "ec_deployment" "this" {
  name                   = "[ deployment_name ]"
  region                 = "azure-canadacentral"
  version                = "9.1.7"
  deployment_template_id = "azure-storage-optimized"

  elasticsearch = {
    autoscale = false
    ref_id    = "main-elasticsearch"
    config    = { plugins = [] }

    hot = {
      instance_configuration_id = "azure.es.datahot.edsv4"
      size                      = "1g"
      size_resource             = "memory"
      zone_count                = 1
      autoscaling               = {}
    }

    warm         = { autoscaling = {} }
    cold         = { autoscaling = {} }
    frozen       = { autoscaling = {} }
    coordinating = { autoscaling = {} }
    ml           = { autoscaling = {} }
  }

  kibana = {
    elasticsearch_cluster_ref_id = "main-elasticsearch"
    ref_id                       = "main-kibana"
    instance_configuration_id    = "azure.kibana.fsv2"
    size                         = "1g"
    size_resource                = "memory"
    zone_count                   = 1
  }
}

Template and instance configuration IDs vary by cloud provider and region. Copy them from terraform state show rather than guessing.

The empty { autoscaling = {} } tiers stop the provider from showing a diff for tiers that exist in the template but have no capacity.

Troubleshooting

# Save the plan and inspect exactly what would change
terraform plan -out=plan.out
terraform show -json plan.out | jq '.resource_changes[] | {address, actions: .change.actions}'

# user_settings_yaml diffs are a common culprit: compare against state
terraform state show ec_deployment.this | sed -n '/user_settings_yaml/,/EOT/p'