> ## Documentation Index
> Fetch the complete documentation index at: https://docs.basaltic.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Volumes and interfaces

> Hot-plugging disks and NICs onto a running instance, which of them survive it, and where a public address comes from.

## Volumes

`GET /v1/instances/{instance_id}/volumes` lists the attachments in boot order,
boot disk first, each resolved with the volume's current name, tier, size and
status.

<Tabs>
  <Tab title="Console">
    **Attach volume** on the instance’s **Volumes** tab opens a dialog asking for the
    **Volume**, and optionally a **Device name** — *Leave blank to assign the
    next available name* — a **Mount path** and a **Filesystem**. Only volumes
    the instance can actually take are offered.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/instances/{instance_id}/volumes
    { "volume": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "mount_path": "/data", "fstype": "ext4" }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    basaltic compute instance attach-volume <instance-id> \
      --volume 7c9e6679-7425-40de-944b-e07fc1f90ae7 \
      --mount-path /data --fstype ext4
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    _, err := compute.New(cfg).AttachInstanceVolume(ctx, instanceID,
        &compute.AttachInstanceVolumeRequest{
            Volume: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
            MountPath: basaltic.String("/data"),
            Fstype: basaltic.String("ext4"),
        })
    ```
  </Tab>
</Tabs>

The volume must be in the same account and in status `available`, and the
instance must be `running` or `stopped`. The disk is hot-plugged; `device`
picks the next free slot (`vdb`, `vdc`, …) unless you name one.

With `mount_path` set, the in-guest agent formats the disk — **only if it is
blank** — and mounts it there. `fstype` picks the filesystem: `ext4` by default,
or `xfs`. Nothing else is accepted. Leave `mount_path` empty and you get the
block device and nothing else.

Detaching is `DELETE /v1/instances/{instance_id}/volumes/{volume_id}`, also
`202`, also requiring `running` or `stopped`. In the console it is the row
action on the instance's **Volumes** tab, confirmed as **Detach volume** — and
the boot disk has no such control there, because detaching it is refused
anyway.

## Network interfaces

`GET /v1/instances/{instance_id}/nics` is the source of every instance address.
Each NIC returns an `addresses` array, with floating IP summaries nested under
their target address. The instance object has no primary or public IP fields.

That listing is ordered by boot index, primary first, and each entry resolves
the interface's current MAC, IPv4, IPv6, subnet and VPC.

Placement comes back as an embedded `subnet` object rather than the former
`subnet_id` and `vpc_id` fields. It carries the parent VPC and the nullable
route-table summary described under
[subnet placement](/networking/subnets#reading-placement), so `subnet.name` and
`subnet.vpc.name` are readable straight off the NIC — no separate subnet or VPC
read is needed to show where an interface sits:

```json theme={null}
{
  "nics": [
    {
      "interface_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "boot_index": 0,
      "subnet": {
        "id": "9b2e4f1a-3c5d-4e6f-8a90-1b2c3d4e5f60",
        "crn": "crn:network:sa-saopaulo-1:my-account:vpc/production/subnet/private",
        "name": "private",
        "cidr": "10.0.1.0/24",
        "vpc": { "id": "c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9", "name": "production" },
        "route_table": { "id": "…", "crn": "…", "name": "private-routes" }
      }
    }
  ]
}
```

`subnet` is **null** when the referenced subnet no longer resolves — a subnet
deleted while the NIC still records it. Treat that as unavailable placement and
refresh; it does not mean the interface has no subnet. In Go, check
`nic.Subnet != nil` before reading `nic.Subnet.VPC`.

Launch requests use `networks[].subnet` string references. Later NIC
attachments require an existing `interface` reference. Do not pass the embedded
subnet object to either operation.

### Attaching and detaching

`POST /v1/instances/{instance_id}/nics` attaches an existing standalone
interface using the `interface` field. It keeps its address, MAC, and security
groups. Creating a NIC during attachment is not supported: create it through
the network interface API first. Detaching returns it to standalone state.

The instance must be `running` or `stopped`. The attachment is durable the
moment the call returns — it is part of the instance's spec and survives
reboots — but the guest does not have the device when the call returns.
Delivery is asynchronous, and the response says what it takes:

```json theme={null}
{ "attachment": { "interface_id": "...", "mac": "02:1a:2b:3c:4d:5e", "ip": "10.0.1.42", "boot_index": 1 } }
```

A **stopped** instance comes up with the device at its next start, and
`restart_required` is absent.

A **running** instance is given the device while it runs, where the region can
do that: the attachment reports no `restart_required`, and the interface
appears in the guest moments later. Poll `GET /v1/instances/{instance_id}/nics`
for the attachment, or watch the guest for a link carrying the `mac` above —
that MAC is the interface's identity on the network, so it is what the device
arrives with.

Where the region cannot, the response carries `restart_required: true`, and a
**hard** reboot delivers it:

```bash theme={null}
POST /v1/instances/{instance_id}/reboot
{ "hard": true }
```

A soft reboot will not — it is an ACPI reboot inside the same launcher, and an
interface past the first is a network on that launcher, fixed for its lifetime.

<Note>
  How many interfaces an instance may carry is a property of the region, and so
  is whether a running guest can be given one. Where the limit is one, a second
  attach is refused with **409** and a launch asking for two with **400**. Treat
  `restart_required` as the answer for the region you are in rather than
  assuming either behaviour: it is absent when there is nothing to do but wait.
</Note>

<Warning>
  **The address is DHCP's, and the guest has to ask for it.** Nothing
  configures the new interface inside the guest — the platform puts the device
  there and the subnet's DHCP holds its lease. An image that brings up a
  network device when it appears (cloud-init with network hotplug enabled,
  NetworkManager, `systemd-networkd` with a wildcard match) picks it up on its
  own; one that configures only the interfaces it booted with will hold the new
  link, with no address, until something asks:

  ```bash theme={null}
  sudo dhclient -1 <device>
  ```

  A reboot configures it either way, which is why a guest that gained the
  device live can still look unconfigured until its network manager is told
  about it.
</Warning>

<Tabs>
  <Tab title="Console">
    Open the instance's **Networking** tab and choose **Attach NIC**.
    Select an existing **Interface**, then **Attach interface**. The selector's
    create action opens interface creation when you need a new standalone NIC.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST /v1/instances/{instance_id}/nics
    { "interface": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" }
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    basaltic compute instance attach-nic <instance-id> \
      --interface 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    _, err := compute.New(cfg).AttachInstanceNIC(ctx, instanceID,
        &compute.AttachInstanceNICRequest{
            Interface: "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        })
    ```
  </Tab>
</Tabs>

<Warning>
  Detaching the **only** NIC on an instance is refused. There is no way to
  reach a guest with no interface, so the API will not leave you with one.
</Warning>

### Public addresses

`networks[].floating_ip_assignment` requests public floating IPs at launch:

| Value        | Allocation                             |
| ------------ | -------------------------------------- |
| `none`       | No floating IP (default).              |
| `ipv4`       | One IPv4 floating IP.                  |
| `ipv6`       | One IPv6 floating IP.                  |
| `dual_stack` | One of each family.                    |
| `auto`       | IPv4 and, when the NIC has IPv6, IPv6. |

Each requested family needs a directly attached address and a default route
to an internet gateway in that family. Allocations count against the matching
`floating_ips_v4` or `floating_ips_v6` quota. Enabling IPv6 later does not add a
floating IP automatically.

All public IPv4 addresses are floating IPs. IPv4 outbound access can also use
a NAT gateway. Native global IPv6 can reach the internet without a floating IP
when routing and security rules allow it; private ULA requires a public IPv6
floating IP for internet access. Attaching a public FIP makes outbound traffic
from its target address use that mapping. A private FIP translates traffic to
the private virtual address and its replies, without changing unrelated egress.
