Simple VM with Floating IP
Simple VM with Floating IP
This pattern composes Compute and Network.
What this template does#
Provisions a single compute instance with:
- A private network, subnet (with DNS resolvers), and a router to the external network
- A neutron port that binds the instance to the private network
- A security group allowing SSH (port 22) and HTTP (port 80)
- A volume-backed boot disk, so the instance works with zero-disk flavors
- A floating IP for SSH and application access
The instance uses an existing SSH keypair named by the key_name variable, so you keep the private key. This is the simplest reliable pattern for a publicly reachable VM on Quake AI.
Parameters#
| Parameter | Description | Default |
|---|---|---|
flavor_name | Instance size | s1a.small |
image_name | Operating system image | Ubuntu-24.04 |
key_name | SSH keypair name (must already exist in your project) | No default |
instance_name | Display name for the instance | simple-vm |
external_network | External network for the router gateway and floating IP | PublicEphemeral |
private_cidr | CIDR for the private subnet | 10.10.10.0/24 |
When to use this pattern#
Reach for this pattern when you need one publicly reachable VM with a private network, security group, and floating IP. Choose Development Environment when you want multiple subnets, a bastion, and shared storage for a team sandbox.
Estimated cost#
Monthly cost estimate
Pricing calculator ↗Sized as a custom package on shared vCPU.
Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.
What each resource is for
Vm
s1a.small · 2 shared vCPU, 2 GiB RAM, 0.5 Gbps
Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.
Included in baseline
s1a.small
2 shared vCPU, 2 GiB RAM, 0.5 Gbps
Compute + RAM rate basis
2 vCPU + 2 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.
Block storage (20 GiB)
20 GiB at $0.08/GiB/mo
Public IP (included)
1 included with the custom package
Package promotional discount
Flat −$5.00/mo on the custom package (same promotion as named plans).
Included at no charge
These line items are zero on Quake AI. Many other providers meter them separately.
Data transfer (inbound and outbound)
Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.
AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.
Learn morePrivate networking
Private networks, subnets, Neutron routers, and security groups are included with the plan.
VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.
Control-plane API requests
OpenStack API calls for provisioning and management are included.
Some managed services on other clouds meter API calls or charge for premium control-plane features.
Pricing data last validated: . For current rates, check quake.ai/pricing.
Template source#
Show source (6 files)Hide source
data "openstack_images_image_v2" "os" {
name = var.image_name
most_recent = true
}
data "openstack_networking_network_v2" "external" {
name = var.external_network
}
resource "openstack_networking_network_v2" "private" {
name = "${var.instance_name}-net"
admin_state_up = true
}
resource "openstack_networking_subnet_v2" "private" {
name = "${var.instance_name}-subnet"
network_id = openstack_networking_network_v2.private.id
cidr = var.private_cidr
ip_version = 4
dns_nameservers = ["1.1.1.1", "8.8.8.8"]
}
resource "openstack_networking_router_v2" "main" {
name = "${var.instance_name}-router"
external_network_id = data.openstack_networking_network_v2.external.id
}
resource "openstack_networking_router_interface_v2" "private" {
router_id = openstack_networking_router_v2.main.id
subnet_id = openstack_networking_subnet_v2.private.id
}
resource "openstack_networking_secgroup_v2" "vm" {
name = "${var.instance_name}-sg"
description = "Allow SSH and HTTP"
}
resource "openstack_networking_secgroup_rule_v2" "ssh" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 22
port_range_max = 22
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.vm.id
}
resource "openstack_networking_secgroup_rule_v2" "http" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 80
port_range_max = 80
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.vm.id
}
resource "openstack_compute_instance_v2" "vm" {
name = var.instance_name
flavor_name = var.flavor_name
key_pair = var.key_name
block_device {
uuid = data.openstack_images_image_v2.os.id
source_type = "image"
destination_type = "volume"
volume_size = 20
boot_index = 0
delete_on_termination = true
}
network {
port = openstack_networking_port_v2.vm.id
}
}
resource "openstack_networking_port_v2" "vm" {
name = "${var.instance_name}-port"
network_id = openstack_networking_network_v2.private.id
security_group_ids = [openstack_networking_secgroup_v2.vm.id]
fixed_ip {
subnet_id = openstack_networking_subnet_v2.private.id
}
depends_on = [openstack_networking_router_interface_v2.private]
}
resource "openstack_networking_floatingip_v2" "vm" {
pool = var.external_network
}
resource "openstack_networking_floatingip_associate_v2" "vm" {
floating_ip = openstack_networking_floatingip_v2.vm.address
port_id = openstack_networking_port_v2.vm.id
}
variable "flavor_name" {
description = "Instance size"
type = string
default = "s1a.small"
}
variable "image_name" {
description = "Operating system image"
type = string
default = "Ubuntu-24.04"
}
variable "key_name" {
description = "SSH keypair name (must already exist in your project)"
type = string
}
variable "instance_name" {
description = "Display name for the instance"
type = string
default = "simple-vm"
}
variable "external_network" {
description = "Shared external network for router gateway and floating IPs; defaults to PublicEphemeral (quickstart-aligned). Set PublicStatic in tfvars for a persisted floating IP."
type = string
default = "PublicEphemeral"
}
variable "private_cidr" {
description = "CIDR for the private tenant network the VM lives in"
type = string
default = "10.10.10.0/24"
}
output "instance_id" {
description = "ID of the compute instance"
value = openstack_compute_instance_v2.vm.id
}
output "floating_ip" {
description = "Public floating IP address"
value = openstack_networking_floatingip_v2.vm.address
}
output "private_ip" {
description = "Private IP address of the instance"
value = openstack_compute_instance_v2.vm.access_ip_v4
}
terraform {
required_version = ">= 1.6.0"
required_providers {
openstack = {
source = "terraform-provider-openstack/openstack"
version = "~> 2.0"
}
}
}
provider "openstack" {}
# Required: SSH keypair must already exist in your project
key_name = "YOUR_KEY_NAME"
# flavor_name = "s1a.small"
# image_name = "Ubuntu-24.04"
# instance_name = "simple-vm"
# private_cidr = "10.10.10.0/24"
# Simple VM
Single compute instance with floating IP and security group.
**Network class:** throwaway / quickstart-aligned — `external_network` defaults to `PublicEphemeral`; use `PublicStatic` in tfvars when you need a persisted floating IP.
## Prerequisites
- OpenTofu >= 1.6.0 or Terraform >= 1.6.0
- Quake AI account with OpenStack credentials
- An existing SSH keypair in your project (the value of `key_name` must match that keypair)
## Usage
1. Clone or copy this template directory
2. Copy `terraform.tfvars.example` to `terraform.tfvars` and fill in your values
3. Source your OpenStack credentials: `source openrc.sh`
4. Initialize: `tofu init`
5. Preview: `tofu plan`
6. Apply: `tofu apply`
## Variables
| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `key_name` | string | yes | n/a | SSH keypair name (must already exist in your project) |
| `flavor_name` | string | no | `s1a.small` | Instance size |
| `image_name` | string | no | `Ubuntu-24.04` | Operating system image |
| `instance_name` | string | no | `simple-vm` | Display name for the instance |
| `external_network` | string | no | `PublicEphemeral` | Defaults to ephemeral quickstart path; set `PublicStatic` for persisted FIP |
| `private_cidr` | string | no | `10.10.10.0/24` | CIDR for the private tenant network the VM lives in |
## Documentation
Full documentation: [Simple VM template](/docs/automation/templates/simple-vm)
Variations
Deploy walkthroughValidated variants of this template, each adding one capability the base does not include.
- Backups
simple-vm-backupsScheduled tarball backups to Object Storage
Show source and downloadHide source
7 files. Download the zip or copy any file.Download simple-vm-backups.zipmain.tfHCLdata "openstack_images_image_v2" "os" { name = var.image_name most_recent = true } data "openstack_networking_network_v2" "external" { name = var.external_network } resource "aws_s3_bucket" "backups" { bucket = var.backup_bucket_name } resource "aws_s3_bucket_acl" "backups" { bucket = aws_s3_bucket.backups.id acl = "private" } resource "openstack_networking_network_v2" "private" { name = "${var.instance_name}-net" admin_state_up = true } resource "openstack_networking_subnet_v2" "private" { name = "${var.instance_name}-subnet" network_id = openstack_networking_network_v2.private.id cidr = var.private_cidr ip_version = 4 dns_nameservers = ["1.1.1.1", "8.8.8.8"] } resource "openstack_networking_router_v2" "main" { name = "${var.instance_name}-router" external_network_id = data.openstack_networking_network_v2.external.id } resource "openstack_networking_router_interface_v2" "private" { router_id = openstack_networking_router_v2.main.id subnet_id = openstack_networking_subnet_v2.private.id } resource "openstack_networking_secgroup_v2" "vm" { name = "${var.instance_name}-sg" description = "Allow SSH and HTTP" } resource "openstack_networking_secgroup_rule_v2" "ssh" { direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = 22 port_range_max = 22 remote_ip_prefix = "0.0.0.0/0" security_group_id = openstack_networking_secgroup_v2.vm.id } resource "openstack_networking_secgroup_rule_v2" "http" { direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = 80 port_range_max = 80 remote_ip_prefix = "0.0.0.0/0" security_group_id = openstack_networking_secgroup_v2.vm.id } resource "openstack_compute_instance_v2" "vm" { name = var.instance_name flavor_name = var.flavor_name key_pair = var.key_name user_data = templatefile("${path.module}/cloud-init/vm.yaml", { s3_endpoint = var.s3_endpoint s3_region = var.s3_region s3_access_key = var.s3_access_key s3_secret_key = var.s3_secret_key backup_bucket = aws_s3_bucket.backups.id backup_schedule = var.backup_schedule }) block_device { uuid = data.openstack_images_image_v2.os.id source_type = "image" destination_type = "volume" volume_size = 20 boot_index = 0 delete_on_termination = true } network { port = openstack_networking_port_v2.vm.id } } resource "openstack_networking_port_v2" "vm" { name = "${var.instance_name}-port" network_id = openstack_networking_network_v2.private.id security_group_ids = [openstack_networking_secgroup_v2.vm.id] fixed_ip { subnet_id = openstack_networking_subnet_v2.private.id } depends_on = [openstack_networking_router_interface_v2.private] } resource "openstack_networking_floatingip_v2" "vm" { pool = var.external_network } resource "openstack_networking_floatingip_associate_v2" "vm" { floating_ip = openstack_networking_floatingip_v2.vm.address port_id = openstack_networking_port_v2.vm.id }variables.tfHCLvariable "flavor_name" { description = "Instance size" type = string default = "s1a.small" } variable "image_name" { description = "Operating system image" type = string default = "Ubuntu-24.04" } variable "key_name" { description = "SSH keypair name (must already exist in your project)" type = string } variable "instance_name" { description = "Display name for the instance" type = string default = "simple-vm" } variable "external_network" { description = "Shared external network for router gateway and floating IPs; defaults to PublicStatic (persisted FIP / production pattern). Override with PublicEphemeral for ephemeral demos." type = string default = "PublicStatic" } variable "private_cidr" { description = "CIDR for the private tenant network the VM lives in" type = string default = "10.10.10.0/24" } variable "s3_access_key" { description = "EC2-compatible access key for Object Storage" type = string sensitive = true } variable "s3_secret_key" { description = "EC2-compatible secret key for Object Storage" type = string sensitive = true } variable "backup_bucket_name" { description = "S3 bucket name for scheduled VM backups" type = string } variable "s3_endpoint" { description = "Quake AI S3-compatible endpoint URL" type = string default = "https://object.us-east-1.rumble.cloud" } variable "s3_region" { description = "S3 region identifier passed to the AWS provider" type = string default = "us-east-1" } variable "backup_schedule" { description = "Cron schedule for tarball backups to Object Storage" type = string default = "0 3 * * *" }outputs.tfHCLoutput "instance_id" { description = "ID of the compute instance" value = openstack_compute_instance_v2.vm.id } output "floating_ip" { description = "Public floating IP address" value = openstack_networking_floatingip_v2.vm.address } output "private_ip" { description = "Private IP address of the instance" value = openstack_compute_instance_v2.vm.access_ip_v4 } output "backup_bucket" { description = "Object Storage bucket name for scheduled backups" value = aws_s3_bucket.backups.id }versions.tfHCLterraform { required_version = ">= 1.6.0" required_providers { openstack = { source = "terraform-provider-openstack/openstack" version = "~> 2.0" } aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "openstack" {} provider "aws" { region = var.s3_region access_key = var.s3_access_key secret_key = var.s3_secret_key skip_credentials_validation = true skip_metadata_api_check = true skip_requesting_account_id = true endpoints { s3 = var.s3_endpoint } }terraform.tfvars.exampleHCL# Required: SSH keypair must already exist in your project key_name = "YOUR_KEY_NAME" # flavor_name = "s1a.small" # image_name = "Ubuntu-24.04" # instance_name = "simple-vm" # external_network = "PublicStatic" # private_cidr = "10.10.10.0/24"cloud-init/vm.yamlYAML#cloud-config package_update: true packages: - python3-pip write_files: - path: /usr/local/bin/vm-backup.sh permissions: "0755" content: | #!/bin/bash set -e export AWS_ACCESS_KEY_ID="${s3_access_key}" export AWS_SECRET_ACCESS_KEY="${s3_secret_key}" export AWS_DEFAULT_REGION="${s3_region}" ts=$(date +%Y%m%d-%H%M%S) tar -czf /tmp/vm-backup-$ts.tar.gz /etc /var/www /home 2>/dev/null || tar -czf /tmp/vm-backup-$ts.tar.gz /etc /home aws s3 cp /tmp/vm-backup-$ts.tar.gz s3://${backup_bucket}/ --endpoint-url ${s3_endpoint} rm -f /tmp/vm-backup-$ts.tar.gz - path: /etc/cron.d/vm-backup owner: root:root permissions: "0644" content: | ${backup_schedule} root /usr/local/bin/vm-backup.sh runcmd: - pip3 install --break-system-packages awscliREADME.mdMarkdown# Simple VM Single compute instance with floating IP and security group. **Network class:** production — `external_network` defaults to `PublicStatic` for persisted floating IPs and multi-tier stacks; override with `PublicEphemeral` for ephemeral demos. ## Prerequisites - OpenTofu >= 1.6.0 or Terraform >= 1.6.0 - Quake AI account with OpenStack credentials - An existing SSH keypair in your project (the value of `key_name` must match that keypair) ## Usage 1. Clone or copy this template directory 2. Copy `terraform.tfvars.example` to `terraform.tfvars` and fill in your values 3. Source your OpenStack credentials: `source openrc.sh` 4. Initialize: `tofu init` 5. Preview: `tofu plan` 6. Apply: `tofu apply` ## Variables | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `key_name` | string | yes | n/a | SSH keypair name (must already exist in your project) | | `flavor_name` | string | no | `s1a.small` | Instance size | | `image_name` | string | no | `Ubuntu-24.04` | Operating system image | | `instance_name` | string | no | `simple-vm` | Display name for the instance | | `external_network` | string | no | `PublicStatic` | Persisted FIP / production default; override with `PublicEphemeral` for demos | | `private_cidr` | string | no | `10.10.10.0/24` | CIDR for the private tenant network the VM lives in | ## Documentation Full documentation: [Simple VM template](/docs/automation/templates/simple-vm) - Monitoring
simple-vm-hardened+$40/mo over the baseFail2ban hardening plus Prometheus and Grafana visibility
Show source and downloadHide source
9 files. Download the zip or copy any file.Download simple-vm-hardened.zipmain.tfHCLdata "openstack_images_image_v2" "os" { name = var.image_name most_recent = true } data "openstack_networking_network_v2" "external" { name = var.external_network } resource "openstack_networking_network_v2" "private" { name = "${var.instance_name}-net" admin_state_up = true } resource "openstack_networking_subnet_v2" "private" { name = "${var.instance_name}-subnet" network_id = openstack_networking_network_v2.private.id cidr = var.private_cidr ip_version = 4 dns_nameservers = ["1.1.1.1", "8.8.8.8"] } resource "openstack_networking_router_v2" "main" { name = "${var.instance_name}-router" external_network_id = data.openstack_networking_network_v2.external.id } resource "openstack_networking_router_interface_v2" "private" { router_id = openstack_networking_router_v2.main.id subnet_id = openstack_networking_subnet_v2.private.id } resource "openstack_networking_secgroup_v2" "vm" { name = "${var.instance_name}-sg" description = "Allow SSH and HTTP" } resource "openstack_networking_secgroup_rule_v2" "ssh" { direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = 22 port_range_max = 22 remote_ip_prefix = "0.0.0.0/0" security_group_id = openstack_networking_secgroup_v2.vm.id } resource "openstack_networking_secgroup_rule_v2" "http" { direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = 80 port_range_max = 80 remote_ip_prefix = "0.0.0.0/0" security_group_id = openstack_networking_secgroup_v2.vm.id } resource "openstack_networking_secgroup_rule_v2" "node_exporter" { direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = 9100 port_range_max = 9100 remote_ip_prefix = var.private_cidr security_group_id = openstack_networking_secgroup_v2.vm.id } resource "openstack_compute_instance_v2" "vm" { name = var.instance_name flavor_name = var.flavor_name key_pair = var.key_name user_data = file("${path.module}/cloud-init/vm.yaml") block_device { uuid = data.openstack_images_image_v2.os.id source_type = "image" destination_type = "volume" volume_size = 20 boot_index = 0 delete_on_termination = true } network { port = openstack_networking_port_v2.vm.id } } resource "openstack_networking_port_v2" "vm" { name = "${var.instance_name}-port" network_id = openstack_networking_network_v2.private.id security_group_ids = [openstack_networking_secgroup_v2.vm.id] fixed_ip { subnet_id = openstack_networking_subnet_v2.private.id } depends_on = [openstack_networking_router_interface_v2.private] } resource "openstack_networking_floatingip_v2" "vm" { pool = var.external_network } resource "openstack_networking_floatingip_associate_v2" "vm" { floating_ip = openstack_networking_floatingip_v2.vm.address port_id = openstack_networking_port_v2.vm.id } locals { hardened_scrape_yaml = " - job_name: \"simple-vm\"\n static_configs:\n - targets:\n - ${yamlencode("${openstack_compute_instance_v2.vm.network[0].fixed_ip_v4}:9100")}" } resource "openstack_networking_secgroup_v2" "prometheus" { name = "${var.instance_name}-prometheus-sg" description = "Prometheus 9090 from private subnet; SSH from anywhere" } resource "openstack_networking_secgroup_rule_v2" "prometheus_http" { direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = 9090 port_range_max = 9090 remote_ip_prefix = var.private_cidr security_group_id = openstack_networking_secgroup_v2.prometheus.id } resource "openstack_networking_secgroup_rule_v2" "prometheus_ssh" { direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = 22 port_range_max = 22 remote_ip_prefix = "0.0.0.0/0" security_group_id = openstack_networking_secgroup_v2.prometheus.id } resource "openstack_networking_secgroup_v2" "grafana" { name = "${var.instance_name}-grafana-sg" description = "Grafana 3000 from anywhere; SSH from anywhere" } resource "openstack_networking_secgroup_rule_v2" "grafana_http" { direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = 3000 port_range_max = 3000 remote_ip_prefix = "0.0.0.0/0" security_group_id = openstack_networking_secgroup_v2.grafana.id } resource "openstack_networking_secgroup_rule_v2" "grafana_ssh" { direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = 22 port_range_max = 22 remote_ip_prefix = "0.0.0.0/0" security_group_id = openstack_networking_secgroup_v2.grafana.id } resource "openstack_networking_port_v2" "prometheus" { name = "${var.instance_name}-prometheus-port" network_id = openstack_networking_network_v2.private.id security_group_ids = [openstack_networking_secgroup_v2.prometheus.id] fixed_ip { subnet_id = openstack_networking_subnet_v2.private.id } depends_on = [openstack_networking_router_interface_v2.private] } resource "openstack_compute_instance_v2" "prometheus" { name = "${var.instance_name}-prometheus" flavor_name = var.prometheus_flavor key_pair = var.key_name user_data = templatefile("${path.module}/cloud-init/prometheus.yaml", { retention_days = var.retention_days scrape_targets_yaml = local.hardened_scrape_yaml }) block_device { uuid = data.openstack_images_image_v2.os.id source_type = "image" destination_type = "volume" volume_size = 20 boot_index = 0 delete_on_termination = true } network { port = openstack_networking_port_v2.prometheus.id } depends_on = [ openstack_networking_router_interface_v2.private, openstack_compute_instance_v2.vm, ] } resource "openstack_networking_port_v2" "grafana" { name = "${var.instance_name}-grafana-port" network_id = openstack_networking_network_v2.private.id security_group_ids = [openstack_networking_secgroup_v2.grafana.id] fixed_ip { subnet_id = openstack_networking_subnet_v2.private.id } depends_on = [openstack_networking_router_interface_v2.private] } resource "openstack_compute_instance_v2" "grafana" { name = "${var.instance_name}-grafana" flavor_name = var.grafana_flavor key_pair = var.key_name user_data = templatefile("${path.module}/cloud-init/grafana.yaml", { prometheus_ip = openstack_compute_instance_v2.prometheus.network[0].fixed_ip_v4 grafana_admin_password = var.grafana_admin_password }) block_device { uuid = data.openstack_images_image_v2.os.id source_type = "image" destination_type = "volume" volume_size = 20 boot_index = 0 delete_on_termination = true } network { port = openstack_networking_port_v2.grafana.id } depends_on = [openstack_compute_instance_v2.prometheus] } resource "openstack_networking_floatingip_v2" "grafana" { pool = var.external_network } resource "openstack_networking_floatingip_associate_v2" "grafana" { floating_ip = openstack_networking_floatingip_v2.grafana.address port_id = openstack_networking_port_v2.grafana.id }variables.tfHCLvariable "flavor_name" { description = "Instance size" type = string default = "s1a.small" } variable "image_name" { description = "Operating system image" type = string default = "Ubuntu-24.04" } variable "key_name" { description = "SSH keypair name (must already exist in your project)" type = string } variable "instance_name" { description = "Display name for the instance" type = string default = "simple-vm" } variable "external_network" { description = "Shared external network for router gateway and floating IPs; defaults to PublicStatic (persisted FIP / production pattern). Override with PublicEphemeral for ephemeral demos." type = string default = "PublicStatic" } variable "private_cidr" { description = "CIDR for the private tenant network the VM lives in" type = string default = "10.10.10.0/24" } variable "prometheus_flavor" { description = "Flavor for the Prometheus monitoring instance" type = string default = "s1a.small" } variable "grafana_flavor" { description = "Flavor for the Grafana dashboard instance" type = string default = "s1a.small" } variable "retention_days" { description = "Prometheus TSDB retention in days" type = number default = 15 } variable "grafana_admin_password" { description = "Initial Grafana admin password" type = string sensitive = true }outputs.tfHCLoutput "instance_id" { description = "ID of the compute instance" value = openstack_compute_instance_v2.vm.id } output "floating_ip" { description = "Public floating IP address" value = openstack_networking_floatingip_v2.vm.address } output "private_ip" { description = "Private IP address of the instance" value = openstack_compute_instance_v2.vm.access_ip_v4 } output "grafana_floating_ip" { description = "Public floating IP for Grafana" value = openstack_networking_floatingip_v2.grafana.address } output "prometheus_private_ip" { description = "Private IPv4 of the Prometheus instance" value = openstack_compute_instance_v2.prometheus.network[0].fixed_ip_v4 }versions.tfHCLterraform { required_version = ">= 1.6.0" required_providers { openstack = { source = "terraform-provider-openstack/openstack" version = "~> 2.0" } } } provider "openstack" {}terraform.tfvars.exampleHCL# Required: SSH keypair must already exist in your project key_name = "YOUR_KEY_NAME" # flavor_name = "s1a.small" # image_name = "Ubuntu-24.04" # instance_name = "simple-vm" # external_network = "PublicStatic" # private_cidr = "10.10.10.0/24"cloud-init/grafana.yamlYAML#cloud-config package_update: true packages: - apt-transport-https - ca-certificates - curl - gnupg write_files: - path: /etc/apt/sources.list.d/grafana.list content: deb [signed-by=/usr/share/keyrings/grafana.gpg] https://apt.grafana.com stable main owner: root:root permissions: "0644" - path: /etc/grafana/provisioning/datasources/prometheus.yaml content: | apiVersion: 1 datasources: - name: Prometheus type: prometheus access: proxy url: http://${prometheus_ip}:9090 isDefault: true editable: false owner: root:root permissions: "0644" - path: /etc/systemd/system/grafana-server.service.d/override.conf content: | [Service] Environment=GF_SECURITY_ADMIN_PASSWORD=${grafana_admin_password} Environment=GF_SERVER_HTTP_ADDR=0.0.0.0 owner: root:root permissions: "0600" runcmd: - | set -e export DEBIAN_FRONTEND=noninteractive DEV=/dev/sdb for i in $(seq 1 12); do [ -b "$DEV" ] && break; sleep 5; done if [ -b "$DEV" ]; then if ! blkid "$DEV" >/dev/null 2>&1; then mkfs.ext4 -F -L grafdata "$DEV"; fi mkdir -p /var/lib/grafana mount "$DEV" /var/lib/grafana grep -q "$DEV" /etc/fstab || echo "$DEV /var/lib/grafana ext4 defaults,nofail 0 2" >> /etc/fstab fi PROM="${prometheus_ip}" for i in $(seq 1 60); do if curl -sf "http://$${PROM}:9090/-/ready" >/dev/null 2>&1; then break; fi sleep 5 done install -d /usr/share/keyrings curl -fsSL https://apt.grafana.com/gpg.key | gpg --batch --yes --dearmor -o /usr/share/keyrings/grafana.gpg for i in 1 2 3; do apt-get update && break; sleep 10; done apt-get install -y grafana chown -R grafana:grafana /var/lib/grafana systemctl daemon-reload systemctl enable grafana-server systemctl restart grafana-servercloud-init/prometheus.yamlYAML#cloud-config package_update: true write_files: - path: /tmp/prometheus.default content: | ARGS="--web.enable-lifecycle --web.listen-address=0.0.0.0:9090 --storage.tsdb.retention.time=${retention_days}d" owner: root:root permissions: "0644" - path: /tmp/prometheus.yml content: | global: scrape_interval: 15s scrape_configs: - job_name: "prometheus" static_configs: - targets: ["localhost:9090"] ${scrape_targets_yaml} owner: root:root permissions: "0644" runcmd: - | set -e export DEBIAN_FRONTEND=noninteractive DEV=/dev/sdb for i in $(seq 1 12); do [ -b "$DEV" ] && break; sleep 5; done if [ -b "$DEV" ]; then if ! blkid "$DEV" >/dev/null 2>&1; then mkfs.ext4 -F -L promdata "$DEV"; fi mkdir -p /var/lib/prometheus mount "$DEV" /var/lib/prometheus grep -q "$DEV" /etc/fstab || echo "$DEV /var/lib/prometheus ext4 defaults,nofail 0 2" >> /etc/fstab fi for i in 1 2 3; do apt-get update && break; sleep 10; done apt-get install -y prometheus cp /tmp/prometheus.yml /etc/prometheus/prometheus.yml cp /tmp/prometheus.default /etc/default/prometheus chown -R prometheus:prometheus /var/lib/prometheus systemctl enable prometheus systemctl restart prometheuscloud-init/vm.yamlYAML#cloud-config package_update: true packages: - fail2ban - prometheus-node-exporter runcmd: - systemctl enable --now fail2ban prometheus-node-exporterREADME.mdMarkdown# Simple VM Single compute instance with floating IP and security group. **Network class:** production — `external_network` defaults to `PublicStatic` for persisted floating IPs and multi-tier stacks; override with `PublicEphemeral` for ephemeral demos. ## Prerequisites - OpenTofu >= 1.6.0 or Terraform >= 1.6.0 - Quake AI account with OpenStack credentials - An existing SSH keypair in your project (the value of `key_name` must match that keypair) ## Usage 1. Clone or copy this template directory 2. Copy `terraform.tfvars.example` to `terraform.tfvars` and fill in your values 3. Source your OpenStack credentials: `source openrc.sh` 4. Initialize: `tofu init` 5. Preview: `tofu plan` 6. Apply: `tofu apply` ## Variables | Name | Type | Required | Default | Description | | --- | --- | --- | --- | --- | | `key_name` | string | yes | n/a | SSH keypair name (must already exist in your project) | | `flavor_name` | string | no | `s1a.small` | Instance size | | `image_name` | string | no | `Ubuntu-24.04` | Operating system image | | `instance_name` | string | no | `simple-vm` | Display name for the instance | | `external_network` | string | no | `PublicStatic` | Persisted FIP / production default; override with `PublicEphemeral` for demos | | `private_cidr` | string | no | `10.10.10.0/24` | CIDR for the private tenant network the VM lives in | ## Documentation Full documentation: [Simple VM template](/docs/automation/templates/simple-vm)
Resources, parameters, and variables
flavor_name="s1a.small"image_name="Ubuntu-24.04"key_namerequiredinstance_name="simple-vm"external_network="PublicEphemeral"private_cidr="10.10.10.0/24"
Customize this pattern#
- Customize a template's image and flavor
- Add a block volume to a template
- Parameterize a template with a tfvars file
See also#
Usage Guidelines
The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.
For the full policy, see Usage Guidelines.
Last validated: 04.06.2026