iaas is a native OpenTofu / Terraform provider for the Hypervisor.io
IaaS platform. Declare instances, VPCs, load balancers, managed Kubernetes clusters, databases,
block & object storage, DNS, VPN and firewalls in HCL - and let tofu apply
converge the real infrastructure, with UUID-based import for everything you provisioned by hand.
provider"iaas" {
# IAAS_API_ENDPOINT / IAAS_API_TOKEN also work
endpoint = "https://panel.example.com/api"
}
resource"iaas_ssh_key""me" {
name = "laptop"
public_key = "ssh-ed25519 AAAA..."
}
resource"iaas_instance""web" {
location_id = data.iaas_location.nyc.id
plan_id = data.iaas_plan.small.id
image_id = data.iaas_image.ubuntu.id
ssh_keys = [iaas_ssh_key.me.id]
}
$ tofu apply+ iaas_ssh_key.me will be created+ iaas_instance.web will be created (deploy task converging…) ✓ running
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Getting started
From zero to a running instance
Four steps: pin the provider, authenticate, configure, apply. The one thing that
catches everyone out is the IP lock below - read that before your first tofu plan.
Require the provider
The provider address is hypervisor-io/iaas, protocol v6, requiring
OpenTofu / Terraform ≥ 1.6. It is not yet published to a public registry - until it is,
build it locally and wire in a dev override (see the snippet's second block).
terraform {
required_version = ">= 1.6"
required_providers {
iaas = {
source = "hypervisor-io/iaas"# version = "~> 0.1" # pin once published to the registry
}
}
}
# Pre-registry / local build workflow:# git clone https://github.com/hypervisor-io/terraform-provider-iaas.git# cd terraform-provider-iaas && make build # produces ./terraform-provider-iaas## cat > ~/.terraformrc <<'RC'# provider_installation {# dev_overrides { "hypervisor-io/iaas" = "/absolute/path/to/terraform-provider-iaas" }# direct {}# }# RC# export TF_CLI_CONFIG_FILE=~/.terraformrc # a dev override skips `tofu init`
Authenticate
Every request carries a Bearer token. Prefer environment variables over hardcoding it in
HCL - the token is sensitive and, critically, IP-locked (next step).
# base URL, including the /api suffixexport IAAS_API_ENDPOINT="https://panel.example.com/api"# issued in the control panel, registered against THIS host's egress IPexport IAAS_API_TOKEN="your-ip-locked-token"
Configure the provider block
Attributes are optional and fall back to the environment variables above. Set
request_timeout for slow networks; only ever set
insecure = true against a staging box with a self-signed cert.
provider"iaas" {
endpoint = "https://panel.example.com/api"# or IAAS_API_ENDPOINT
token = var.iaas_token # or IAAS_API_TOKEN - prefer the env var
request_timeout = 30 # seconds, optional (default 30)
insecure = false # optional, staging self-signed certs only
}
Apply your first resource
The canonical quickstart: an SSH key injected into a fresh Cloud Service instance, sized
and placed via three catalog data-source lookups so nothing is a hardcoded UUID.
data"iaas_location""nyc" { name = "nyc" }
data"iaas_plan""small" {
location_id = data.iaas_location.nyc.id
name = "s1.small"
}
data"iaas_image""ubuntu" { name = "Ubuntu 24.04" }
resource"iaas_ssh_key""deploy" {
name = "deploy key"
public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... deploy@example.com"
}
resource"iaas_instance""web" {
# immutable placement - changing any of these forces a new instance
location_id = data.iaas_location.nyc.id
plan_id = data.iaas_plan.small.id
image_id = data.iaas_image.ubuntu.id
# write-only deploy fields, echoed from config (the API never returns them)
ssh_keys = [iaas_ssh_key.deploy.id]
timezone = "UTC"# updatable in place
hostname = "web-01"
display_name = "Web 01"
timeouts {
create = "30m"# deploy is async - this instance is the golden async example
delete = "30m"
}
}
output"instance_public_ip" { value = iaas_instance.web.primary_public_ip }
🔒 The IP lock - read this before anything else
IAAS_API_TOKEN is a Bearer token that the platform validates against
the exact egress IP address it was registered with. A request from any other
source IP is rejected with 401/403 -
regardless of whether the token itself is valid.
In practice, that means you run tofu / terraform
from one of these, and only these:
A static CI runner with a fixed, known egress IP (a self-hosted runner or a
provider that offers static IPs) - register the token against that IP.
A bastion host you SSH into to run applies, with a stable public IP.
A workstation on a static IP or a VPN/NAT egress that doesn't rotate.
Most hosted CI (GitHub-hosted runners, etc.) is not supported - their egress IPs
are dynamic and unregistrable. Route applies through a static runner or bastion instead.
The token may also be scoped to a subuser with a limited permission set, in which
case in-scope requests from the right IP still fail with 403 if the
action is outside that subuser's permissions - see Users & admins.
Users & admins
One provider, two kinds of token
The provider always speaks to one IaaS account at a time, scoped entirely by the
token you give it. What that token can touch depends on who issued it.
Account owner
A token issued directly against your own account has full, "admin-scoped" access to
everything that account owns: every instance, VPC, cluster, database, and bucket it holds.
This is the common case for a single team managing its own infrastructure, and for a
reseller managing several separate customer accounts - each managed account
gets its own provider "iaas" {} block (or workspace) with its own
IP-locked, account-scoped token. There is no single "super-token" that spans multiple accounts.
Subuser / scoped token
Account owners can create subusers with a restricted permission set in the control panel,
then issue an IP-locked token to that subuser for Terraform to use. Routes are gated per
permission - e.g. monitoring.view / monitoring.manage
for notification channels & alert rules, autoscaling.view /
.manage / .delete for autoscaling groups,
kubernetes.kubeconfig_download for the kubeconfig data source. An
operation outside the token's scope fails with 403 even from the
correct IP.
Permission scope
Grants
Governs
monitoring.view
Read notification channels & alert rules
iaas_notification_channel, iaas_alert_rule
monitoring.manage
Create / update / delete the above
iaas_notification_channel, iaas_alert_rule
autoscaling.view
Read autoscaling groups & policies
iaas_autoscaling_group, iaas_autoscaling_policy
autoscaling.manage / .delete
Create/update/pause/resume, and destroy
iaas_autoscaling_group, iaas_autoscaling_policy
kubernetes.kubeconfig_download
Fetch a fresh admin kubeconfig
iaas_kubernetes_kubeconfig data source
Deliberately not modelled
The provider does not manage iaas_api_token, roles, or subusers
themselves. Tokens are IP-locked, so a token an apply just created
would not be usable from a different egress IP - a bootstrap paradox. Create tokens, roles and
subusers manually in the control panel, then feed the resulting IP-locked token
to this provider.
Reference
Every resource & data source
All 56 registered types, grouped by domain. Search narrows by name or purpose;
the chips narrow by group. Cards marked NEW were added this
cycle. Full per-attribute reference for every type is generated under docs/
in the provider source via tfplugindocs.
Compute & instances · 8 resources
iaas_instanceresource
Provisions a Cloud Service VM: the record is created synchronously, the OS deployed asynchronously via a task the provider waits on. Plan, location and network placement are immutable; hostname/display_name update in place.
Captures a custom image (snapshot) from a running instance. Creation converges asynchronously to "available"; every input forces a new resource (no update route).
Example
resource"iaas_image""web_snapshot" {
instance_id = iaas_instance.web.id
name = "web-prod-2026-07"
}
iaas_instance_backup_policyresource
A named backup schedule & retention policy; instances attach to it via the instance_ids set, in place.
iaas_docker_deploymentresourceNEW
Deploys a catalog Docker app or a custom Compose file onto an instance, installing the Docker engine automatically first if needed. No update route - every change replaces the deployment.
A self-healing fleet kept between min_instances and max_instances. Launch placement is fixed at create; template, bounds and security groups update in place; deletion is async.
A CPU or memory-driven scale-up/down policy attached to an autoscaling group. Child resource - the group_id is part of the API path.
Networking - VPC & IP · 6 resources
iaas_vpcresource
An isolated layer-2 private network with a server-assigned VNI. No update endpoint - every attribute is immutable.
Example
resource"iaas_vpc""main" {
name = "prodnet01"
cidr = "10.0.0.0/24"
hypervisor_group_id = "00000000-..."
}
resource"iaas_vpc_subnet""web" {
vpc_id = iaas_vpc.main.id
cidr = "10.0.0.0/24"
type = "public"
name = "web tier"
}
iaas_vpc_subnetresource
A subnet inside a VPC; gateway and netmask are server-derived from the CIDR. Only the name updates in place - changing CIDR or type replaces it.
iaas_instance_vpc_attachmentresourceNEW
Attaches an instance's single VPC network interface to a subnet. Auto-assigns the primary IP; additional_ips manages secondaries in place. Destroying releases the whole interface.
Reserves a long-lived public IPv4 address from a location's pool, attachable to / detachable from instances. Billing must be enabled; no update endpoint (replace on change).
A named, stateful firewall rule set attachable to instances. Rules (CIDR, IP-set, or remote-group based) are managed as an inline order-independent set; instance attachments update in place.
An HAProxy load balancer backed by a dedicated instance, deployed in public mode (location) or VPC mode (subnet). Asynchronous; every input is immutable - frontends/backends/targets are separate resources.
A backend pool of a load balancer, selecting the balancing algorithm and mode. Child of the load balancer; import as <lb_id>/<backend_id>.
iaas_lb_targetresource
A backend member - target_ip:target_port, optionally linked to an instance - with weight and enabled toggle. Import as a 3-part composite id.
iaas_lb_frontendresource
A listener (port + protocol) that accepts traffic and routes to a default backend; attach a certificate here for HTTPS.
iaas_lb_routing_ruleresource
An L7 routing rule on a frontend that directs matching traffic to a specific backend. Import as a 3-part composite id.
iaas_lb_certificateresource
A manually-uploaded PEM certificate + private key attached to an HTTPS frontend. Immutable - any change rotates (replaces) it.
VPN · 3 resources
iaas_vpn_gatewayresource
A WireGuard endpoint VM deployed into a VPC's public subnet, giving remote clients and sites encrypted access to the VPC. One per VPC; asynchronous; entirely immutable.
Example
resource"iaas_vpn_gateway""example" {
vpc_id = iaas_vpc.example.id
vpngw_plan_id = "11111111-..."
vpc_subnet_id = iaas_vpc_subnet.public.id # write-only, must be a PUBLIC subnet
name = "vpngw-prod"
}
iaas_vpn_peerresource
A WireGuard peer on a gateway: a road-warrior client or a third-party site-to-site connection you configure yourself (public_key/endpoint/allowed_ips).
iaas_vpn_peeringresourceNEW
A server-derived WireGuard peering between two of your own VPN gateways in different VPCs. The server generates a symmetric pair of peer rows; model each side with its own resource.
Example
resource"iaas_vpn_peering""a_to_b" {
vpn_gateway_id = var.vpc_a_gateway_id
remote_gateway_id = var.vpc_b_gateway_id
}
# Model the mirror side too, to own both rows in Terraform:resource"iaas_vpn_peering""b_to_a" {
vpn_gateway_id = var.vpc_b_gateway_id
remote_gateway_id = var.vpc_a_gateway_id
}
DNS · 3 resources
iaas_dns_zoneresource
An internal, per-VPC CoreDNS zone attachable to one or more VPCs, where its records become resolvable. Name is immutable; description and attached VPCs update in place.
A named group of same-type records inside a zone, sharing a routing policy (simple / weighted / multivalue / failover) and TTL.
iaas_dns_recordresource
A single validated record value within a set (A/AAAA/CNAME/SRV), with an optional active health check that withholds unhealthy records from resolution.
Databases · 4 resources
iaas_managed_databaseresource
A managed MySQL/MariaDB/PostgreSQL instance deployed into a VPC subnet. Asynchronous; plan resizes and engine_version upgrades happen in place; a billed add-on.
A read replica of a managed database, inheriting engine and version from its primary. Plan can be resized in place; everything else forces replacement.
iaas_db_parameter_groupresource
A named, engine-scoped map of database configuration parameters (suffix-free keys only, e.g. max_connections).
iaas_db_backup_policyresource
An off-host S3-based backup policy (schedule, optional point-in-time recovery, retention) attached to managed databases via database_ids.
Storage - volumes & S3 · 4 resources
iaas_volumeresource
A plan-sized block storage volume. Async-provisioned; resize by switching to a larger plan; attach/detach an instance in place.
A standalone S3 access key whose secret is returned only once, at creation. No delete endpoint - destroying only removes it from state.
Kubernetes · 4 resources
iaas_kubernetes_clusterresource
A managed cluster: a 1- or 3-node control plane behind a dedicated CP load balancer, plus a default worker pool. Async multi-stage provisioning; version bumps drive a staged in-place upgrade.
Example
resource"iaas_kubernetes_cluster""prod" {
name = "prod"; slug = "prod"
hypervisor_group_id = "22222222-..."
vpc_id = "33333333-..."
cp_vpc_subnet_id = "44444444-..."# must be private
worker_vpc_subnet_id = "55555555-..."
kubernetes_version_id = "66666666-..."# bump in place to upgrade
control_node_count = 3
cp_instance_plan_id = "77777777-..."
cp_lb_plan_id = "88888888-..."
worker_instance_plan_id = "99999999-..."
worker_count = 3
timeouts { create = "45m"; update = "45m"; delete = "30m" }
}
iaas_kubernetes_node_poolresource
A worker node pool with its own plan, min/max size, labels, taints and autoscaling toggle. Also manages a cluster's default pool via import.
iaas_kubernetes_ssl_certificateresourceNEW
A custom PEM or Let's Encrypt TLS certificate on a cluster's control-plane load balancer. No update route - any change rotates the certificate.
A single firewall rule on one of a cluster's auto-provisioned groups (lb / cp / worker). No update - changing a field replaces the rule. Import as a 3-part composite id.
Operations & alerting · 2 resources
iaas_notification_channelresource
A Slack, Discord, Telegram or generic webhook delivery target that alert rules dispatch through. Config is sensitive and round-trips encrypted.
iaas_alert_ruleresource
A metric-threshold alert that fires through attached notification channels when a resource metric crosses a threshold for a configured duration.
Account & project · 2 resources
iaas_projectresource
A logical grouping label for organizing instances, VPCs, load balancers, buckets and databases. Fully updatable in place.
iaas_project_assignmentresourceNEW
Assigns one resource (instance / vpc / load_balancer / s3_bucket / managed_database) to a project as a standalone tagging action - not an attribute on the resource itself.
Resolves a deploy location (hypervisor group) by slug or display name.
iaas_plandata source
Resolves an instance plan by name within a location (optionally disambiguated by plan group).
iaas_imagedata source
Resolves an OS image by exact name, falling back to a unique case-insensitive substring match. Distinct from the iaas_image resource.
iaas_isodata source
Resolves a mountable ISO by exact name.
Data sources - Kubernetes · 7 data sources
iaas_kubernetes_versiondata source
Resolves an active Kubernetes version by semantic version string, e.g. 1.31.4.
iaas_kubernetes_regiondata source
Resolves a region eligible to host a cluster - only regions with Kubernetes + VPC + Load Balancer enabled are returned.
iaas_kubernetes_plandata source
Resolves a cluster-create plan by name and kind (worker / cp / lb).
iaas_kubernetes_vpcdata sourceNEW
Resolves a VPC eligible to host a cluster by name; exposes has_nat_gateway since a private control plane needs NAT egress.
Example
data"iaas_kubernetes_vpc""prod" { name = "prod-vpc" }
data"iaas_kubernetes_subnet""cp" {
vpc_id = data.iaas_kubernetes_vpc.prod.id
name = "cp-subnet"
type = "private"
}
iaas_kubernetes_subnetdata sourceNEW
Resolves a subnet within a VPC by name (and optional type) for use as cp_vpc_subnet_id / worker_vpc_subnet_id.
iaas_kubernetes_kubeconfigdata source
Downloads a fresh admin kubeconfig for a running cluster. Sensitive - every read mints a new client certificate.
iaas_kubernetes_autoscaler_manifestdata source
Renders the self-contained cluster-autoscaler manifest, embedding a freshly-minted controller JWT. Sensitive - every read rotates the active token.
Data sources - account & VPN · 2 data sources
iaas_accountdata sourceNEW
Returns the caller's own account (whoami) - a singleton with no input filter. Referencing it validates the token & IP-lock during plan/apply.
Example
data"iaas_account""current" {}
output"is_admin" { value = data.iaas_account.current.is_admin }
iaas_vpn_peer_configdata source
Downloads the rendered WireGuard client .conf for a road-warrior peer. Sensitive; only valid for road_warrior peers.
No matching types. Try a different search term or choose "All".
Common patterns
How this provider actually behaves
Four things every resource in this catalog shares. Learn these once and every
type behaves predictably.
Async convergence & timeouts
Anything backed by a hypervisor task or a slave-provisioned VM (instances, load balancers,
managed databases, Kubernetes clusters, NAT/VPN gateways, volumes, images) creates
synchronously, then polls until the platform task reports done before
apply returns. Tune the wait per resource:
Every resource is UUID-identified. Top-level resources import with a single UUID; children
of a parent path (backends, targets, replicas, subnets…) import with a composite id joined
by / - 2 or 3 segments depending on nesting depth.
Almost nothing is standalone - VPCs feed subnets, subnets feed instances and load balancers,
instances feed targets, catalog data sources resolve every UUID by name. Build the graph with
real references and tofu plan orders the applies for you: