Skip to main content
Client examples use CLI v0.13.0 and Go SDK v0.15.0. See CLI setup and Go configuration. Go snippets assume a configured cfg, a ctx from context.Background(), and imports for log, basaltic (github.com/basaltic-sh/sdk-go) and compute (github.com/basaltic-sh/sdk-go/compute).
Go to Compute → Instances and choose Create instance. The form is one card per decision — Details, Flavor, Image, Boot volume, Data volumes, Networking, SSH keys, IAM role, User data — with a running summary alongside them.Networking is the card to slow down on. Every interface carries its own VPC, Subnet, Security groups and Public floating IPs; Add network interface adds another, and the first in the list is the primary NIC.
The call answers 202 with the instance in pending. Building the boot disk, the interfaces and the seed happens after the response, so poll GET /v1/instances/{instance_id} and watch current_state.
Security groups are per interface. They belong in networks[].security_groups, not in a top-level field. A NIC provisioned with an empty list gets no per-interface filtering at all, which is not the same thing as a closed instance.
An instance with no networks entry boots with no networking. Nothing fails and nothing warns you — the guest comes up with no interface, so nothing reaches it and it reaches nothing. Send at least one entry; index 0 becomes the primary NIC, the one carrying the guest’s default route and its route to the metadata service.

Sizing: flavors

A flavor is CPU and memory, and nothing else. It carries no disk size — the boot disk is a volume sized at launch — so GET /v1/flavors is a catalog of compute shapes, not of machine types.
shared | dedicated
shared oversubscribes CPU for higher density. dedicated pins each vCPU 1:1 to a physical core. Both classes run on the same hosts; the class decides how the instance draws on a host’s threads.
general | loadbalancer | database
Only general flavors can run instances and instance pools. The other two are reserved for the managed products, whose nodes are operated and priced by the platform, and are refused here. Pass ?family=general when listing.
percent of one vCPU
The guaranteed floor and the ceiling. The instance is entitled to vcpus × cpu_baseline_pct / 100 cores however busy its neighbours get, and may not exceed the burst ceiling even on an idle host. Absent means the flavor guarantees no floor, or imposes no ceiling beyond the vCPU count.
aggregate throughput
The instance’s network ceiling, in megabits/s, across all its interfaces. Absent means uncapped. A resize moves this with the flavor.
The catalog is small and comes back in one page — GET /v1/flavors is not paginated.

Choosing an image

image takes four forms, and the difference matters for reproducibility:
"image": "debian-13" follows the tag. You get whichever build is current at the moment the instance is created, so two launches a month apart can boot different bits.
Names resolve across your own images plus the public platform catalog, and use the request architecture (default amd64). A complete image CRN pins its architecture. See Images and keypairs for how a tag moves and what the listing does and does not show you.

Disks

volumes is one list, and the boot disk is the entry that says so:
boolean
Marks the disk cloned from image. At most one entry may set it, and a boot entry takes no mount_path or fstype — both come from the image, so sending either is refused rather than ignored.Omit the boot entry entirely and you get the image’s min_disk_gb on the region’s default tier.
integer, 1–16384
required
On the boot entry this may not go below the image’s min_disk_gb. Anything smaller is a 400 before the instance row exists, not a failed build.
string
With a mount path set, the in-guest agent formats the disk — only if it is blank — and mounts it there, using fstype: ext4 by default, or xfs.
boolean, default true
Every launch-time volume defaults to true, which is the opposite of a volume you attach later.
The boot volume is destroyed when the instance is deleted. If you want it to outlive the instance, flip delete_on_termination on the attachment after launch — see what survives a delete.

SSH keys and cloud-init

keypairs authorizes existing keypairs on the instance. The public keys are baked into the seed at launch, so deleting the keypair afterwards does not remove access from an instance already running. user_data is base64-encoded cloud-init, and the platform merges it over a base configuration that creates the login user:
Your user-data must decode to a document whose first line is #cloud-config. Anything else — a #!/bin/bash script, a MIME multipart bundle — is ignored in silence: the instance boots, your script never runs, and nothing in the API says so. Wrap shell work in cloud-init’s runcmd instead.
The merge is by top-level key, not a deep merge. A key you set replaces the base entirely. Sending your own users: block therefore replaces the basaltic user — and takes your keypair’s authorized keys with it, because those live inside it. Add users under a different key, or restate the base user’s ssh_authorized_keys yourself.
A user_data document that is not valid YAML fails the build, and the instance lands in error rather than booting without it.

Giving the instance an identity

At launch, iam_role accepts a role name, UUID or CRN and attaches that IAM role. Software inside the guest then fetches short-lived credentials from the metadata service at 169.254.169.254 — no access key is written to the instance, and nothing has to be rotated.
Two conditions, and both are easy to miss. The role’s trust policy must accept crn:compute:*:*:instance/* (or the specific instance CRN), and the principal making the launch call needs iam:PassRole on the role, separately from compute:CreateInstance. Without the trust policy the instance launches and the credentials never mint; without iam:PassRole the launch itself is denied. Roles and instance identity walks through both.
Instance responses return iam_role as an optional summary instead of a string reference or iam_role_id:
The summary is visible with instance read access; it does not require iam:GetRole. It contains only id, crn and name, excluding sensitive role fields such as policies. The field is omitted when no role is attached, the role has been deleted or it belongs to another account. Opening the role’s IAM details still requires iam:GetRole.

Names, tags and metadata

unique per account
1–128 characters, DNS-safe: letters, digits, dot, dash, underscore, starting and ending alphanumeric. A name that collides with another instance in the account is a 409.
not supported
PATCH /v1/instances/{instance_id} edits description, metadata and tags. A name is fixed for the life of the instance.In the console those three are the Details, Tags and Metadata cards on the instance’s Settings tab, and there is no name field among them.
IAM and cost
Read by IAM policy conditions as basalt:RequestTag/<key> on the create and basalt:ResourceTag/<key> afterwards, and used for cost attribution. See policies.
replaced, not merged
The map you PATCH becomes the whole set of your keys.
Metadata keys under the basalt: namespace are control-plane state — how an instance proves which pool or managed resource it belongs to. You cannot set them, and you cannot remove them: they are preserved through a PATCH whether or not you echo them back, and proposing a different value for one is a 400 rather than a silent drop. Read-modify-write against the whole metadata map is therefore safe.

Reading instance addresses

The instances table shows Private IPv4, Public IPv4, and IPv6 from the primary NIC. Instance details show those same values individually. The Networking tab lists each NIC with its primary addresses. An attached IPv6 floating IP is shown instead of the NIC’s directly attached IPv6 address. The overview shows one address per instance, preferring public IPv4, then public IPv6, then private IPv4.

Resource references

See Resource references for classification, image versions and fetching an instance by name or CRN. flavor, keypairs, iam_role, and per-NIC security_groups accept a UUID, a CRN, or an exact name. Flavors are regional; keypairs and security groups belong to your account, and IAM roles belong to your account. subnet accepts a UUID or a complete VPC/subnet CRN. A bare subnet name cannot be resolved without its VPC parent. Interface attachment likewise requires a UUID or complete VPC/subnet/interface CRN. Every compute list accepts exact name and crn filters. They intersect with other filters and pagination. A foreign or mismatched CRN matches no rows; a malformed or empty CRN returns 400. Empty names do not broaden the list. Instance lists also accept flavor and image references. NIC-list names match interface names within the instance bindings; duplicate names can return multiple interfaces. Floating IPs have no name identity.