Skip to main content

Create an instance

This guide creates a running instance from an existing image with tritonctl, the tenant CLI. You end with an instance record you can list, show, stop, and delete.

You need four things before you start:

  • tritonctl authenticated against your endpoint (tritonctl configure).
  • The UUID of the project the instance belongs to.
  • The UUID of the boot image.
  • The UUID of the subnet the primary NIC attaches to.

Every selector in tritonctl is a UUID. No command accepts a resource name.

warning

tritonctl instance create sends only ?project= on POST /v1/instances, and that handler requires both ?tenant= and ?project=. The command returns 400 MissingScope until the CLI is fixed. Use the API call in Create the instance through the API to get an instance today.

Register an SSH key

Keys registered this way are owned by the calling user. There is no tenant, silo, or project selector on this command.

On your workstation:

tritonctl ssh-key create --name workstation --public-key-file ~/.ssh/id_ed25519.pub

The command prints Registered ssh key followed by the new key's UUID, then the key's id, name, scope, and fingerprint. Record the UUID: you pass it to instance create as --ssh-key-id.

--public-key takes the OpenSSH key string inline instead of a path. The two flags conflict; passing neither fails with --public-key or --public-key-file is required.

The server parses the key as OpenSSH and rejects anything it cannot parse with invalid openssh public key, followed by the parser's own message.

To list the keys you already have:

tritonctl ssh-key list

The table columns are ID, NAME, FINGERPRINT, and SCOPE.

Find the image and subnet UUIDs

Read one image by UUID:

tritonctl image show <IMAGE_UUID>

The command prints id, name, os, version, size in bytes, and sha256.

tritonctl image list sends scope=tenant, and GET /v1/images implements scope=public only, so that command returns 400 ScopeNotImplemented. Get the image UUID from your operator until the tenant scope is implemented.

List the subnets in a VPC:

tritonctl subnet list --vpc <VPC_UUID>

The table columns are ID, NAME, and VPC. --vpc is required: the server rejects an unscoped subnet list with GET /v1/subnets requires `?vpc=<uuid>` .

tritonctl vpc list sends only ?project= against a handler that requires ?tenant= and ?project=, so it returns 400 MissingScope. Get the VPC UUID from your operator.

The subnet must live in a VPC inside the same project as the instance. A subnet from another project resolves as 404 NotFound.

Create the instance

On your workstation:

tritonctl --project <PROJECT_UUID> instance create \
--name web-1 \
--image-id <IMAGE_UUID> \
--primary-subnet-id <SUBNET_UUID> \
--ssh-key-id <SSH_KEY_UUID> \
--cpu 2 \
--memory-bytes 4294967296

--project is a global flag, so it can go anywhere on the line. Without it the command fails with --project is required to create an instance before any request is sent.

FlagRequiredValueNotes
--nameYesString1 to 63 bytes. No / or \, no ASCII control bytes, no leading or trailing whitespace. Must be unique within the project.
--image-idYesUUIDBoot image. Must be visible to you.
--primary-subnet-idYesUUIDSubnet for the primary NIC. Must be in a VPC in the same project.
--cpuYesIntegervCPU count. Zero is rejected.
--memory-bytesYesIntegerMemory budget in bytes. Zero is rejected.
--descriptionNoStringFree text.
--ssh-key-idNoUUIDSSH key to inject at first boot. Repeat the flag for more than one key.
--disk-bytesNoIntegerBoot disk size in bytes. See below.

The CLI hardcodes extra_nics to empty, mac to none, and user_data to none. There is no flag for cloud-init user data, an extra NIC, or a pinned MAC, even though the API accepts all three.

How the boot disk is sized

With --disk-bytes omitted, the boot disk is the image's content size, except for images whose compatibility block names the bhyve brand, where the floor is 20 GiB.

With --disk-bytes set, the value is floored at the image's content size (a volume smaller than the image cannot hold it) and capped at 16 TiB at the API edge.

What the request waits for

The create request runs the instance-create saga and does not return until the provisioning job reaches a terminal state. The response carries the instance record re-read after provisioning, so its lifecycle field is the instance's current state, not the pending it was created in.

The wait is bounded at 600 seconds. On timeout, or on a failed provisioning job, the saga unwinds: the instance record, its NICs, its IPs, and its disks are removed, and the request returns an error.

Create errors

ResponseCause
400 MissingScope, POST /v1/instances requires `?tenant=<uuid>&project=<uuid>` selectorsThe CLI does not send tenant=. See the API workaround below.
400 BadRequest, cpu must be greater than zero--cpu 0.
400 BadRequest, memory_bytes must be greater than zero--memory-bytes 0.
400 BadRequest, disk_bytes must be greater than zero and at most 16 TiB--disk-bytes is zero or above 16 TiB.
404 NotFoundThe image, an SSH key, the subnet, or the project does not exist or is not visible to you.
409 Conflict, instance with name "..." already exists in project ...Another instance in the project already has that --name.
409 Conflict, a validation message naming the instance field--name broke one of the name rules in the flag table.
503, no eligible compute node for placement; capacity, traits, affinity, or scope pinning rejected every candidatePlacement found no compute node that could host the instance.

Create the instance through the API

This is the request the CLI should be sending. Run it until tritonctl instance create sends both selectors.

Read your tenant UUID from the tenant_id claim on your token. On your workstation:

tritonctl whoami

tenant_id is printed only for an identityd token. An API key prints auth: API key and no claims.

Load the endpoint and access token into the shell:

eval "$(tritonctl env)"

Then post the instance:

curl -sS -X POST \
-H "Authorization: Bearer $TRITONCTL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"$TRITONCTL_ENDPOINT/v1/instances?tenant=<TENANT_UUID>&project=<PROJECT_UUID>" \
-d '{
"name": "web-1",
"image_id": "<IMAGE_UUID>",
"primary_subnet_id": "<SUBNET_UUID>",
"ssh_key_ids": ["<SSH_KEY_UUID>"],
"cpu": 2,
"memory_bytes": 4294967296
}'

name, image_id, primary_subnet_id, cpu, and memory_bytes are required. description, ssh_key_ids, disk_bytes, mac, extra_nics, and user_data default when omitted. The response is 201 with the instance record as JSON.

The silo selector is rejected on this route with 400 ScopeNotAccepted.

Confirm the instance

List the instances in the project:

tritonctl --project <PROJECT_UUID> instance list

The table columns are ID, NAME, STATE, and IMAGE. --project is not optional here either: with neither --project nor --image, the server returns 400 MissingScope.

Narrow by lifecycle state with --state, which is matched case-insensitively against pending, provisioning, running, stopping, stopped, and failed.

STATE carries the lifecycle in its wire form, which is a JSON object keyed by state rather than a bare string:

{"state":"running"}

Show one instance:

tritonctl instance show <INSTANCE_UUID>

The command prints id, name, lifecycle, and image. Pass -o json for the full record. JSON is the stable interface; the table layout is not.

The instance record carries no IP address. tritonctl has no command that prints the primary NIC's address.

Next steps