Skip to main content
An image is the disk an instance’s boot volume is cloned from. A keypair is the SSH public key baked into it at launch. Both are regional, both live in the compute service, and both are named things an IAM policy can wildcard.

Names are movable tags

How debian-13 and debian-13:20260807 differ, and when the tag moves under you.

Importing

Presigned URL in, background conversion, and what the status column tells you.

What the listing shows

Why a build you published yesterday is missing, and the flag that brings it back.

Keypairs

Generate or import, the one-time private key, and what deleting one does not do.

Images

Public and private

GET /v1/images returns your own images plus the public platform catalog — the maintained OS images every account can boot. Platform images carry no account in their CRN (crn:compute:<region>::image/<id>), so they read the same for everyone.
visibility: "public" on an image you own does not share it. Only images in the platform account are visible across accounts; an image of yours stays private to your account whatever its visibility says. The field is stored, returned and filterable — it just is not a sharing mechanism, and there is no cross-account image sharing today.

A name is a tag, a version is a build

Every image row has a name and a version, and they do different jobs.

name

A movable tag, shared by every build behind it. debian-13 points at whichever build is current for its (name, architecture).

version

Identifies one build within that name, and must be unique there. Omit it on import and the server stamps a UTC timestamp, so every build is addressable whether or not you labelled one.
That gives three ways to name an image at launch, and the choice is a choice about reproducibility: is_current on the image says whether it is the name’s current target. Older versions stay fully bootable by id and by name:version — promotion moves a pointer, it does not retire anything.
Publishing a version a tag already carries is a 409, not a second anonymous build. The error says which half to change: rename and you publish a second tag for the same release, which is the mess the constraint exists to prevent — change the version instead.

Importing an image

No image bytes flow through the API. You upload the disk to a bucket you control — the Basaltic object store, S3, MinIO, anything — with a real multipart S3 client, then hand over a presigned GET URL.
1

Register the import

The response is 202 with status: "importing". Accepted formats are qcow2, raw, vmdk, vhd, vhdx and vdi; all are converted to a raw base on import, and format describes what is at source_url.
2

Wait for the fetch and conversion

A worker fetches the URL once, converts the disk and imports it. The URL is not retained afterwards, so it only has to stay valid long enough to be read.
3

Poll until active

active means bootable, and size_bytes is filled in from what was actually written. error means the import gave up; import_error says why.
source_url must be https, and must not resolve to a private address. Loopback, RFC 1918 and ULA private ranges, link-local and metadata addresses are all refused — on the original URL and again on every redirect, against the address it actually resolves to. A bucket reachable only inside your VPC cannot be imported from; presign from something publicly resolvable.
import_error appearing does not mean the import has stopped. Each failed attempt writes the reason onto the row so a customer polling it can see what is going wrong, and the import is retried. The row reaching error is what says nothing further will be attempted.

Publishing without switching the tag

current defaults to true: a completed import becomes the name’s current version and future launches of that bare name boot the new bits. Send "current": false to stage a build without switching, then promote it later:
Promotion is atomic — whatever else was current for that (name, architecture) is demoted in the same operation. The same call is how you roll back: point the name at the older build and launches follow it immediately.
The switch happens when the import completes, not when it is accepted. A new build is importing for as long as the conversion takes, and the tag keeps pointing at the previous build for all of it — so publishing over a name in use never leaves it resolving to something that cannot boot.Only an active image can be made current; asking to promote one that is still importing is a 400.

What List images returns

GET /v1/images is a listing of what is worth launching, not of every row you own. One tag normally contributes one entry. A build drops out only when a newer build holds its name. That is superseded history, and nothing else — the predicate is deliberately narrow:
Whatever its age. These are rows you have to deal with — an import you are waiting on, or one that failed and is still holding an image slot until you delete it.
“Not current” is the wrong test on its own. A version you staged deliberately, and a version you rolled back from, are both not current and both still yours to act on. Only being superseded by something newer takes a build out of the listing.
A deleted image, or a platform release the catalog withdrew, is hidden and excluded — from every listing, whatever you filter on. Passing ?status=hidden returns nothing rather than revealing them, because every other endpoint answers 404 for a hidden image.
Pass all_versions=true for a tag’s whole history. Results are ordered by name, and paginate through meta.marker like every other listing. The other filters are os, architecture, visibility, status and name — which is a substring match, not a prefix or an exact one.

End of life

eol_date records the day an OS release stops receiving free security updates for a default install. Absent means nobody has recorded one, which is not the same as supported indefinitely. Omit eol_date on a new build and it inherits the date the name’s current version carries, so republishing a tag cannot quietly stop tracking its release. An explicit null on PATCH clears it; omitting the field leaves it alone.
Platform images are withdrawn from the catalog a grace period after their eol_date. They stay bootable by id until then, and the date is published well ahead of it so you can plan the move.After withdrawal, resolving the bare name fails with a message naming the release and the date it ended — so a launch that suddenly cannot find some-distro-11 tells you why, rather than looking like a typo. Anything pinning name:version or an id of a withdrawn platform image also stops resolving, because withdrawal hides every build of the tag.
An eol_date on your own image is stored and shown, and nothing acts on it. The platform never hides an image you own — there is no un-hide, so withdrawing your image would be a silent, unrecoverable delete of something you still hold.

Deleting an image

DELETE /v1/images/{image_id} answers 202 and marks the image hidden, so clones already in flight can finish. The underlying data is reclaimed as soon as nothing is cloned from it — immediately when no instance was built from the image, and by a background sweep otherwise.
Superseded builds of your own images are never reclaimed for you. Every build you publish holds an images slot and its bytes against image_storage_gb until you delete it — so a pipeline that publishes on every commit needs a deletion step, or the quota becomes the deletion step.
Deleting an already-deleted image is a 404. A platform image the catalog withdrew at end of life keeps its data on purpose, so deleting it by id is how you ask for that data to go.

Sizes

enforced floor
The smallest boot volume that can hold the image. Defaults at import to the image’s virtual size rounded up. A launch or reinstall asking for less is refused.
recorded, not enforced
What the image is documented to need. It is stored and returned for you to read; nothing stops an instance booting on a flavor below it.

Keypairs

A keypair is an SSH public key stored under a name in your account. A name is 1–128 characters of letters, digits, dot, dash and underscore, starting and ending alphanumeric, and unique per account. It goes straight into the CRN — crn:compute:<region>:<account>:keypair/<name> — so an IAM policy can fence a naming convention rather than a list of ids.
Import accepts the ordinary authorized_keys line shape — ssh-ed25519, ssh-rsa, ecdsa-sha2-nistp256, with or without a trailing comment.
Omit public_key and a keypair is generated for you — and the private key is returned once, in the 201 response, and never stored. There is no endpoint that returns it again. Save it before you do anything else with the response, or delete the keypair and create another.
Generated keys are ed25519. fingerprint is the SHA256 form (SHA256:…), which is what ssh-keygen -lf prints by default.

Using a keypair

Name keypairs at launch with key_names, and their public keys are written into the instance’s cloud-init seed. Several are fine — every named key ends up in the default user’s authorized_keys.
Deleting a keypair does not revoke access to instances already running. The key is baked in at launch, exactly as it is on AWS: the instance never consults the keypair again, so removing the record removes it from future launches only. To cut off access to a running instance, remove the key inside the guest.
The keypairs an instance was launched with are embedded in the instance itself, so GET /v1/instances/{instance_id} shows them without a second call.

Troubleshooting

Check whether a newer build holds the same name. The default listing shows one entry per tag, and drops builds a newer one has superseded. Add ?all_versions=true to see the history — the build is still there and still bootable by name:version or by id.
A bare name follows the tag, and the tag moves when a new build is published as current. Pin name:version or an id in anything that has to be reproducible; keep the bare name for “always the latest”.
Two causes. A platform release past end of life has been withdrawn — the error names the release and the date. Or the name has no current version, which happens after a PATCH with current: false on the only current build: the tag then points at nothing, though every build behind it still launches by name:version. Promote one to fix it.
Read import_error — a failed attempt records the reason on the row while retrying. The usual causes are a presigned URL that expired before the fetch, a format that does not match the bytes at the URL, or a host that resolves to a private address and is refused.Note that an import which finishes but exceeds your image_storage_gb quota is refused and not retried: rebuilding the same bytes would cost a full download and conversion to reach the same answer.
That name already carries that version. A name is shared by every build behind it on purpose, so publish under a new version rather than a new name.
That is the design. Only the platform catalog crosses accounts; visibility: "public" on your own image does not share it, and an image id belonging to another account answers 404 rather than 403, so the API never confirms it exists.

Next

Launching instances

Flavors, boot volumes, cloud-init and the instance lifecycle.

Instance pools

Where an image reference is resolved once, at create, and every replica boots the same build.