> ## Documentation Index
> Fetch the complete documentation index at: https://gcore-doc-1894.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a virtual instance

export const MethodSection = ({children}) => children ?? null;

export const MethodSwitch = ({children}) => {
  const tabs = React.Children.toArray(children).map(c => {
    if (!c || !c.props) return null;
    if (c.props.id) return c;
    const inner = c.props.children;
    if (inner && inner.props && inner.props.id) return inner;
    return null;
  }).filter(Boolean);
  const firstId = tabs.length > 0 ? tabs[0].props.id : "";
  const [active, setActive] = React.useState(firstId);
  React.useEffect(() => {
    try {
      const saved = localStorage.getItem("gcore_docs_method");
      if (saved && tabs.find(t => t.props.id === saved)) {
        setActive(saved);
      }
    } catch (_) {}
  }, []);
  React.useEffect(() => {
    try {
      document.querySelectorAll("h2[id], h3[id]").forEach(heading => {
        const visible = heading.offsetParent !== null;
        document.querySelectorAll(`a[href="#${heading.id}"]`).forEach(link => {
          if (link.closest("h1,h2,h3,h4,h5,h6")) return;
          const li = link.closest("li");
          if (li) li.style.display = visible ? "" : "none";
        });
      });
    } catch (_) {}
    window.dispatchEvent(new Event("scroll"));
  }, [active]);
  const handleClick = id => {
    setActive(id);
    try {
      localStorage.setItem("gcore_docs_method", id);
    } catch (_) {}
  };
  return <div>
      <div className="not-prose flex gap-0 border-b border-zinc-200 dark:border-zinc-800 mb-8 mt-2" role="tablist">
        {tabs.map(tab => {
    const isActive = active === tab.props.id;
    return <button key={tab.props.id} role="tab" aria-selected={isActive} onClick={() => handleClick(tab.props.id)} className={["px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors cursor-pointer", isActive ? "border-primary text-primary" : "border-transparent text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"].join(" ")}>
              {tab.props.label}
            </button>;
  })}
      </div>

      {tabs.map(tab => <div key={tab.props.id} style={{
    display: active === tab.props.id ? "" : "none"
  }}>
          {tab.props.children}
        </div>)}
    </div>;
};

<MethodSwitch>
  <MethodSection id="portal" label="Customer Portal">
    <p>Create Linux or Windows Virtual Instances in Gcore Cloud.</p>

    <Tip>
      When using [VAST file shares](/cloud/file-shares/configure-file-shares), it is best to create them **before** provisioning the corresponding compute resources.
    </Tip>

    ## Step 1. Select a region

    1. In the [Gcore Customer Portal](https://portal.gcore.com/accounts/reports/dashboard), navigate to **Virtual Instances** and click **Create Instance**.

    <Frame>
      <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/1-create-instancebutton.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=b2cf0af6d8a45abbcff49631e6d7d057" alt="The Create VM button" width="1440" height="463" data-path="images/docs/cloud/virtual-instances/create-an-instance/1-create-instancebutton.png" />
    </Frame>

    2. Select the region where the Virtual Instance will be deployed.

    <Frame>
      <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/2-region-selection.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=4130ea5ad9626a1f0797fe64c0c0c0bd" alt="Region selection" width="896" height="457" data-path="images/docs/cloud/virtual-instances/create-an-instance/2-region-selection.png" />
    </Frame>

    <p>Notice that regions are labeled either Core or Edge. This represents the region's equipment specifications.</p>

    |                                          | Core                        | Edge\*                          |
    | ---------------------------------------- | --------------------------- | ------------------------------- |
    | Equipment generation                     | The latest                  | Different                       |
    | Designed for high scalability on the fly | Yes                         | Not                             |
    | Available resources                      | 1000 cores and 30 TB of RAM | Up to 300 cores and 1 TB of RAM |
    | Ports for user traffic and storage       | Separate                    | Shared                          |
    | Price                                    | Higher                      | Lower                           |

    * Edge regions can be transformed into Core regions — contact [support](mailto:support@gcore.com) to request the conversion.

    ## Step 2. Configure the image

    <p>Select the type of hardware architecture for the instance:</p>

    * **x86-64** : This architecture is known for its broad compatibility with Linux operating systems and Windows distributions. It is commonly used in general purpose computing applications.
    * **ARM** : ARM architecture is designed for energy efficiency and low power consumption, which also supports strong performance, making it ideal for high-performance computing tasks. However, ARM Virtual Machines are compatible with fewer OS distributions.

    <Frame>
      <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/configure-image.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=22a541d7a00c3e56165a183daae22a3a" alt="Choose x86-64 or ARM architecture" width="981" height="757" data-path="images/docs/cloud/virtual-instances/create-an-instance/configure-image.png" />
    </Frame>

    <p>The selected hardware architecture determines the available OS options and instance flavors. Select the image source from one of the tabs:</p>

    * **Distributions** — standard OS distributions: Ubuntu, Debian, CentOS, Windows, and others.
    * **Marketplace** — pre-configured templates with software already installed.
    * **Volume** — boot from an existing volume.
    * **Snapshot** — boot from a previously created snapshot.
    * **Custom Images** — use an image uploaded to the project.

    ## Step 3. Select the VM type

    <p>The available CPU generation is determined by the selected region and displayed in the **CPU generation** field. Select one of the available flavors:</p>

    * **Standard** — balanced compute, memory, and networking for a wide range of workloads.
    * **CPU-Optimized** — higher vCPU-to-RAM ratio, suited for CPU-intensive tasks — batch processing, video encoding, and large-scale data processing.
    * **Memory-Optimized** — higher RAM-to-vCPU ratio, suited for memory-intensive workloads — databases, SRM/ERP, and data warehouses.

    <Info>
      GPU, High-frequency, and Shared flavors are available in select regions only. The flavor tabs appear automatically when those flavors are available in the selected region.
    </Info>

    ## Step 4. Set up volumes

    <p>Enter a volume name, select its type, and set its size in GiB.</p>

    <Frame>
      <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/add-volume.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=95284526b02414f7ae8b6534b86a9361" alt="Configure Volumes" width="1440" height="900" data-path="images/docs/cloud/virtual-instances/create-an-instance/add-volume.png" />
    </Frame>

    <Accordion title="Available volume types">
      * **High IOPS SSD**. High-performance SSD block storage designed for latency-sensitive transactional workloads (60 IOPS per 1 GB; 2.5 MB/s per 1 GiB). The IOPS performance limit is 9,000. The bandwidth limit is 500 MB/s.

      * **Standard**. Network SSD disk that provides stable and high random I/O performance, as well as high data reliability (6 IOPS per 1 GB; 0.4 MB/s per 1 GiB). The IOPS performance limit is 4,500. The bandwidth limit is 300 MB/s.

      * **SSD Low-Latency**. SSD block storage designed for applications that require low-latency storage and real-time data processing. 50 IOPS per 1 GB; minimum 1,000 IOPS; maximum 75,000 IOPS. Bandwidth limit: 1,500 MB/s. Average latency: 300 µs.
    </Accordion>

    <p>(Optional) Add an **Attachment Tag**.</p>

    ## Step 5. Configure networking

    ### Configure the interface

    <p>Configure the network interface:</p>

    1. Select the **Network type**: **Public** or **Private**.

    2. In the **IP Family** section, select the IP version: **IPv4**, **IPv6**, or **Dual (IPv4 + IPv6)**.

    3. In the **Security Groups** section, select the security groups to attach. The default security group is attached automatically if no selection is made.

    4. (Optional) Enable **Use reserved IP** to assign a [reserved IP address](/cloud/networking/ip-address/create-and-configure-a-reserved-ip-address) to the instance, or enable **Use floating IP** to assign a [floating IP address](/cloud/networking/ip-address/create-and-configure-a-floating-ip-address).

    <p>To assign multiple public IP addresses, add additional public interfaces after the instance is created: open the **Networking** tab in the VM settings and click **Add Interface**.</p>

    <Frame>
      <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/vm-network-section.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=15580e51f244f01ee5c58c625442a41b" alt="Network settings section with Public network interface selected" width="655" height="510" data-path="images/docs/cloud/virtual-instances/create-an-instance/vm-network-section.png" />
    </Frame>

    <Info>
      The default security group allows inbound traffic over ICMP, SSH (TCP/22), and RDP (TCP/3389 and UDP/3389). All outbound traffic is allowed. [Custom security groups](/cloud/networking/add-and-configure-a-firewall) can override these defaults.
    </Info>

    <p>For a **private** interface, configure a network and a subnet as described below.</p>

    <Tip>
      If both a public and private interface are needed, disable the default gateway on the private network's subnet and assign a floating IP to the private interface.
    </Tip>

    ### Configure the network

    <p>Select an existing network from the dropdown list or create a new one by clicking **Add a new network**.</p>

    <Frame>
      <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/add-new-network.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=c8b4339c4befeb75dc2a6b69d3acf586" alt="Private interface with highlighted Add a new network link" width="656" height="512" data-path="images/docs/cloud/virtual-instances/create-an-instance/add-new-network.png" />
    </Frame>

    <Accordion title="Create a new network">
      1. Click **Add a new network**.
      2. Enter the network name.
      3. (Optional) Enable **Bare Metal Network** to make the network available to Bare Metal servers.
      4. (Optional) Enable **Create router** to create a router that all subnets in this network will connect to by default.
      5. (Optional) Enable **Add tags** to attach metadata to the network.
      6. Click **Create & Proceed**.

      <Frame>
        <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/create-network.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=b0d544696797866c2a07ad996d5b6d13" alt="Add a new network" width="1440" height="900" data-path="images/docs/cloud/virtual-instances/create-an-instance/create-network.png" />
      </Frame>
    </Accordion>

    ### Configure the subnet

    <Info>
      When a Virtual Instance has several subnets, keep only one routable — [default gateway conflicts](/cloud/networking/create-and-manage-a-subnet#set-up-the-default-gateway) otherwise prevent connecting to the instance.
    </Info>

    <p>Select an existing subnet from the dropdown list or create a new one by clicking **Add a new subnet**.</p>

    <Frame>
      <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/add-new-subnetwork.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=94387505d01e27e26f1f50288257b948" alt="Add a new subnet" width="670" height="749" data-path="images/docs/cloud/virtual-instances/create-an-instance/add-new-subnetwork.png" />
    </Frame>

    <Accordion title="Create a new subnet">
      1. Click **Add a new subnet**.
      2. Enter the subnet name.
      3. Set CIDR in one of the private ranges: 10.0.0.0–10.255.255.255, 172.16.0.0–172.31.255.255, or 192.168.0.0–192.168.255.255. Set the mask between /16 and /29.
      4. (Optional) Enable **Enable DHCP** to assign IP addresses to machines in the subnet automatically.
      5. (Optional) Enable **Connect to network router** to connect this subnet to the network's router by default.
      6. Select **Gateway IP** assignment: **Automatically** or **Specific**.
      7. (Optional) Enter **Custom DNS** server addresses.
      8. (Optional) Enter static routes using the **Destination** and **Nexthop** fields.
      9. (Optional) Enable **Add tags** to attach metadata to the subnet.
      10. Click **Create subnet**.

      <Frame>
        <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/create-subnetwork-annotated.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=60ebc46a077d81fae044d9234fa73af4" alt="Add a new subnet dialog" width="1440" height="900" data-path="images/docs/cloud/virtual-instances/create-an-instance/create-subnetwork-annotated.png" />
      </Frame>
    </Accordion>

    ### Interface order

    <Accordion title="Interface naming when using multiple interfaces">
      <p>When adding multiple interfaces, the order in the creation form determines how the OS names them after the instance starts. The diagram below shows how the selection order carries through to the **Networking** tab and then to the OS interface names:</p>

      <Frame>
        <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/vm-network-interface-order-diagram.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=6184d857e0fa675c1b87546a755ae993" alt="Diagram showing how interface order set during VM creation maps to the Networking tab and OS interface names" width="2725" height="639" data-path="images/docs/cloud/virtual-instances/create-an-instance/vm-network-interface-order-diagram.png" />
      </Frame>

      ```bash theme={null}
      $ ip addr show enp3s0
      2: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
          inet 85.234.84.186/24 metric 1000 brd 85.234.84.255 scope global dynamic enp3s0

      $ ip addr show enp4s0
      3: enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
          inet 10.0.0.132/24 metric 2000 brd 10.0.0.255 scope global dynamic enp4s0
      ```

      <p>The first interface (`enp3s0`) is the public interface; the second interface (`enp4s0`) is the private interface. The order set at creation determines routing priority: the first interface has higher priority for outbound traffic than subsequent ones. The interface order cannot be changed after the instance is created.</p>
    </Accordion>

    ## Step 6. Set up authentication

    <Tabs>
      <Tab title="Linux Virtual Machine">
        Configure an SSH key for a remote SSH connection. Add an existing SSH key or generate a new one using the [SSH key guide](/cloud/virtual-instances/connect/connect-via-ssh).

        <Frame>
          <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/11-ssh-key.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=573ee0c407b29e35f3f3c6cd35508950" alt="SSH keys section with three options: select, generate, or add a new SSH key" width="677" height="197" data-path="images/docs/cloud/virtual-instances/create-an-instance/11-ssh-key.png" />
        </Frame>

        In addition to SSH keys, a password can also be set for the Virtual Instance, as described in step 7. A password is required to connect to a Linux instance from the [Customer Portal](/cloud/virtual-instances/connect/connect-to-your-instance-via-control-panel#connect-to-a-linux-instance).
      </Tab>

      <Tab title="Windows Virtual Machine">
        Configure **Access** by setting a password for the Admin user.

        <Frame>
          <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/12-access-for-windows-instance.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=37df9bdfa393920872388f7fc8fb6963" alt="Access field with a password" width="677" height="157" data-path="images/docs/cloud/virtual-instances/create-an-instance/12-access-for-windows-instance.png" />
        </Frame>

        <Info>
          The password must contain 8-16 characters, including at least one lowercase letter (`a-z`), one uppercase letter (`A-Z`), one number (`0-9`), and one special character (`!#$%&'()*+,-./:;<=>?@[]^_{|}~`).
        </Info>

        The password can be used to connect to a Windows instance from the [Customer Portal](/cloud/virtual-instances/connect/connect-to-your-instance-via-control-panel) or from a local machine using RDP (Remote Desktop Protocol).
      </Tab>
    </Tabs>

    ## Step 7 (Optional). Configure additional options

    ### Instance initialization

    <p>Enable the **User data** toggle to customize the instance during the initial boot using a `cloud-init` agent.</p>

    <Frame>
      <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/13-add-user-data.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=0eacd65cdd6e6c57369f264904af733e" alt="User data field with password configuration" width="1024" height="718" data-path="images/docs/cloud/virtual-instances/create-an-instance/13-add-user-data.png" />
    </Frame>

    <p>A password for connecting to a Linux instance from the [Customer Portal](/cloud/virtual-instances/connect/connect-to-your-instance-via-control-panel) or [via SSH](/cloud/virtual-instances/connect/connect-via-ssh) can be set via **User data**. Insert the following code into the field, replacing `my-password` with the chosen password:</p>

    ```yaml theme={null}
    #cloud-config 
    password: my-password 
    chpasswd: { expire: False } 
    ssh_pwauth: True
    ```

    <Info>
      On Cloud instances with Windows OS, the `password` parameter cannot be used in both the "Access" and "User data" fields. Since the "Access" field is required, configuring user data on Windows instances is not possible. Allowed parameters are listed in the [API reference](/api-reference/cloud#tag/Instances/operation/InstanceCreateSetV2.post).
    </Info>

    <Accordion title="Configure a password hash">
      A password hash — a machine-readable set of symbols — can be configured to protect the actual password from being exposed. To generate a hash, use the following Python script:

      ```
      #!/usr/bin/env python3  
      \# based on [https://stackoverflow.com/a/17992126/117471](https://stackoverflow.com/a/17992126/117471)\# pip3 install passlib  
      import sys  
      from getpass import getpass  
      from passlib.hash import sha512_crypt  
      passwd = input() if not sys.stdin.isatty() else getpass()  
      print(sha512_crypt.hash(passwd, rounds = 5000 ))
      ```
    </Accordion>

    <Warning>
      When an instance is only in a private subnet, DHCP must be enabled in the subnet settings to allow password-based login.
    </Warning>

    ### Tags

    <p>Enable **Add tags** to add a key-value pair as metadata for the Virtual Instance.</p>

    ### Placement

    <p>Enable **Add to placement group** to determine how to place multiple instances.</p>

    <Frame>
      <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/15-add-placement-group.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=55370d7fee88316b4d256421de52b298" alt="The field to add the VM to a placement group" width="630" height="453" data-path="images/docs/cloud/virtual-instances/create-an-instance/15-add-placement-group.png" />
    </Frame>

    <Accordion title="Types of placement groups">
      <p>Instances can be placed in one of three types of groups:</p>

      * **Affinity** groups assemble virtual machines on the same hardware. Machines launched in one affinity group will exchange data faster because they are located on the same server.
      * **Anti-Affinity** groups work the opposite way: all Virtual Machines in this group will be separated across different physical hardware. This increases fault tolerance of a cluster: Even if something goes wrong with one server, machines on the other servers will remain available.
      * **Soft Anti-Affinity** groups encourage, but don't strictly enforce, the separation of Virtual Machines. Unlike a strict anti-affinity policy, where machines may never be placed together, soft anti-affinity allows placement on the same hardware when it is necessary due to factors like resource constraints or high demand. It is suitable for users who want to use the anti-affinity policy by default while also avoiding machine creation failures if an unused host is not found.
    </Accordion>

    <p>Add the instance to an existing placement group or create a new one by clicking **Add placement group**.</p>

    ## Step 8. Specify the number of instances

    <p>Indicate how many instances with the same configuration are needed. No more than 10 instances can be created at a time.</p>

    <Frame>
      <img src="https://mintcdn.com/gcore-doc-1894/Dsn9N5qugAZ3zecV/images/docs/cloud/virtual-instances/create-an-instance/16-number-of-instances.png?fit=max&auto=format&n=Dsn9N5qugAZ3zecV&q=85&s=d02ffd8d0def58d34c54585828a80269" alt="The field to set the number of instances" width="682" height="225" data-path="images/docs/cloud/virtual-instances/create-an-instance/16-number-of-instances.png" />
    </Frame>

    <p>Set the **Name Template** for the instances. Select one of the available options from the dropdown:</p>

    * `{ip_octets}` — the placeholder is replaced with the last two octets of the assigned IP address — `my-vm-{ip_octets}` becomes `my-vm-10-0-5`.
    * `{instance_role}` — the placeholder is replaced with the value of the `instance_role` metadata tag set in the **Add tags** section.

    ## Step 9. Create the instance

    <p>Click **Create Instance**.</p>

    <p>The instance transitions to the **Building** status while resources are being allocated.</p>

    <p>After that, the instance moves to the **Power on** status and is ready to use.</p>
  </MethodSection>

  <MethodSection id="api" label="REST API">
    <p>Creating a Linux VM via the REST API consists of three API calls: list available images, list flavors, then submit the create request. The instance is reachable via SSH within about 30 seconds of the task finishing.</p>

    <Info>
      An [API token](/account-settings/api-tokens) is required, along with a [project ID](/api-reference/cloud/projects/list-projects) and [region ID](/api-reference/cloud/regions/list-regions).
    </Info>

    <p>Open a bash terminal and set these as environment variables before running the examples:</p>

    ```bash theme={null}
    export GCORE_API_KEY="{YOUR_API_KEY}"
    export GCORE_CLOUD_PROJECT_ID="{YOUR_PROJECT_ID}"
    export GCORE_CLOUD_REGION_ID="{YOUR_REGION_ID}"
    ```

    ## Add an SSH key

    <p>If the project does not yet have an SSH key, register one before creating the instance. The create call references the key by name, not by ID.</p>

    | Parameter    | Required | Description                                                           |
    | ------------ | -------- | --------------------------------------------------------------------- |
    | `name`       | Yes      | Identifier used as `ssh_key_name` in the create call.                 |
    | `public_key` | No       | An RSA or Ed25519 public key string. Omit to generate a new key pair. |

    <Tabs>
      <Tab title="Python SDK">
        ```python theme={null}
        ssh_key = client.cloud.ssh_keys.create(
            name="my-key",
            public_key="ssh-ed25519 AAAA... user@host",
        )
        ```
      </Tab>

      <Tab title="Go SDK">
        ```go theme={null}
        sshKeyCreated, err := client.Cloud.SSHKeys.New(ctx, cloud.SSHKeyNewParams{
            Name: "my-ssh-key",
        })
        if err != nil {
            panic(err.Error())
        }
        fmt.Printf("%+v\n", sshKeyCreated.ID)
        ```
      </Tab>

      <Tab title="curl">
        ```bash theme={null}
        curl -X POST "https://api.gcore.com/cloud/v1/ssh_keys/$GCORE_CLOUD_PROJECT_ID" \
          -H "Authorization: APIKey $GCORE_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{
            "name": "my-key",
            "public_key": "ssh-ed25519 AAAA... user@host"
          }'
        ```

        Response:

        ```json theme={null}
        {
          "id": "36a7a97a-0672-4911-8f2b-92cd4e5b0d91",
          "name": "my-key",
          "state": "ACTIVE"
        }
        ```
      </Tab>
    </Tabs>

    <p>When `public_key` is omitted, the response includes a `private_key` field - save it immediately, as it is not shown again.</p>

    ## Quickstart

    <p>Complete scripts for the full flow. See the step-by-step section below for details on each call.</p>

    <Tabs>
      <Tab title="Python SDK">
        ```python theme={null}
        import os
        from gcore import Gcore

        client = Gcore()

        # Step 1. Find Ubuntu 22.04 image
        images = client.cloud.instances.images.list()
        image = next(img for img in images.results if "ubuntu-22.04" in img.name and "x64" in img.name)
        print(f"Image: {image.name} ({image.id}), min_disk={image.min_disk} GiB")

        # Step 2. Pick a flavor (IDs are region-specific; this selects 2 vCPU / 4 GB)
        flavors = client.cloud.instances.flavors.list()
        flavor = next(f for f in flavors.results if f.vcpus == 2 and f.ram == 4096)
        print(f"Flavor: {flavor.flavor_id} ({flavor.vcpus} vCPU / {flavor.ram // 1024} GB RAM)")

        # Step 3. Create instance and wait (create_and_poll polls automatically)
        instance = client.cloud.instances.create_and_poll(
            name="my-vm",
            flavor=flavor.flavor_id,
            ssh_key_name="my-key",
            interfaces=[{"type": "external"}],
            volumes=[{
                "source": "image",
                "image_id": image.id,
                "size": max(20, image.min_disk),
                "boot_index": 0,
            }],
        )
        print(f"Instance ID: {instance.id}, Status: {instance.status}")

        # Step 4. Wait for ACTIVE status — handled automatically by create_and_poll()

        # Step 5. Get public IP
        for ifaces in instance.addresses.values():
            for iface in ifaces:
                print(f"Public IP: {iface.addr}")

        # Step 6. Connect via SSH
        for ifaces in instance.addresses.values():
            for iface in ifaces:
                print(f"ssh ubuntu@{iface.addr}")
        ```
      </Tab>

      <Tab title="Go SDK">
        ```go theme={null}
        package main

        import (
            "context"
            "fmt"
            "os"
            "strings"

            gcore "github.com/G-Core/gcore-go"
            "github.com/G-Core/gcore-go/cloud"
        )

        func main() {
            client := gcore.NewClient()
            ctx := context.Background()

            // Step 1. Find Ubuntu 22.04 image
            imageList, err := client.Cloud.Instances.Images.List(ctx, cloud.InstanceImageListParams{})
            if err != nil {
                panic(err.Error())
            }
            var imageID string
            for _, img := range imageList.Results {
                if strings.Contains(img.Name, "ubuntu-22.04") && strings.Contains(img.Name, "x64") {
                    imageID = img.ID
                    fmt.Printf("Image: %s (%s)\n", img.Name, img.ID)
                    break
                }
            }

            // Step 2. Pick a flavor (IDs are region-specific; this selects 2 vCPU / 4 GB)
            flavorList, err := client.Cloud.Instances.Flavors.List(ctx, cloud.InstanceFlavorListParams{})
            if err != nil {
                panic(err.Error())
            }
            var flavorID string
            for _, f := range flavorList.Results {
                if f.Vcpus == 2 && f.Ram == 4096 {
                    flavorID = f.FlavorID
                    fmt.Printf("Flavor: %s (%d vCPU / %d MB RAM)\n", f.FlavorID, f.Vcpus, f.Ram)
                    break
                }
            }

            // Step 3. Create instance and wait (NewAndPoll polls automatically)
            instance, err := client.Cloud.Instances.NewAndPoll(ctx, cloud.InstanceNewParams{
                Flavor:       flavorID,
                NameTemplate: gcore.String("my-vm-{ip_octets}"),
                SSHKeyName:   gcore.String("my-key"),
                Interfaces: []cloud.InstanceNewParamsInterfaceUnion{{
                    OfExternal: &cloud.InstanceNewParamsInterfaceExternal{},
                }},
                Volumes: []cloud.InstanceNewParamsVolumeUnion{{
                    OfImage: &cloud.InstanceNewParamsVolumeImage{
                        ImageID:   imageID,
                        Size:      gcore.Int(20),
                        BootIndex: gcore.Int(0),
                    },
                }},
            })
            if err != nil {
                panic(err.Error())
            }
            fmt.Printf("Instance ID: %s, Status: %s\n", instance.ID, instance.Status)

            // Step 4. Wait for ACTIVE status — handled automatically by NewAndPoll()

            // Step 5. Get public IP
            // Step 6. Connect via SSH
            for _, addrs := range instance.Addresses {
                for _, addr := range addrs {
                    fmt.Printf("ssh ubuntu@%s\n", addr.Addr)
                }
            }
        }
        ```
      </Tab>
    </Tabs>

    ## Step-by-step

    <p>Each step below explains what the call does, which parameters matter, and what the response looks like. Use this section to understand the flow or to debug a specific step.</p>

    <Accordion title="Show all steps">
      ### Step 1. Select an OS image

      The image determines the operating system and sets the minimum boot volume size for Step 3.

      <Tabs>
        <Tab title="Python SDK">
          ```python theme={null}
          import os
          from gcore import Gcore

          client = Gcore()
          images = client.cloud.instances.images.list()
          for img in images.results:
              print(img.id, img.name)
          ```
        </Tab>

        <Tab title="Go SDK">
          ```go theme={null}
          package main

          import (
              "context"
              "fmt"

              gcore "github.com/G-Core/gcore-go"
              "github.com/G-Core/gcore-go/cloud"
          )

          func main() {
              client := gcore.NewClient()
              ctx := context.Background()
              imageList, err := client.Cloud.Instances.Images.List(ctx, cloud.InstanceImageListParams{})
              if err != nil {
                  panic(err.Error())
              }
              fmt.Printf("%+v\n", imageList.Count)
          }
          ```
        </Tab>

        <Tab title="curl">
          ```bash theme={null}
          curl "https://api.gcore.com/cloud/v1/images/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY"
          ```

          Response:

          ```json theme={null}
          {
            "results": [
              {
                "id": "e460e48c-6836-447e-bc9c-16fc4225d318",   // save as IMAGE_ID
                "name": "ubuntu-22.04-x64",
                "os_distro": "ubuntu",
                "os_version": "22.04",
                "architecture": "x86_64",
                "min_disk": 5
              }
            ]
          }
          ```
        </Tab>
      </Tabs>

      Save the `id` of the chosen image. The `min_disk` value is the minimum boot volume size in GiB.

      ```bash theme={null}
      export IMAGE_ID="{IMAGE_ID}"
      ```

      ### Step 2. Select a flavor

      A [flavor](/api-reference/cloud/flavors/list-flavors) sets the vCPU count and RAM for the instance. IDs are region-specific — run this call to see what's available in the selected region.

      <Tabs>
        <Tab title="Python SDK">
          ```python theme={null}
          flavors = client.cloud.instances.flavors.list()
          for f in flavors.results:
              print(f.flavor_id, f"{f.vcpus} vCPU / {f.ram // 1024} GB RAM")
          ```
        </Tab>

        <Tab title="Go SDK">
          ```go theme={null}
          instanceFlavorList, err := client.Cloud.Instances.Flavors.List(ctx, cloud.InstanceFlavorListParams{})
          if err != nil {
              panic(err.Error())
          }
          fmt.Printf("%+v\n", instanceFlavorList.Count)
          ```
        </Tab>

        <Tab title="curl">
          ```bash theme={null}
          curl "https://api.gcore.com/cloud/v1/flavors/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY"
          ```

          Response:

          ```json theme={null}
          {
            "results": [
              {"flavor_id": "g2-standard-2-4", "vcpus": 2, "ram": 4096},
              {"flavor_id": "g2-standard-4-8", "vcpus": 4, "ram": 8192}
            ]
          }
          ```
        </Tab>
      </Tabs>

      ```bash theme={null}
      export FLAVOR_ID="{FLAVOR_ID}"   # replace with a flavor_id from the response above
      ```

      ### Step 3. Create the instance

      The instance is created with a public interface - `"type": "external"` assigns a public IPv4 address automatically, without a separate floating IP.

      | Parameter              | Required | Description                                                           |
      | ---------------------- | -------- | --------------------------------------------------------------------- |
      | `name`                 | Yes      | Display name for the instance.                                        |
      | `flavor`               | Yes      | Flavor ID string from Step 2.                                         |
      | `ssh_key_name`         | Yes      | Name of an existing SSH key in the project.                           |
      | `interfaces[].type`    | Yes      | `"external"` for a public IP. Use `"subnet"` for a private interface. |
      | `volumes[].source`     | Yes      | `"image"` to boot from an image.                                      |
      | `volumes[].size`       | Yes      | Boot volume size in GiB. Must be at least `min_disk` from Step 1.     |
      | `volumes[].boot_index` | Yes      | Set to `0` for the boot volume.                                       |

      <Tabs>
        <Tab title="Python SDK">
          ```python theme={null}
          instance = client.cloud.instances.create_and_poll(
              name="my-vm",
              flavor=os.environ["FLAVOR_ID"],
              ssh_key_name="my-key",
              interfaces=[{"type": "external"}],
              volumes=[{
                  "source": "image",
                  "image_id": os.environ["IMAGE_ID"],
                  "size": 20,
                  "boot_index": 0,
              }],
          )
          instance_id = instance.id
          ```
        </Tab>

        <Tab title="Go SDK">
          ```go theme={null}
          instance, err := client.Cloud.Instances.NewAndPoll(ctx, cloud.InstanceNewParams{
              Flavor:       os.Getenv("FLAVOR_ID"),
              NameTemplate: gcore.String("my-vm-{ip_octets}"),
              SSHKeyName:   gcore.String("my-key"),
              Interfaces: []cloud.InstanceNewParamsInterfaceUnion{{
                  OfExternal: &cloud.InstanceNewParamsInterfaceExternal{},
              }},
              Volumes: []cloud.InstanceNewParamsVolumeUnion{{
                  OfImage: &cloud.InstanceNewParamsVolumeImage{
                      ImageID:   os.Getenv("IMAGE_ID"),
                      Size:      gcore.Int(20),
                      BootIndex: gcore.Int(0),
                  },
              }},
          })
          if err != nil {
              panic(err.Error())
          }
          // instance is ACTIVE when NewAndPoll returns
          instanceID := instance.ID
          ```
        </Tab>

        <Tab title="curl">
          ```bash theme={null}
          curl -X POST "https://api.gcore.com/cloud/v2/instances/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
              "name": "my-vm",
              "flavor": "'"$FLAVOR_ID"'",
              "ssh_key_name": "my-key",
              "interfaces": [{"type": "external"}],
              "volumes": [
                {
                  "source": "image",
                  "image_id": "'"$IMAGE_ID"'",
                  "size": 20,
                  "boot_index": 0
                }
              ]
            }'
          ```

          Response:

          ```json theme={null}
          {
            "tasks": ["d4a4b10e-5d22-4c23-8e09-f7a3e1c1a999"]   // save as TASK_ID
          }
          ```
        </Tab>
      </Tabs>

      ### Step 4. Wait for the instance

      Instance creation is asynchronous. The Python SDK `create_and_poll()` and the Go SDK `NewAndPoll()` in Step 3 handle polling automatically and return only when the instance is ACTIVE. For curl, poll `GET /cloud/v1/tasks/{task_id}` every 5 seconds until `state` is `FINISHED`.

      <Tabs>
        <Tab title="Python SDK">
          `create_and_poll()` in Step 3 polls automatically. `instance` is ACTIVE when Step 3 returns.
        </Tab>

        <Tab title="Go SDK">
          `NewAndPoll()` in Step 3 polls automatically. `instance` is ACTIVE when Step 3 returns.
        </Tab>

        <Tab title="curl">
          ```bash theme={null}
          curl "https://api.gcore.com/cloud/v1/tasks/$TASK_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY"
          ```

          While provisioning:

          ```json theme={null}
          {"state": "RUNNING", "task_type": "create_vm"}
          ```

          When complete:

          ```json theme={null}
          {
            "state": "FINISHED",
            "created_resources": {
              "instances": ["169942e0-9b53-42df-95ef-1a8b6525c2bd"],   // save as INSTANCE_ID
              "volumes": ["726ecfcc-7fd0-4e30-a86e-7892524aa483"]
            }
          }
          ```
        </Tab>
      </Tabs>

      ```bash theme={null}
      export INSTANCE_ID="{INSTANCE_ID}"
      ```

      ### Step 5. Get the public IP address

      The `addresses` field on the instance object contains the public IP assigned at creation.

      <Tabs>
        <Tab title="Python SDK">
          ```python theme={null}
          # instance.addresses is populated on the object returned by create_and_poll()
          for ifaces in instance.addresses.values():
              for iface in ifaces:
                  print(f"IP: {iface.addr}")
          ```
        </Tab>

        <Tab title="Go SDK">
          ```go theme={null}
          instance, err := client.Cloud.Instances.Get(
              ctx,
              "instance_id",
              cloud.InstanceGetParams{},
          )
          if err != nil {
              panic(err.Error())
          }
          fmt.Printf("%+v\n", instance.ID)
          ```
        </Tab>

        <Tab title="curl">
          ```bash theme={null}
          curl "https://api.gcore.com/cloud/v1/instances/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID/$INSTANCE_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY"
          ```

          Response:

          ```json theme={null}
          {
            "status": "ACTIVE",
            "addresses": {
              "pub_net": [
                {"addr": "203.0.113.42", "type": "fixed"}
              ]
            }
          }
          ```
        </Tab>
      </Tabs>

      The keys in `addresses` are network names that vary by region (typically `"pub_net"` for public interfaces), so iterate over all keys when parsing programmatically.

      ### Step 6. Connect via SSH

      Connect using the public IP from Step 5. Replace `ubuntu` with the default user for the chosen OS image.

      ```bash theme={null}
      ssh -i ~/.ssh/my-key ubuntu@203.0.113.42
      ```

      | OS          | Default user |
      | ----------- | ------------ |
      | Ubuntu      | `ubuntu`     |
      | Debian      | `debian`     |
      | CentOS      | `centos`     |
      | Rocky Linux | `rocky`      |
      | Fedora      | `fedora`     |

      The default security group is applied automatically, allowing inbound SSH (port 22) and ICMP.
    </Accordion>

    ## Filter images by OS

    <p>To list only Gcore-managed public images, add the `visibility=public` parameter.</p>

    <Tabs>
      <Tab title="Python SDK">
        ```python theme={null}
        images = client.cloud.instances.images.list(visibility="public")
        for img in images.results:
            print(img.id, img.name, img.os_distro, img.os_version)
        ```
      </Tab>

      <Tab title="Go SDK">
        ```go theme={null}
        imageList, err := client.Cloud.Instances.Images.List(ctx, cloud.InstanceImageListParams{
            Visibility: cloud.InstanceImageListParamsVisibilityPublic,
        })
        if err != nil {
            panic(err.Error())
        }
        for _, img := range imageList.Results {
            fmt.Printf("%s %s\n", img.ID, img.Name)
        }
        ```
      </Tab>

      <Tab title="curl">
        ```bash theme={null}
        curl "https://api.gcore.com/cloud/v1/images/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID?visibility=public" \
          -H "Authorization: APIKey $GCORE_API_KEY"
        ```
      </Tab>
    </Tabs>

    ## Clean up

    <p>To delete the instance and its boot volume in a single call, pass the volume ID in the `volumes` query parameter.</p>

    <Tabs>
      <Tab title="Python SDK">
        ```python theme={null}
        boot_volume_id = instance.volumes[0].id
        client.cloud.instances.delete_and_poll(
            instance_id=instance.id,
            volumes=boot_volume_id,
        )
        ```
      </Tab>

      <Tab title="Go SDK">
        ```go theme={null}
        taskIDList, err := client.Cloud.Instances.Delete(
            ctx,
            "instance_id",
            cloud.InstanceDeleteParams{},
        )
        if err != nil {
            panic(err.Error())
        }
        fmt.Printf("%+v\n", taskIDList.Tasks)
        ```
      </Tab>

      <Tab title="curl">
        ```bash theme={null}
        curl -X DELETE \
          "https://api.gcore.com/cloud/v1/instances/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID/$INSTANCE_ID?volumes=$BOOT_VOLUME_ID" \
          -H "Authorization: APIKey $GCORE_API_KEY"
        ```

        Response:

        ```json theme={null}
        {"tasks": ["ef3b0d34-..."]}
        ```

        Poll `GET /cloud/v1/tasks/{task_id}` every 5 seconds until `state` is `FINISHED`.
      </Tab>
    </Tabs>

    <p>If a floating IP was assigned to the instance, add `delete_floatings=true` to the query string.</p>

    ## Create a Basic VM

    <p>Basic VMs boot from a system-managed local SSD — no volume configuration is accepted on create. Networking is references-only: the create call attaches existing ports by ID rather than creating interfaces inline. The region is specified in the request body, so a single endpoint handles all regions without encoding the region in the URL path.</p>

    <Info>
      An [API token](/account-settings/api-tokens) is required, along with a [project ID](/api-reference/cloud/projects/list-projects) and a pre-created [port](/cloud/networking/ip-address/create-and-configure-a-reserved-ip-address) in the target region.
    </Info>

    <p>Set the required environment variables. `REGION_ID` is the numeric ID of a region that supports Basic VMs — query the [regions API](/api-reference/cloud/regions/list-regions) and filter for regions where `has_basic_vm` is `true`.</p>

    ```bash theme={null}
    export GCORE_API_KEY="{YOUR_API_KEY}"
    export GCORE_CLOUD_PROJECT_ID="{YOUR_PROJECT_ID}"
    export REGION_ID="{REGION_ID}"
    export PORT_ID="{YOUR_PORT_ID}"
    ```

    ### Step 1. Select an image

    <p>Only Linux images are accepted on Basic VM create. Query the images endpoint for the target region and save the ID of the chosen image.</p>

    ```bash theme={null}
    curl "https://api.gcore.com/cloud/v1/images/$GCORE_CLOUD_PROJECT_ID/$REGION_ID?visibility=public" \
      -H "Authorization: APIKey $GCORE_API_KEY"
    ```

    ```bash theme={null}
    export IMAGE_ID="{IMAGE_ID}"
    ```

    ### Step 2. List Basic VM flavors

    <p>Basic VM flavors use the `g1s-shared-*` or `g2s-shared-*` naming scheme. Query the standard flavors endpoint and filter by name prefix.</p>

    ```bash theme={null}
    curl "https://api.gcore.com/cloud/v1/flavors/$GCORE_CLOUD_PROJECT_ID/$REGION_ID" \
      -H "Authorization: APIKey $GCORE_API_KEY"
    ```

    The API returns:

    ```json theme={null}
    {
      "results": [
        {"flavor_id": "g2s-shared-1-2-25",  "vcpus": 1, "ram": 2048},
        {"flavor_id": "g2s-shared-2-4-50",  "vcpus": 2, "ram": 4096},
        {"flavor_id": "g2s-shared-4-8-80",  "vcpus": 4, "ram": 8192}
      ]
    }
    ```

    <p>The number after the RAM value in the flavor name is the local SSD size in GiB. Set `FLAVOR_NAME` to the chosen flavor.</p>

    ```bash theme={null}
    export FLAVOR_NAME="g2s-shared-1-2-25"
    ```

    ### Step 3. Create the Basic VM

    <p>Pass `region_id` in the request body rather than the URL path. The boot disk is created automatically; no volume fields are accepted.</p>

    | Parameter              | Required | Description                                                                                                                         |
    | ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
    | `region_id`            | Yes      | Numeric ID of the target region.                                                                                                    |
    | `flavor_name`          | Yes      | Shared-CPU flavor name from Step 1.                                                                                                 |
    | `image_id`             | Yes      | ID of a Linux OS image. Only Linux images are accepted.                                                                             |
    | `interfaces[].port_id` | Yes      | ID of a pre-created port in the same region. Pass multiple entries to attach several NICs; the array order sets the NIC boot order. |
    | `name`                 | No       | Display name. Auto-generated if omitted.                                                                                            |
    | `ssh_key_id`           | No       | UUID of an SSH key registered in the project.                                                                                       |
    | `user_data`            | No       | Base64-encoded cloud-init script.                                                                                                   |
    | `tags`                 | No       | Key-value map of labels.                                                                                                            |

    ```bash theme={null}
    curl -X POST "https://api.gcore.com/cloud/v1/projects/$GCORE_CLOUD_PROJECT_ID/basic_vms" \
      -H "Authorization: APIKey $GCORE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "region_id": '"$REGION_ID"',
        "flavor_name": "'"$FLAVOR_NAME"'",
        "image_id": "'"$IMAGE_ID"'",
        "name": "my-basic-vm",
        "interfaces": [{"port_id": "'"$PORT_ID"'"}]
      }'
    ```

    The API returns:

    ```json theme={null}
    {"tasks": ["d4a4b10e-5d22-4c23-8e09-f7a3e1c1a999"]}
    ```

    <p>Poll `GET /cloud/v1/tasks/{task_id}` every 5 seconds until `state` is `FINISHED`, then read the instance ID from `created_resources`.</p>

    ```bash theme={null}
    curl "https://api.gcore.com/cloud/v1/tasks/$TASK_ID" \
      -H "Authorization: APIKey $GCORE_API_KEY"
    ```

    When complete:

    ```json theme={null}
    {
      "state": "FINISHED",
      "created_resources": {
        "instances": ["a1b2c3d4-..."]
      }
    }
    ```

    ```bash theme={null}
    export BASIC_VM_ID="{BASIC_VM_ID}"
    ```

    ### Step 4. Get Basic VM details

    <p>The response includes `boot_volume_id` referencing the system-managed local disk, which is destroyed together with the VM on delete.</p>

    ```bash theme={null}
    curl "https://api.gcore.com/cloud/v1/projects/$GCORE_CLOUD_PROJECT_ID/basic_vms/$BASIC_VM_ID" \
      -H "Authorization: APIKey $GCORE_API_KEY"
    ```

    The API returns:

    ```json theme={null}
    {
      "id": "a1b2c3d4-...",
      "name": "my-basic-vm",
      "status": "ACTIVE",
      "region_id": 26,
      "flavor_name": "g2s-shared-1-2-25",
      "image_id": "bf221f42-...",
      "boot_volume_id": "e5f6a7b8-...",
      "addresses": {
        "doc-test-net": [{"addr": "10.10.10.5", "type": "fixed"}]
      }
    }
    ```

    ### Step 5. Update the Basic VM

    <p>Rename the VM or replace its interface list. Updating `interfaces[]` is a full replace — the API diffs the submitted list against the current state and attaches or detaches ports accordingly. Interface order is immutable after creation.</p>

    | Field          | Description                                                                                                                                  |
    | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
    | `name`         | New display name.                                                                                                                            |
    | `tags`         | Merge-patch map — a `null` value removes the key.                                                                                            |
    | `interfaces[]` | Full list of port IDs. Ports in the list but not currently attached are attached; ports currently attached but not in the list are detached. |

    ```bash theme={null}
    curl -X PATCH "https://api.gcore.com/cloud/v1/projects/$GCORE_CLOUD_PROJECT_ID/basic_vms/$BASIC_VM_ID" \
      -H "Authorization: APIKey $GCORE_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"name": "my-basic-vm-renamed"}'
    ```

    The API returns:

    ```json theme={null}
    {"tasks": ["c7d8e9f0-..."]}
    ```

    <p>Poll the task until `state` is `FINISHED`.</p>

    ### Step 6. Delete the Basic VM

    <p>Deleting a Basic VM also destroys its system-managed boot disk. Ports attached to the VM are detached but not deleted.</p>

    ```bash theme={null}
    curl -X DELETE "https://api.gcore.com/cloud/v1/projects/$GCORE_CLOUD_PROJECT_ID/basic_vms/$BASIC_VM_ID" \
      -H "Authorization: APIKey $GCORE_API_KEY"
    ```

    The API returns:

    ```json theme={null}
    {"tasks": ["f1a2b3c4-..."]}
    ```

    <p>Poll the task until `state` is `FINISHED`.</p>
  </MethodSection>

  <MethodSection id="terraform" label="Terraform">
    <p>Declare a Virtual Machine and its boot volume together. The [Terraform provider](/developer-tools/terraform/overview) models these as two separate resources — [`gcore_cloud_volume`](https://registry.terraform.io/providers/G-Core/gcore/latest/docs/resources/cloud_volume) and [`gcore_cloud_instance`](https://registry.terraform.io/providers/G-Core/gcore/latest/docs/resources/cloud_instance) — provisioned in order.</p>

    ## Create a VM

    <p>Declares a boot volume from an Ubuntu 22.04 image and a VM with a public IPv4 interface. `ssh_key_name` takes the name of an [SSH key](#add-an-ssh-key) registered in the project.</p>

    ```hcl theme={null}
    resource "gcore_cloud_volume" "boot" {
      project_id = var.project_id
      region_id  = var.region_id
      name       = "my-boot-volume"
      source     = "image"
      image_id   = var.image_id
      size       = 20
      type_name  = "ssd_hiiops"
    }

    resource "gcore_cloud_instance" "example" {
      project_id   = var.project_id
      region_id    = var.region_id
      flavor       = "g2-standard-2-4"
      name         = "my-vm"
      ssh_key_name = "my-keypair"

      volumes = [{ volume_id = gcore_cloud_volume.boot.id }]

      interfaces = [{
        type      = "external"
        ip_family = "ipv4"
      }]
    }

    output "instance_id" {
      value = gcore_cloud_instance.example.id
    }

    output "public_ip" {
      value = gcore_cloud_instance.example.addresses
    }
    ```

    <p>To find the image ID for the region, query the [Images API](/api-reference/cloud#tag/Images/operation/ImageListForProjectRegion.get):</p>

    ```bash theme={null}
    curl "https://api.gcore.com/cloud/v1/images/{project_id}/{region_id}?visibility=public&os_distro=ubuntu" \
      -H "Authorization: APIKey $GCORE_API_KEY" | jq '.results[] | select(.name | contains("22.04")) | {id, name}'
    ```

    <Warning>
      `gcore_cloud_volume` is an independent resource. Removing `gcore_cloud_instance.example` from the configuration does not delete its boot volume. To deprovision both, remove `gcore_cloud_volume.boot` from the configuration as well, then run `terraform apply`.
    </Warning>

    ## Add an SSH key

    <p>If the project does not yet have an SSH key, declare it as a Terraform resource. SSH keys are project-wide and do not require `region_id`.</p>

    ```hcl theme={null}
    resource "gcore_cloud_ssh_key" "my_key" {
      project_id = var.project_id
      name       = "my-keypair"
      public_key = "ssh-ed25519 AAAA... user@example.com"
    }

    resource "gcore_cloud_instance" "example" {
      # ...
      ssh_key_name = gcore_cloud_ssh_key.my_key.name
    }
    ```

    <p>When `public_key` is omitted, Gcore generates a key pair. The private key is returned once in the response and is not stored — save it before applying.</p>

    ## Optional fields

    | Field                      | Description                                                                                                                    |
    | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
    | `user_data`                | Base64-encoded cloud-init script that runs at first boot.                                                                      |
    | `security_groups`          | List of security group IDs. If omitted, the default security group is applied.                                                 |
    | `username` / `password_wo` | Creates a Linux user or sets the Windows Admin password at boot.                                                               |
    | `vm_state`                 | `"active"` (default) to start immediately; `"stopped"` to keep the VM off.                                                     |
    | `tags`                     | Map of key-value labels attached to the instance.                                                                              |
    | `name_template`            | Auto-generates the name from the public IP, e.g. `"my-vm-{ip_octets}"`. Use instead of `name` when dynamic naming is required. |

    <p>Example with `user_data` and `tags`:</p>

    ```hcl theme={null}
    resource "gcore_cloud_instance" "example" {
      # ... required fields ...

      user_data = base64encode(<<-EOF
        #cloud-config
        password: your-password
        chpasswd: { expire: false }
        ssh_pwauth: true
      EOF
      )

      tags = {
        env  = "production"
        team = "backend"
      }
    }
    ```

    ## Delete a VM

    <Warning>
      `gcore_cloud_volume` is an independent resource. Removing `gcore_cloud_instance.example` from the configuration does not delete its boot volume. To deprovision both, remove `gcore_cloud_volume.boot` from the configuration as well, then run `terraform apply`.
    </Warning>

    <p>Remove both `gcore_cloud_instance.example` and `gcore_cloud_volume.boot` from the configuration, then run `terraform apply` — Terraform deletes the instance first, then the volume.</p>

    ```hcl theme={null}
    # Remove or comment out both blocks:
    # resource "gcore_cloud_instance" "example" {
    #   name       = "my-vm"
    #   flavor     = "g2-standard-2-4"
    #   ...
    # }
    # resource "gcore_cloud_volume" "boot" {
    #   name      = "my-boot-volume"
    #   source    = "image"
    #   ...
    # }
    ```

    ```bash theme={null}
    terraform apply
    ```

    ## Import a VM

    <p>Import the instance and its boot volume separately to bring existing infrastructure under configuration management:</p>

    ```bash theme={null}
    terraform import gcore_cloud_instance.example '<project_id>/<region_id>/<instance_id>'
    terraform import gcore_cloud_volume.boot '<project_id>/<region_id>/<volume_id>'
    ```

    <p>To find the boot volume ID, look for `"is_root_volume": true` in the instance details:</p>

    ```bash theme={null}
    curl "https://api.gcore.com/cloud/v1/instances/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID/$INSTANCE_ID" \
      -H "Authorization: APIKey $GCORE_API_KEY" | jq '.volumes[] | select(.is_root_volume == true) | .id'
    ```
  </MethodSection>
</MethodSwitch>
