Skip to content

frostmoln_instance (Resource) ​

Manages a compute instance in the Frostmoln platform.

Authoritative scope — who owns what on this resource, declared in internal/scopedecl and machine-checked against the schema.

Enacted and reconciled — every configurable attribute not listed below: the platform applies it, and a refresh reads the truth back.

Create-immutable — console_password, console_password_wo_version, image_id, ssh_key_names, subnet_id, vpc_id: the platform has no in-place update for it — a change destroys and re-creates the instance.

Create-immutable — instance_access: the in-guest agent is installed at first boot; enabling or disabling it later means a new instance.

Create-immutable — user_data: it runs at first boot only — changing it later cannot affect the running instance, so a change re-creates it.

Create-immutable — user_data_wo_version: it versions the first-boot user data — bumping it re-creates the instance so the new document runs.

Create-immutable — zone: the platform pins the zone at create; there is no in-place migration between zones.

Not enacted state — console_password_wo: write-only: sent, never stored or read back — the console_password_wo_version companion carries change detection. user_data_wo: write-only: sent, never stored or read back — the user_data_wo_version companion carries change detection.

Observed, not enacted — private_ip: platform-assigned from the subnet at create. public_ip: platform-attached; an association made out of band is read back, not fought. tags_all: keys set outside Terraform — in the portal, by the fm CLI, or stamped by the platform — appear only here and are kept on every apply, never removed.

Example Usage ​

terraform
resource "frostmoln_instance" "example" {
  name      = "web-server-01"
  flavor_id = data.frostmoln_flavor.medium.id
  image_id  = data.frostmoln_image.ubuntu.id
  zone      = "falkenberg"
  vpc_id    = frostmoln_vpc.example.id
  subnet_id = frostmoln_subnet.example.id

  security_groups = [frostmoln_security_group.web.id]
  ssh_key_names   = [frostmoln_ssh_key.example.name]

  # Password for the default OS user, usable only at the VNC console (SSH stays
  # key-only). Written to Terraform state in plaintext — prefer
  # console_password_wo (see the second block below).
  console_password = "change-me-at-the-console" # pragma: allowlist secret

  # Install the Frostmoln in-guest agent at first boot for `fm shell` (formerly `fm ssh`) terminal access.
  instance_access = true

  # Prefer user_data_wo (see the second block below) when this document carries
  # anything secret: user_data is written to Terraform state in plaintext.
  #
  # Cloud-init as PLAIN TEXT. Base64 is accepted by the API, but do not use it here:
  # this instance also sets ssh_key_names, console_password and instance_access, so the
  # platform merges its own cloud-config into the document — and that merge dispatches
  # on the literal #cloud-config prefix. A base64 blob does not carry it, so the blob
  # would be treated as a shell script and the document below would never run. The
  # apply still succeeds and the plan stays clean; the only evidence is in the guest's
  # cloud-init log.
  #
  # Changing user_data REPLACES the instance, and the change-detection hash is taken
  # over the value as written — so moving a live instance off base64encode(file(...))
  # plans a replacement even though the document itself is unchanged.
  #
  # cloud-init.yaml, alongside this configuration:
  #
  #   #cloud-config
  #   package_update: true
  #   packages:
  #     - nginx
  #   write_files:
  #     - path: /var/www/html/index.html
  #       content: |
  #         <h1>web-server-01</h1>
  #   runcmd:
  #     - [systemctl, enable, --now, nginx]
  #
  # That package step needs the VPC to have an outbound path. A VPC with no
  # frostmoln_gateway has no internet and no platform DNS, and cloud-init fails on
  # first boot with nothing in the Terraform plan to explain it.
  user_data = file("${path.module}/cloud-init.yaml")

  tags = {
    role        = "web"
    environment = "production"
  }
}

# Prefer the write-only form when the document carries anything you would not
# want in the state file. user_data_wo never reaches the plan or state; it needs
# Terraform 1.11 or later. Because Terraform cannot see a write-only value
# change, bumping user_data_wo_version is what launches a replacement with the
# current document — user data is only ever read at first boot, so this is the
# same lifecycle a change to user_data has.
resource "frostmoln_instance" "bootstrap" {
  name      = "worker-01"
  flavor_id = data.frostmoln_flavor.medium.id
  image_id  = data.frostmoln_image.ubuntu.id
  vpc_id    = frostmoln_vpc.example.id
  subnet_id = frostmoln_subnet.example.id

  # The document is rendered from a template with a secret injected into it.
  # Neither var.bootstrap_token nor the rendered document reaches state — which
  # is the point: where a value is CONSUMED decides whether it is stored, so
  # passing a secret through a variable into user_data would store it in full.
  user_data_wo = templatefile("${path.module}/cloud-init.yaml.tftpl", {
    bootstrap_token = var.bootstrap_token
  })

  # Bumping this REPLACES the instance with one launched from the current
  # document. Do not derive it from the document: it is not sensitive, so it is
  # printed verbatim in plan output, CI logs and PR plan comments.
  user_data_wo_version = "1"

  # console_password has a write-only form too, with the same shape and the same
  # replace-on-version-bump lifecycle — the password is seeded at first boot and
  # never again, so there is nothing an in-place update could change.
  console_password_wo         = var.console_password
  console_password_wo_version = "1"
}

Schema ​

Required ​

  • flavor_id (String) The flavor ID for the instance. Changing this resizes the instance in place: the platform powers it down for the migration and brings it back up. It does not power on an instance that was already stopped, and a guest that fails to come back up after the migration leaves the instance stopped with the resize still reported as successful — check status after a resize.
  • image_id (String) The image ID to use for the instance.
  • name (String) The name of the instance.

Optional ​

NOTE: Write-only arguments are supported in Terraform 1.11 and later.

  • console_password (String, Sensitive) Password for the default OS user, usable only at the VNC console; SSH stays key-only. Changing forces replacement — so this is not an attribute you can rotate in place; a new value destroys and recreates the instance. Stored in Terraform state in plaintext — sensitive redacts CLI output, not the state file. See the Secrets in Terraform state guide. Prefer console_password_wo, which carries the same password but is never written to state; the two are mutually exclusive.

  • console_password_wo (String, Sensitive, Write-only) Password for the default OS user, as a write-only argument: the password reaches the provider on apply and is never written to the plan or to state. Requires Terraform 1.11 or later. Mutually exclusive with console_password, and console_password_wo_version is required whenever this one is set; setting neither is fine, and is what leaves the console with no password. An empty value is rejected — omit the attribute instead. Terraform cannot see a write-only value, so it cannot detect a change to this password in either direction: changing console_password_wo_version is what makes the next apply send the current password, and it does so by REPLACING the instance — matching console_password, which is create-only because the password is only ever applied at first boot. Editing the password without touching the version does nothing.

  • console_password_wo_version (String) Change tracker for console_password_wo, required whenever that attribute is set. Changing this value REPLACES the instance and boots the replacement with the current console_password_wo — the same lifecycle a change to console_password has, since the password is only ever applied at first boot. Leaving it alone leaves the running instance untouched however much the write-only password changes. Its content is arbitrary — a counter or a date is typical — and unlike the password it is stored in state, so do not derive it from the password: a digest of it is printed verbatim in terraform plan output, and it lets anyone holding it confirm a guessed password offline. terraform import leaves this unset, so the first apply against an imported instance plans a REPLACEMENT.

  • instance_access (Boolean) Install the Frostmoln in-guest agent at first boot to enable fm shell (formerly fm ssh) terminal and fm forward access to the instance. Create-time only; the API does not return it, and terraform import leaves it unset — a true config on an imported instance plans a replacement. Enabling or disabling the agent forces replacement; unset and false both mean no agent, and switching between them does not.

  • security_groups (Set of String) The security group IDs attached to the instance. On create, the platform requires at least one security group whenever subnet_id is set — a pinned port with no security groups is refused inside the create saga (security_group_ids is required), and a create plan is refused up front by this same rule, so the failure no longer surfaces mid-apply. Set security_groups whenever you set subnet_id; the required group needs no rules. On update, changing the set replaces the instance's security groups across all its ports in place, and setting it to [] or removing the attribute clears ALL security groups (the instance falls back to default-drop — typically no inbound access). Out-of-band changes (made via the portal, CLI, or another client) are detected as drift on refresh when you set security_groups in your configuration and every port shares the same set; if you leave the attribute unset, out-of-band changes are not tracked, and if ports hold differing sets the configured value is preserved with a warning (edit per port instead).

  • ssh_key_names (Set of String) The SSH key names to inject into the instance.

  • subnet_id (String) The subnet ID for the instance.

  • tags (Map of String) Key-value tags for the instance. Merged with the provider's default_tags on every write (a key set here wins). Holds only the keys this configuration sets; the full set is in tags_all.

  • timeouts (Block, Optional) (see below for nested schema)

  • user_data (String, Sensitive) User data to provide to the instance at launch — typically a cloud-init document. The API does not return it, so the value you configure is preserved from state on refresh and a SHA256 hash is stored alongside it for change detection. The document is written to Terraform state in plaintext, so anything embedded in it — credentials, tokens, private keys — is readable by anyone who can read the state; sensitive redacts CLI output, not the state file. See the Secrets in Terraform state guide. Prefer user_data_wo, which carries the same document but is never written to state; the two are mutually exclusive.

    Write the document as plain text — file("cloud-init.yaml"), not base64encode(file(...)). Base64 is accepted by the API, but it must NOT be used on an instance that also sets ssh_key_names, console_password or instance_access. In those cases the platform merges its own cloud-config into the document, and the merge dispatches on the literal #cloud-config prefix: a base64 blob does not carry it, so the blob is treated as a shell script and combined alongside the platform's cloud-config instead of into it. The apply succeeds and the plan stays clean, but the document never runs as cloud-config and the only evidence is in the guest's cloud-init log. Plain text is correct in both directions: a #cloud-config document is merged in place, and a #! script is combined as intended. See the example below.

    The hash is taken over the value AS WRITTEN in the configuration, and any change to user_data forces the instance to be REPLACED — so moving a live instance off base64encode(file(...)) onto file(...) plans a replacement even though the document itself is unchanged. Worth doing, but do it deliberately.

    A cloud-init step that installs packages or calls an external endpoint also needs the instance's VPC to have an outbound path: declare a frostmoln_gateway for the VPC, or the step fails on first boot with no internet and no name resolution.

  • user_data_wo (String, Sensitive, Write-only) User data to provide to the instance at launch, as a write-only argument: the document reaches the provider on apply and is never written to the plan or to state, so anything embedded in it stays out of the state file. Requires Terraform 1.11 or later. Mutually exclusive with user_data, and user_data_wo_version is required whenever this one is set. Everything user_data says about the document itself applies here unchanged: write it as plain text rather than base64encode(...), and give the VPC an outbound path if cloud-init reaches the network. One rule does NOT carry over: unlike user_data, an empty document is rejected here — omit the attribute for no user data. Terraform cannot see a write-only value, so it cannot detect a change to this document in either direction: changing user_data_wo_version is what makes the next apply send the current document, and it does so by REPLACING the instance — matching user_data, which is create-only for the same reason. Editing the document without touching the version does nothing. user_data_hash is null on this path; there is no config value in state to hash, and a digest of the document does not belong in state either.

  • user_data_wo_version (String) Change tracker for user_data_wo, required whenever that attribute is set. Changing this value REPLACES the instance and launches the replacement with the current user_data_wo — the same lifecycle a change to user_data has, since user data is only ever read at first boot. Leaving it alone leaves the running instance untouched however much the write-only document changes. Its content is arbitrary — a counter or a date is typical — and unlike the document it is stored in state, so do not derive it from the document or from anything in it: a digest of the document is a digest of whatever the document carries, and it is printed verbatim in terraform plan output. terraform import leaves this unset, so the first apply against an imported instance plans a REPLACEMENT — set it to match what the instance was launched with, or accept the rebuild.

  • vpc_id (String) The VPC ID for the instance.

  • zone (String) The availability zone for the instance. If omitted, the platform selects one and records it in state.

Read-Only ​

  • created_at (String) The timestamp when the instance was created.
  • flavor_name (String) The name of the instance flavor.
  • id (String) The unique identifier of the instance.
  • image_name (String) The name of the image used to create the instance.
  • private_ip (String) The private IP address of the instance.
  • public_ip (String) The public IP address of the instance, if assigned.
  • status (String) The current status of the instance.
  • tags_all (Map of String) Every tag the platform holds on this resource: the provider's default_tags, this resource's own tags (which win on a shared key), and any key set outside Terraform — in the portal, by the fm CLI, or stamped by the platform. Keys set outside Terraform appear only here and are kept on every apply, never removed.
  • user_data_hash (String) SHA256 hash of the user data, used for change detection. Computed from the configured user_data, so it is null when the document is supplied through user_data_wo — there is no config value in state to hash, and user_data_wo_version is what carries change detection on that path.

Nested Schema for timeouts ​

Optional:

  • create (String) How long the provider waits for the create operation to converge before giving up (e.g. "45m", "2h").
  • delete (String) How long the provider waits for the delete to complete before giving up (e.g. "30m").
  • update (String) How long the provider waits for an update (resize, in-place change) to converge before giving up (e.g. "30m").