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
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 — checkstatusafter 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 —sensitiveredacts CLI output, not the state file. See the Secrets in Terraform state guide. Preferconsole_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 withconsole_password, andconsole_password_wo_versionis 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: changingconsole_password_wo_versionis what makes the next apply send the current password, and it does so by REPLACING the instance — matchingconsole_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 forconsole_password_wo, required whenever that attribute is set. Changing this value REPLACES the instance and boots the replacement with the currentconsole_password_wo— the same lifecycle a change toconsole_passwordhas, 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 interraform planoutput, and it lets anyone holding it confirm a guessed password offline.terraform importleaves 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 enablefm shell(formerlyfm ssh) terminal andfm forwardaccess to the instance. Create-time only; the API does not return it, andterraform importleaves it unset — atrueconfig on an imported instance plans a replacement. Enabling or disabling the agent forces replacement; unset andfalseboth 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 wheneversubnet_idis 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. Setsecurity_groupswhenever you setsubnet_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 setsecurity_groupsin 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'sdefault_tagson every write (a key set here wins). Holds only the keys this configuration sets; the full set is intags_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;sensitiveredacts CLI output, not the state file. See the Secrets in Terraform state guide. Preferuser_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"), notbase64encode(file(...)). Base64 is accepted by the API, but it must NOT be used on an instance that also setsssh_key_names,console_passwordorinstance_access. In those cases the platform merges its own cloud-config into the document, and the merge dispatches on the literal#cloud-configprefix: 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-configdocument 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_dataforces the instance to be REPLACED — so moving a live instance offbase64encode(file(...))ontofile(...)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_gatewayfor 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 withuser_data, anduser_data_wo_versionis required whenever this one is set. Everythinguser_datasays about the document itself applies here unchanged: write it as plain text rather thanbase64encode(...), and give the VPC an outbound path if cloud-init reaches the network. One rule does NOT carry over: unlikeuser_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: changinguser_data_wo_versionis what makes the next apply send the current document, and it does so by REPLACING the instance — matchinguser_data, which is create-only for the same reason. Editing the document without touching the version does nothing.user_data_hashis 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 foruser_data_wo, required whenever that attribute is set. Changing this value REPLACES the instance and launches the replacement with the currentuser_data_wo— the same lifecycle a change touser_datahas, 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 interraform planoutput.terraform importleaves 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'sdefault_tags, this resource's owntags(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 configureduser_data, so it is null when the document is supplied throughuser_data_wo— there is no config value in state to hash, anduser_data_wo_versionis 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").