# How to provision a production-ready VM

Source: https://docs.quake.ai/docs/compute/how-to/provision-production-vm
Markdown: https://docs.quake.ai/docs/compute/how-to/provision-production-vm.md

---

# How to provision a production-ready VM

Walk through the decisions and steps to launch a virtual machine you can run a workload on: pick sizing, attach SSH access, place the instance on the right network, pass first-boot configuration, and confirm you can connect.



Quake AI provisions infrastructure, not a managed operating system. There is no production VM preset or hardening service. This page composes existing primitives into a recommended order of operations. You own the resulting VM, its updates, and its security posture.



This guide orchestrates the primitive how-tos below. Use them when you need full detail on a single step:

- [Create a virtual machine instance](/docs/compute/how-to/create-instance)
- [Create a VM on a private network](/docs/compute/how-to/create-vm-private-network)
- [Create a VM on a public network](/docs/compute/how-to/create-vm-public-network)
- [Set a password on a virtual machine instance](/docs/compute/how-to/create-password) (console access alternative, not the primary path for production)

<PrerequisiteBlock methods={["console", "cli", "api", "terraform"]}>

- An [SSH key pair](/docs/tools/add-ssh-key) uploaded to your account
- A project with quota for at least one instance, one boot volume, and (for the private-network path) one floating IP

</PrerequisiteBlock>

## Step 1. Choose a flavor and image

Right-size the instance before you create it. Start with the smallest flavor that meets your workload's CPU, memory, and disk needs, then scale up if monitoring shows sustained pressure.

| Decision | Guidance |
|---|---|
| Flavor family | [General-purpose flavors](/docs/compute/concepts/flavors) (`m2a.*`) suit most web and API workloads. Pick [compute-optimized](/docs/compute/concepts/flavors) (`c2a.*`) for CPU-bound jobs and [memory-optimized](/docs/compute/concepts/flavors) (`r2a.*`) for in-memory caches or analytics. |
| Shared vs dedicated vCPU | Shared flavors (`s1a.*`) work for dev and light traffic. Dedicated flavors give predictable CPU for production services. |
| Image | Use a current LTS image such as `Ubuntu-22.04` unless your workload requires another [supported image](/docs/compute/concepts/images). |
| Boot disk | All Quake AI flavors have `disk=0`; you must boot from a volume. Size the volume for the OS plus application data (10 GiB is a common starting point for Linux). |

List available flavors and images from the CLI:

```bash
openstack flavor list --long
openstack image list
```

## Step 2. Create or select an SSH key pair

Production VMs should authenticate with SSH keys, not password-only login.

<MethodTabs>
<Method label="Console">

1. Open **Identity** > **Key Pairs**.
2. Select **Create Key Pair**, name it (e.g., `prod-deploy`), and download the private key.
3. Restrict permissions on the downloaded file on your workstation (`chmod 600` on Linux or macOS).

</Method>
<Method label="CLI">

If you already have a public key locally:

```bash
openstack keypair create --public-key ~/.ssh/id_ed25519.pub prod-deploy
```

List existing keys:

```bash
openstack keypair list
```

</Method>
<Method label="API">

Upload a public key with the Identity API. See [add an SSH key](/docs/tools/add-ssh-key) for the full procedure.

</Method>
<Method label="Terraform">

Reference an existing key pair by name:

```hcl
variable "key_pair_name" {
  default = "prod-deploy"
}
```

</Method>
</MethodTabs>

If you need password-based console access as a fallback, configure it after creation with [set a password on a virtual machine instance](/docs/compute/how-to/create-password). Do not rely on passwords as the only login method.

## Step 3. Place the VM on the right network

For production workloads, place the instance on a [private network](/docs/compute/how-to/create-vm-private-network) with a router to the public internet. Allocate one [floating IP](/docs/network/how-to/allocate-floating-ips) only when you need inbound SSH or HTTP/HTTPS from the internet.

For quick tests, you can use a [public network](/docs/compute/how-to/create-vm-public-network) instead. That path assigns a public address automatically and does not require a floating IP.

Before you launch the instance, create a security group that allows only the ports your workload needs. At minimum, allow SSH (TCP 22) from your admin IP range rather than `0.0.0.0/0` when you can.

<MethodTabs>
<Method label="Console">

<NoMoreButton />

1. Follow [create a VM on a private network](/docs/compute/how-to/create-vm-private-network) through Step 3 to create the network, router, and security group.
2. Stop before Step 4 (create the instance). You will launch the instance in Step 5 with cloud-init user data.

</Method>
<Method label="CLI">

Create the network plane (adjust names and CIDR as needed):

```bash
openstack network create prod-net
openstack subnet create --network prod-net \
  --subnet-range 192.168.100.0/24 --dns-nameserver 1.1.1.1 prod-subnet
openstack router create --external-gateway PublicStatic prod-router
openstack router add subnet prod-router prod-subnet

openstack security group create prod-ssh
openstack security group rule create \
  --protocol tcp --dst-port 22 --remote-ip YOUR_ADMIN_CIDR/32 \
  prod-ssh
```

Replace `YOUR_ADMIN_CIDR` with the public IP you SSH from.

</Method>
<Method label="API">

See [create a network](/docs/network/how-to/create-network), [create a router](/docs/network/how-to/create-router), and [create a security group](/docs/network/how-to/create-security-group) for the API calls.

</Method>
<Method label="Terraform">

```hcl
resource "openstack_networking_network_v2" "prod_net" {
  name = "prod-net"
}

resource "openstack_networking_subnet_v2" "prod_subnet" {
  name       = "prod-subnet"
  network_id = openstack_networking_network_v2.prod_net.id
  cidr       = "192.168.100.0/24"
  ip_version = 4
}

resource "openstack_networking_router_v2" "prod_router" {
  name                = "prod-router"
  external_network_id = data.openstack_networking_network_v2.external.id
}

resource "openstack_networking_router_interface_v2" "prod_router_if" {
  router_id = openstack_networking_router_v2.prod_router.id
  subnet_id = openstack_networking_subnet_v2.prod_subnet.id
}

resource "openstack_networking_secgroup_v2" "prod_ssh" {
  name = "prod-ssh"
}

resource "openstack_networking_secgroup_rule_v2" "prod_ssh" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = var.admin_cidr
  security_group_id = openstack_networking_secgroup_v2.prod_ssh.id
}
```

</Method>
</MethodTabs>

## Step 4. Pass cloud-init user data

Cloud-init runs on first boot. Use it to create a non-root sudo user, apply package updates, and set the hostname. See [cloud-init and first-boot configuration](/docs/compute/concepts/cloud-init) for how the first-boot model works, and [How to configure a VM with cloud-init](/docs/compute/how-to/configure-vm-cloud-init) for module reference, launch methods, and debugging.

Save this template as `cloud-init.yaml` and replace the placeholder values:

```yaml
#cloud-config
hostname: PROD_HOSTNAME
users:
  - name: deploy
    groups: sudo
    shell: /bin/bash
    sudo: ALL=(ALL) NOPASSWD:ALL
    ssh_authorized_keys:
      - YOUR_SSH_PUBLIC_KEY
package_update: true
package_upgrade: true
```

In the Console, paste the YAML into **Advanced Options** > **User Data** on the **System Config** step of the Create Instance wizard.

## Step 5. Create the instance

Launch the VM on the private network with your key pair, security group, and user data.

<MethodTabs>
<Method label="Console">

1. Select **Compute** > **Instances** > **Create Instance**.
2. On **Base Config**, name the instance, select `us-east-1a`, pick your flavor (e.g., `m2a.large` under **General Purpose**), choose `Ubuntu-22.04`, set disk to **10 GiB**, and check **Deleted with the instance**.
3. On **Network Config**, select `prod-net` from **Current Project Networks**, assign the subnet, and attach the `default` and `prod-ssh` security groups.
4. On **System Config**, select **Keypair** and your key. Paste the cloud-init YAML from Step 4 into **User Data** under **Advanced Options**.
5. On **Confirm Config**, review and select **Confirm**.
6. After the instance is **Active**, allocate and associate a floating IP if you need inbound SSH from the internet. Follow Step 5 in [create a VM on a private network](/docs/compute/how-to/create-vm-private-network).

</Method>
<Method label="CLI">

```bash
openstack server create \
  --flavor m2a.large \
  --image Ubuntu-22.04 \
  --boot-from-volume 10 \
  --network prod-net \
  --key-name prod-deploy \
  --security-group default \
  --security-group prod-ssh \
  --user-data cloud-init.yaml \
  prod-app-01

openstack server show prod-app-01 -c status -c addresses
```

If you need a public SSH endpoint, allocate and associate a floating IP:

```bash
openstack floating ip create PublicStatic
openstack server add floating ip prod-app-01 FLOATING_IP
```

Replace `FLOATING_IP` with the address from the create command.

</Method>
<Method label="API">

<ComputeApiEnvironment />

Create the server with a `user_data` field (base64-encoded cloud-init) and the network UUIDs from your project. See [create a virtual machine instance](/docs/compute/how-to/create-instance) for the request shape.

</Method>
<Method label="Terraform">

```hcl
resource "openstack_compute_instance_v2" "prod_app" {
  name            = "prod-app-01"
  flavor_name     = "m2a.large"
  image_name      = "Ubuntu-22.04"
  key_pair        = var.key_pair_name
  security_groups = ["default", openstack_networking_secgroup_v2.prod_ssh.name]
  user_data       = file("${path.module}/cloud-init.yaml")

  network {
    uuid = openstack_networking_network_v2.prod_net.id
  }

  block_device {
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 10
    boot_index            = 0
    delete_on_termination = true
  }
}

resource "openstack_networking_floatingip_v2" "prod_app" {
  pool = "PublicStatic"
}

resource "openstack_compute_floatingip_associate_v2" "prod_app" {
  floating_ip = openstack_networking_floatingip_v2.prod_app.address
  instance_id = openstack_compute_instance_v2.prod_app.id
}
```

See the [simple VM template](/resources/iac-templates/simple-vm) for a complete working example.

</Method>
</MethodTabs>

## Step 6. Verify access

Confirm the instance booted, cloud-init finished, and SSH works as the non-root user.

<MethodTabs>
<Method label="Console">

1. Open **Compute** > **Instances** and confirm the instance status is **Active**.
2. Note the floating IP or private address from the instance detail page.

</Method>
<Method label="CLI">

```bash
openstack server show prod-app-01 -c status -c addresses
ssh -i ~/.ssh/prod-deploy deploy@FLOATING_IP
```

After login, confirm the hostname and that package updates ran:

```bash
hostname
sudo apt list --upgradable 2>/dev/null | head
```

</Method>
<Method label="API">

Fetch the server record and confirm `status` is `ACTIVE`. Test SSH from your workstation.

</Method>
<Method label="Terraform">

```bash
tofu output
ssh deploy@$(tofu output -raw floating_ip)
```

</Method>
</MethodTabs>

If the workload exposes HTTP or HTTPS, curl the service port from your workstation:

```bash
curl -I http://FLOATING_IP
```

Open only the ports your application needs in the security group.

## Next steps

Continue the VM lifecycle cluster:

- [Harden a production VM](/docs/compute/how-to/harden-production-vm)
- [Patch and update a VM](/docs/compute/how-to/patch-and-update-vm)
- [Operate a running VM](/docs/compute/how-to/operate-running-vm)
- [Maintain a VM](/docs/compute/how-to/maintain-vm)

## See also

- [Flavors](/docs/compute/concepts/flavors)
- [Images](/docs/compute/concepts/images)
- [Key pairs](/docs/compute/concepts/key-pairs)
- [Security groups](/docs/network/concepts/security-groups)
- [Floating IPs](/docs/network/concepts/floating-ips)
