# Launch your first server

Source: https://docs.quake.ai/docs/quickstart/launch-your-first-server
Markdown: https://docs.quake.ai/docs/quickstart/launch-your-first-server.md
> The fundamentals path: launch a Developer-plan instance on Quake AI, open the right ports, and serve a public web page over SSH in about 30 minutes.

---

# Launch your first server

This is the fundamentals path of the [Quickstart](/docs/quickstart): the honest IaaS on-ramp every other learning path builds on. The walkthrough takes about 30 minutes and uses a public web server as the working example. By the end, you'll have a running server on the public internet serving a page you wrote yourself, plus a working understanding of how the platform fits together.

<Figure caption="The greeting page you'll publish in step 4, served from a Quake AI VM at its public IP">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-5-outcome-greeting-page-f5ef3a98.png" alt="Browser address bar pointing at a public IP and rendering the heading 'Hello from Alex on Quake AI'" />
</Figure>



This walkthrough launches a single `s1a.micro` instance (1 vCPU, 1 GiB RAM) on the `PublicEphemeral` network, which assigns a public IP at boot with no floating IP add-on. See [Resource tiers](/docs/account/resource-tiers) for upgrade paths if you outgrow it.



## Prerequisites

You need a Quake AI account with a project and a resource tier picked. If you have not done that yet, work through [Get started](/docs/quickstart#get-started) at the top of the Quickstart first.

The walkthrough has five steps:

1. Set up a secure login from your computer to your server.
2. Configure your project's firewall to allow login and web traffic.
3. Launch a server.
4. Connect to the server, install web-server software, and publish a page.
5. Delete the server when you're done (optional).

To follow along, you'll need:

- A web browser and a terminal on your computer (macOS, Linux, or WSL on Windows).
- About 30 minutes. Most of that is the server starting up and the web-server software installing.

You can follow the walkthrough through Quake AI's browser interface (the default), the command line, or OpenTofu (Terraform) code. From step 2 onward, each step has tabs for all three. Pick one and stay with it for the rest of the walkthrough.

If you work with an AI coding assistant, you can reach the same result by driving it instead: see [How to provision a server with an AI agent](/resources/ai-assisted-development/quickstart-with-ai-agent).

The final step is optional. A running server uses your project's resources (a CPU core, memory, a public IP address) and stays reachable from the internet for as long as it's up. Step 5 walks through deleting the server when you're ready to clean up. If you want to keep the server running and experiment further, skip the final step.



The bash commands below assume macOS, Linux, or WSL. If you are on Windows, [set up a Linux CLI environment](/docs/tools/windows-cli-environment) first. WSL 2 is the recommended option.



## 1. Add an SSH key

SSH (Secure Shell) is the standard protocol for logging into a Linux server from your terminal. Quake AI authenticates SSH with a key pair: a public key you upload to your project and a private key that stays on your workstation. SSH never transmits the private key over the network. See [Key pairs](/docs/compute/concepts/key-pairs) for more.

<SshKeygen />

<Figure caption="Generating an ed25519 key pair from a local terminal with ssh-keygen">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-2-cli-ssh-keygen-069d2827.png" alt="Terminal showing ssh-keygen invocation, the resulting key fingerprint, and the randomart image for a new ed25519 key pair" />
</Figure>

Upload the public key (`~/.ssh/id_ed25519.pub`) to your project. Name the key after the workstation that holds the private key. `my-laptop`, your hostname, or your username are all common choices. You will reuse this key for every server you create, so pick a name you will recognize later.

1. In the portal, go to **Compute** > **Key Pairs** and select **Create Keypair**.
2. In the dialog that opens, switch to the **Import Keypair** tab.
3. Provide a name (for example, `my-laptop`).
4. Paste the contents of `~/.ssh/id_ed25519.pub` into the **Public Key** field.
5. Select **OK**.

<Figure caption="Create Keypair dialog on the default Create Keypair tab; switch to Import Keypair before pasting the public key">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-2-keypair-create-dialog-a1759d06.png" alt="Create Keypair dialog with the Create Keypair tab selected and the Name field visible at the top of the form" />
</Figure>

Verify your key appears in the **Key Pairs** list before continuing.

<Figure caption="Key Pairs list (capture session) showing the existing project key pairs">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-2-keypairs-list-bb55e2c3.png" alt="Compute Key Pairs list showing the project key pairs (val-2026-05-c1-key, val-2026-q3-add-bastion-key, val-2026-q3-mig-auto-key, val-2026-q3f-qs-key), each with a name and a fingerprint column" />
</Figure>

## 2. Create a security group

A security group is a virtual firewall attached to your instance's network port. Each rule allows specific inbound (ingress) or outbound (egress) traffic by protocol, port range, and source address. Every project includes a `default` security group that allows traffic only between its own members, so an instance using only `default` is unreachable from the internet. Create a dedicated group that opens TCP port 22 (SSH) and TCP port 80 (HTTP), then select it instead of `default` when you launch the server in step 3. See [Security groups](/docs/network/concepts/security-groups) for more.

<MethodTabs>
<Method label="Console">

<Figure caption="Network Security Groups list (capture session) showing the project's existing groups">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-3-security-groups-list-ad4b56f1.png" alt="Network Security Groups list showing the project groups (val-2026-q3f-qs-sg and default) and a Create Security Group button in the top right" />
</Figure>

1. In the portal, go to **Network** > **Security Groups** > **Create Security Group**.
2. Name the group `web-access`. The Description field is optional.
3. After saving, open the group and select **Create Rule**.
4. From the **Protocol** dropdown, select **SSH**. The dialog auto-fills **Direction** to `Ingress` and **Ether Type** to `IPv4`. The SSH preset implies port 22 and hides the **Port Type** and **Port** fields, so you do not set them. Leave **Source** at its default `All Traffic` (which resolves to `0.0.0.0/0`).

<Figure caption="Create Rule dialog with Protocol set to the SSH preset; the Port Type and Port fields are hidden because port 22 is implied by the preset">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-3-add-rule-dialog-e1a44b8b.png" alt="Create Rule dialog with Protocol set to SSH preset, Ingress direction, IPv4 ether type, and All Traffic source. The SSH preset hides the Port Type and Port fields because port 22 is implied by the preset." />
</Figure>

5. Save the rule.
6. Select **Create Rule** again. From the **Protocol** dropdown, select **Custom TCP Rule** and set **Port** to `80`. Leave **Direction** at `Ingress`, **Ether Type** at `IPv4`, and **Source** at `All Traffic`.
7. Save the rule.

<Figure caption="Security Group detail page for the capture-session group val-2026-q3f-qs-sg, with the same two ingress rules the prose creates on web-access">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-3-security-group-detail-f2fc1cdf.png" alt="Security Group detail page for val-2026-q3f-qs-sg with description Quickstart capture session: SSH+HTTP, two ingress rules (TCP port 22 from 0.0.0.0/0 and TCP port 80 from 0.0.0.0/0), and the default IPv4 and IPv6 egress rules" />
</Figure>

8. Confirm the group has both rules listed (TCP/22 and TCP/80) before continuing.

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

```bash
openstack security group create web-access \
  --description "SSH and HTTP access"

openstack security group rule create web-access \
  --ingress --protocol tcp --dst-port 22 --remote-ip 0.0.0.0/0

openstack security group rule create web-access \
  --ingress --protocol tcp --dst-port 80 --remote-ip 0.0.0.0/0
```

<Figure caption="Terminal output for the three openstack security group commands, ending with the second rule on TCP/80">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-3-cli-security-group-create-6e8fa8ff.png" alt="Terminal showing openstack security group create and two openstack security group rule create commands with their tabular response bodies" />
</Figure>

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

The Terraform path defines the security group inline alongside the instance in [step 3](#3-launch-your-first-server). Skip ahead.

</Method>
</MethodTabs>

For production, restrict the SSH rule's **Source** (CLI flag `--remote-ip`) to your workstation's public address instead of leaving the default `All Traffic` (`0.0.0.0/0`). Port 80 stays open to the internet because you're running a public web server.

## 3. Launch your first server

Launch a virtual machine on the **PublicEphemeral** network. Instances on this network receive a public IP directly, so SSH and HTTP work as soon as the instance reaches **Active** status.



PublicEphemeral is the shortest path to a public IP because the address binds directly to the instance's network port. Deleting the instance also releases the IP. That fits a quickstart, where you create, view, and delete the server in the same session. Do not use this pattern for production projects.

For a real project, run the instance on a private network behind a router and associate a **floating IP** from the **PublicStatic** pool. The floating IP persists when you rebuild the VM, and you can detach and reattach it across instances. See [Create a VM on a private network](/docs/compute/how-to/create-vm-private-network) for that walkthrough.



<MethodTabs>
<Method label="Console">

<Figure caption="Compute Instances list (capture session) showing existing project instances and the Create Instance button">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-4-instances-list-empty-b52dbbc4.png" alt="Compute Instances list showing the val-2026-q3f-qs-vm row in Active status and a Create Instance button in the top right" />
</Figure>

<VideoFigure
  caption="Step 3: Create Instance wizard advancing through Base Config, Network Config, System Config, and Confirm Config"
  src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-4-launch-wizard-56e35719.webm"
  poster="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-4-launch-wizard-poster-8d994883.png"
/>

The wizard opens with three tabs (`Base Config`, `Network Config`, `System Config`); the `Confirm Config` tab appears once the first three tabs validate. Advance with the **Next:** button at the bottom of each tab.

#### Base Config

1. Select **Compute** > **Instances** > **Create Instance**.
2. Name the instance `web-01` and choose an availability zone (for example, `us-east-1a`).
3. Pick the `s1a.micro` flavor under **Shared Resources** (1 vCPU, 1 GiB RAM, the Developer-plan footprint).
4. Select an image (`Ubuntu-24.04` is a good first choice) and set a disk size of at least 20 GiB. Check **Deleted with the instance** so the platform removes the disk when you delete the instance later.

<Figure caption="Base Config tab: name, availability zone, flavor, image, and disk size">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-4-launch-wizard-details-tab-8d994883.png" alt="Create Instance wizard Base Config tab with instance name, availability zone, flavor category tabs, image selection, and disk size" />
</Figure>

#### Network Config

1. Select **Next: Network Config**.
2. From the network list, select **PublicEphemeral**.
3. In the security groups list, **select `web-access`** (the group created in step 2). The wizard does not pre-select the project's `default` group; selecting `web-access` applies the rules you created in step 2 and is enough on its own to allow inbound SSH and HTTP.

<Figure caption="Network Config tab: network selection panel with PublicEphemeral chosen">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-4-launch-wizard-network-tab-68ddaa6e.png" alt="Create Instance wizard Network Config tab showing the PublicEphemeral network selected from the available networks list" />
</Figure>

<Figure caption="Network Config step: Security Group panel (capture session) with val-2026-q3f-qs-sg checked">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-4-launch-wizard-security-tab-2a7a6cc8.png" alt="Create Instance wizard Network Config step showing the Security Group panel. A search box filters a scrollable table (Name, Description, Created At, Rules) with one checkbox per security group; selected groups appear as chips below the table. The val-2026-q3f-qs-sg row is checked." />
</Figure>

#### System Config

1. Select **Next: System Config**.
2. Choose **Keypair** as the login type.
3. Select the `my-laptop` keypair you uploaded in step 1.

<Figure caption="System Config tab: login type and keypair selection">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-4-launch-wizard-keypair-tab-50ff5196.png" alt="Create Instance wizard System Config tab showing Keypair login type and key pair dropdown" />
</Figure>

#### Confirm Config

1. Select **Next: Confirm Config** and review the summary.
2. Select **Confirm** to launch.

The instance moves from **Build** to **Active** within a minute.

<Figure caption="Instance detail page for the capture-session instance val-2026-q3f-qs-vm in Active status; your instance shows the same fields under the name you chose (web-01 if you followed the prose)">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-4-instance-detail-running-132f3015.png" alt="Instance detail page for val-2026-q3f-qs-vm showing Active power state, flavor, image, key pair, security group, and the assigned public IP" />
</Figure>

#### Connect

In the Instances list, copy the public address shown in the **Fixed IP** column for `web-01` (PublicEphemeral attaches the public address directly to the instance's port, so it appears as the fixed IP rather than as a separate floating IP). Then from your local terminal:

```bash
ssh ubuntu@INSTANCE_IP
```

Replace `INSTANCE_IP` with the address from the Instances list. Type `yes` to accept the host key. Your prompt changes to `ubuntu@web-01`, confirming the server is up and reachable.

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

The CLI path requires the OpenStack client and a sourced credentials file.

1. Install the OpenStack client following [Install the OpenStack client](/docs/tools/install-openstack-client).
2. [Generate app credentials](/docs/tools/generate-app-credentials) and download `openrc.sh`.
3. Source the file in your shell:

```bash
source ~/path/to/openrc.sh
```

4. Verify the connection:

```bash
openstack token issue
```

A successful response returns a token with your project ID, user ID, and expiration time.

Launch the instance on **PublicEphemeral**:

<CreateVmCli network="PublicEphemeral" flavor="s1a.micro" instanceName="web-01" keyName="my-laptop" securityGroups={["web-access"]} showFloatingIp={false} />

Wait for the server to reach `ACTIVE` status, then look up its public address:

```bash
openstack server show web-01 -c status -c addresses
```

Then connect from your local terminal:

```bash
ssh ubuntu@INSTANCE_IP
```

Replace `INSTANCE_IP` with the address from the previous command's output. Type `yes` to accept the host key. Your prompt changes to `ubuntu@web-01`.

<Figure caption="Terminal showing openstack server create, openstack server show, and the first SSH connection to the new instance">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-4-cli-server-create-and-ssh-cd736cd5.png" alt="Terminal output for openstack server create with boot-from-volume, openstack server show returning Active status and the public address, and the resulting ssh ubuntu@INSTANCE_IP session reaching the ubuntu@web-01 prompt" />
</Figure>

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

The IaC path uses [OpenTofu](https://opentofu.org/), the open-source fork of Terraform. The `tofu` and `terraform` commands accept identical configuration; this tab uses `tofu`.

1. [Install OpenTofu](https://opentofu.org/docs/intro/install/).
2. [Generate app credentials](/docs/tools/generate-app-credentials) and download `openrc.sh`.
3. Source the file in your shell:

```bash
source ~/path/to/openrc.sh
```

Create a working directory and add `main.tf`:

```hcl
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 2.0"
    }
  }
}

provider "openstack" {}

data "openstack_images_image_v2" "ubuntu_24_04" {
  name        = "Ubuntu-24.04"
  most_recent = true
}

data "openstack_networking_network_v2" "public" {
  name = "PublicEphemeral"
}

resource "openstack_compute_keypair_v2" "my_laptop" {
  name       = "my-laptop"
  public_key = file("~/.ssh/id_ed25519.pub")
}

resource "openstack_networking_secgroup_v2" "web_access" {
  name        = "web-access"
  description = "SSH and HTTP access"
}

resource "openstack_networking_secgroup_rule_v2" "web_access_22" {
  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.web_access.id
}

resource "openstack_networking_secgroup_rule_v2" "web_access_80" {
  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.web_access.id
}

resource "openstack_networking_port_v2" "web_01" {
  name               = "web-01-port"
  network_id         = data.openstack_networking_network_v2.public.id
  security_group_ids = [openstack_networking_secgroup_v2.web_access.id]
}

resource "openstack_compute_instance_v2" "web_01" {
  name        = "web-01"
  flavor_name = "s1a.micro"
  key_pair    = openstack_compute_keypair_v2.my_laptop.name

  block_device {
    uuid                  = data.openstack_images_image_v2.ubuntu_24_04.id
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 20
    boot_index            = 0
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.web_01.id
  }
}

output "instance_address" {
  value = openstack_compute_instance_v2.web_01.network[0].fixed_ip_v4
}
```

The `block_device` block creates a 20 GiB volume from the Ubuntu-24.04 image and boots the instance from it. Attach security groups to the Neutron port with `security_group_ids`, not to the instance by name. See [Authoring IaC templates](/docs/automation/concepts/authoring-iac-templates) for the full pattern. Quake AI's `s1a.micro` flavor has zero ephemeral disk and boots only from a volume (see the [Block Storage](/docs/block/concepts/volumes) concept for more on volume-backed instances).

Initialize and apply:

```bash
tofu init
tofu apply
```

OpenTofu prints the instance's public address as the `instance_address` output. Connect from your local terminal:

```bash
ssh ubuntu@INSTANCE_IP
```

Replace `INSTANCE_IP` with the value of `instance_address`. Type `yes` to accept the host key. Your prompt changes to `ubuntu@web-01`.

<Figure caption="Terminal showing tofu apply with the instance_address output and the resulting ssh connection to the new instance">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-4-terraform-apply-and-ssh-56a1b543.png" alt="Terminal output for tofu apply showing the OpenStack provider plan, the apply confirmation, the instance_address output value, and the resulting ssh ubuntu@INSTANCE_IP session" />
</Figure>

</Method>
</MethodTabs>

## 4. Serve a page

You are logged in to `web-01` as `ubuntu` over SSH. Install nginx, write a page, and visit it from your browser.

Install nginx:

```bash
sudo apt update
sudo apt install -y nginx
```

Ubuntu starts nginx automatically. nginx is already serving its default welcome page on port 80.

Replace the default page with your own greeting (substitute your name):

```bash
echo '<h1>Hello from Alex on Quake AI</h1>' | sudo tee /var/www/html/index.html
```

<Figure caption="Terminal showing sudo apt update, sudo apt install -y nginx, and the echo command writing the greeting to /var/www/html/index.html">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-5-cli-install-nginx-1760d9a2.png" alt="Terminal session on the instance showing apt update output, apt install -y nginx output, and the echo tee command writing the greeting to the nginx document root" />
</Figure>

Open `http://INSTANCE_IP` in your browser (substitute the public IP you used to SSH in). The greeting renders. nginx is serving the page from the Quake AI VM you provisioned and configured in this walkthrough.

To publish the same site on a domain you own, [point your domain at the instance's public address](/docs/network/how-to/point-domain-to-quake-ai) before you add [TLS with Let's Encrypt](/docs/network/how-to/lets-encrypt-certificate) or [a CDN in front](/docs/network/how-to/front-with-cdn).

<VideoFigure
  caption="Step 4: install nginx, write a page, and visit the public IP from a browser"
  src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-5-install-then-visit-3f0a682d.webm"
  poster="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-5-install-then-visit-poster-f5ef3a98.png"
/>

Disconnect from the server:

```bash
exit
```

## 5. Clean up

A public web server should not run unattended. Delete the instance now that you have seen the page. You can reuse the SSH key (`my-laptop`) and the security group (`web-access`); leave them in place for the next server you launch.

<MethodTabs>
<Method label="Console">

1. In the portal, go to **Compute** > **Instances**.
2. Find the `web-01` row, open the Settings (gear) icon, and select **Delete**.

<Figure caption="Compute Instances list (capture session) showing the val-2026-q3f-qs-vm row and the Settings (gear) icon; click the icon and choose Delete to remove the instance">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-6-instances-list-with-vm-5cb4ce05.png" alt="Compute Instances list with the val-2026-q3f-qs-vm row, the Settings (gear) icon visible at the right of the row, and no menu currently expanded" />
</Figure>

3. Confirm the deletion. The instance disappears from the list within a few seconds.
4. Verify `web-01` is no longer in the Instances list before closing the page.



The Console delete-instance dialog has no option to delete attached volumes. The **Deleted with the instance** checkbox you set when creating the instance controls whether the platform removes the boot volume; the delete dialog does not. Boot volumes created by the instance wizard show no name in **Storage** > **Volumes** (the Name column renders `-`), so the Console cannot match a leftover boot volume by name. Identify it by its **Available** status and creation time, or read the volume ID from the CLI or API.



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

```bash
openstack server delete web-01
```

Verify the instance is gone:

```bash
openstack server list
```

<Figure caption="Terminal output for openstack server delete web-01 followed by openstack server list with no rows for web-01">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-6-cli-server-delete-94cce43c.png" alt="Terminal showing openstack server delete web-01 returning to the prompt and a subsequent openstack server list with the web-01 row no longer present" />
</Figure>

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

```bash
tofu destroy
```

OpenTofu prints the destruction plan and asks for confirmation. Type `yes`. OpenTofu removes every resource declared in `main.tf`, including the key pair and security group. If you want to keep the key pair and security group for future use, run the Console or CLI deletion instead.

<Figure caption="Terminal showing tofu destroy with the destruction plan and the Destroy complete! confirmation">
  <img src="https://object.us-east-1.rumble.cloud/77e78aa9c0e04ed0b9d1a3f0626a8c4d:developer-platform-images/images/quickstart/step-6-terraform-destroy-acadcc64.png" alt="Terminal output for tofu destroy listing the five resources to be removed and the trailing Destroy complete! Resources: 5 destroyed line" />
</Figure>

</Method>
</MethodTabs>

You provisioned a public web server, viewed it in a browser, and deleted it. The same workflow extends to more complex projects: more instances, custom images, automation, self-managed reverse proxies, Kubernetes clusters. Where to go next:

- [How to point a domain at a Quake AI resource](/docs/network/how-to/point-domain-to-quake-ai): map a hostname at your registrar to a floating IP or public address.
- [How to issue and auto-renew a TLS certificate with Let's Encrypt](/docs/network/how-to/lets-encrypt-certificate): HTTPS on the VM you built.
- [How to put a CDN in front of a Quake AI workload](/docs/network/how-to/front-with-cdn): cache static assets at the edge after DNS is set.
- [Compute concepts](/docs/compute/concepts/): instances, flavors, images, and how the platform models them.
- [Quickstart learning paths](/docs/quickstart): static sites, databases, containerized apps, object storage, and infrastructure as code.
- [Access & Credentials](/docs/tools/): full CLI setup, app credentials, API tokens, and credential rotation.
- [Automation how-tos](/docs/automation/how-to/): IaC patterns, CI integration, and template usage.
- [Platform overview](/docs/platform/): what Quake AI is, what it runs on, and how it differs from other providers.
