Boot a single OpenStack compute instance (Nova) on an existing tenant network, using a Glance image and a Nova flavor. This is the "hello world" of the OpenStack Terraform provider and the template every other compute example builds on.
Primary search phrase: Terraform OpenStack instance example
flowchart LR
TF[Terraform] -->|create| VM[openstack_compute_instance_v2]
DN[data: networking_network_v2] -->|network id| VM
VM -->|attaches to| NET[(Tenant network)]
VM -->|boots from| IMG[(Glance image)]
VM -->|sized by| FLV[(Nova flavor)]
The instance looks up its network by name with a data source (so no cloud-specific UUIDs are hard-coded), then boots from the named image with the named flavor.
export OS_CLOUD=openstack # or set `cloud` in terraform.tfvars
cp terraform.tfvars.example terraform.tfvars
terraform init
terraform plan
terraform apply| Name | Description | Type | Default |
|---|---|---|---|
cloud |
clouds.yaml entry to use | string |
"openstack" |
instance_name |
Name of the instance | string |
"example-single-instance" |
flavor_name |
Flavor (size) | string |
"m1.small" |
image_name |
Glance image to boot | string |
"ubuntu-22.04" |
network_name |
Tenant network to attach | string |
"private" |
key_pair_name |
Existing key pair for SSH (optional) | string |
"" |
security_group_names |
Security groups | list(string) |
["default"] |
tags |
Instance tags | list(string) |
see variables.tf |
| Name | Description |
|---|---|
instance_id |
UUID of the instance |
instance_name |
Name of the instance |
access_ip_v4 |
First IPv4 address |
network_id |
Network the instance is attached to |
- Why this approach: Looking up the network by name with a data source keeps the example portable across clouds — no UUIDs in code. Naming flavors/images by string rather than ID keeps configs readable and reviewable.
- Common mistakes: Hard-coding network/image UUIDs; forgetting that
image_nameis resolved to animage_idat create time (handled here withignore_changes); attaching to thedefaultsecurity group and assuming SSH is open (it usually is not — see the security examples). - Scaling considerations: For more than a couple of instances use
multiple-instanceswithcount/for_each, or thecomputemodule. - Performance considerations: Pick a flavor matched to the workload; for IO-heavy workloads prefer boot-from-volume over the ephemeral root disk.
- Cost considerations: Instances bill while
ACTIVE. Tag everything (done here) so you can attribute spend, andterraform destroydev environments.
- The
defaultsecurity group is often permissive inside the project but blocks ingress from outside. Define least-privilege groups explicitly — seesecurity/security-group. - Never bake secrets into user-data; use application credentials and a metadata service or a secrets manager.
- Always inject SSH access via a managed key pair rather than passwords.
| Symptom | Likely cause | Fix |
|---|---|---|
No valid host was found |
No host has capacity for the flavor / AZ | Try a smaller flavor or another AZ; check openstack hypervisor stats show |
Quota exceeded |
Project instance/cores/RAM quota hit | Raise quota or destroy unused instances (quotas examples) |
Image <name> not found |
Wrong image_name or image not visible to the project |
openstack image list; check image visibility |
Network <name> not found |
Wrong network_name or no access |
openstack network list |
| Provider auth errors | Bad/missing clouds.yaml or OS_CLOUD |
See provider configuration |
| Apply timeout building | Slow scheduler/image copy | Increase timeouts; check Nova/Glance health |
terraform destroy