Skip to main content
An instance pool is a launch template plus a target count. The platform keeps that many instances running from that template, replaces ones that fail, and spreads them across hosts. It is a primitive for identical, interchangeable machines. Everything it does follows from that: replicas cannot have fixed addresses, a template change does not touch what is already running, and a pool is what makes one public address answerable by several instances at once.

Create a pool

The launch template, the sizing bounds, and how replicas get named.

Sizing and healing

What the pool converges, what it replaces, and the counter to alert on.

Rolling a template change

Why editing the template changes nothing yet, and what a refresh does.

A shared public address

Anycast across the replicas — and the ways it is not a load balancer.

Creating a pool

template is the launch config, in the same shape a standalone instance create takes — same field names, same types, same meanings. There is no separate launch-template resource to create, version or share; the template belongs to the pool. A flavor and a primary subnet are required: template.flavor_id and template.networks[0].subnet_id. Index 0 is the primary NIC; the rest are extras.
An older, flat shape — flavor_id, subnet_id, extra_nics and friends at the top level — still works, and the response renders both, so nothing written against either has to move. They are alternatives, not layers: sending template together with any flat launch field is a 400 rather than a precedence rule you would have to know to predict what your replicas boot as.Read template in responses. The flat fields are a projection of the same stored config and cannot disagree with it.
A template cannot carry a fixed ip_address or mac. Every replica launches from the same template, so a fixed address would have the second replica ask for one the first already holds. Both are refused with a 400 rather than dropped in silence.

The image is resolved once

template.image_id takes the same three forms instance create does — an id, name:version, or a bare name. Unlike instance create, the reference is resolved once, when the pool is created (or when the template is replaced), and the resulting image id is what every replica boots — including replacements spawned months later. That is deliberate. A tag re-resolved per replica would let a healed member boot a newer build than its siblings, and a pool whose members are quietly not identical is the premise of the primitive breaking silently. To move a pool to a new build, change the template and refresh.

What the replicas are called

Each replica gets a sequence number, stable for as long as it holds the slot, and is named <pool-name>-<sequence_num>web-asg-0, web-asg-1, and so on. GET /v1/instance-pools/{pool_id}/instances lists the live bindings with their sequence numbers; a replacement takes over the freed number.
Instance names are unique per account, so a pool called web cannot coexist with an instance you already named web-0. Pool names themselves are 1–127 characters of letters, digits, dot, dash and underscore, unique per account.

Per-replica disks and addresses

template.data_volumes creates a blank volume with each replica and reclaims it with that replica. delete_on_termination defaults to true; set it to false and a replica scaled in, replaced, or torn down with the pool releases its volume back to available instead of destroying it. template.assign_public_ip gives each replica its own floating IP on its primary NIC, allocated as the pool scales out and released as it scales in. A NIC in template.networks[] carries its own flag, so a secondary interface can be the public one. Each address counts against your floating_ips quota. That is a different thing from the pool’s shared address — see below.

Sizing and convergence

0–100
The target. This is the only sizing field you can change later.
fixed at create
Bounds on desired_count. Both default to desired_count when omitted, and neither is patchable — to change them, create a new pool.
Creating the pool returns 201 immediately. The instances are spawned by a background reconciler, which is also what converges the pool toward desired_count whenever you change it, so watch the pool rather than expecting members in the create response. Four counters tell you where a pool stands, and confusing two of them is the usual source of a false alarm:
status: "active" means member_count == desired_count — the pool holds the members it was asked for. It is not a claim that all of them are up. A pool can be active with live_count below desired_count when members have stopped. Read live_count for liveness.scaling means it does not hold its target and is converging: after a create, after a desired_count change, and for the length of a refresh. error carries error_message and is still reconciled — the pool keeps being retried. deleting is a teardown in flight.
error_message is cleared when the pool reaches its target, and is left in place through a later resize: a pool that failed to spawn and is being scaled again has not yet proved the failure is behind it.

What gets replaced, and what does not

Each pass, the pool replaces any member whose vm_state is error or deleted, and any binding whose instance has been deleted out from under it. The replacement takes the freed sequence number and launches from the pool’s current template.
The pool does not health-check anything inside the guest, and it does not replace a member you stopped. A replica that is stopped, or running but serving errors, stays a member: live_count falls for a stopped one, and nothing at all changes for a wedged application.Replacement is driven by the instance failing, not by the workload failing. If you need application-level health checking, put a load balancer in front.
Scaling in removes the highest sequence numbers first, so a scale from 5 to 3 retires -4 and -3. Each retirement runs the full instance delete, so its quota, its volumes and its addresses are handled exactly as for a standalone instance. Replicas are spread across hosts — best effort. Each new replica avoids the hosts its siblings already occupy, but when the fleet has no room the spread is dropped rather than the launch failing, so replicas can end up sharing a host.

Changing the template

PATCH /v1/instance-pools/{pool_id} changes desired_count, the pool’s tags, the template, or any combination. Every field is optional; sending none of them is a 400 rather than a silent no-op.
A new template replaces the stored one wholesale. Anything you leave out is cleared, not kept — replacement rather than a deep merge, so a shorter networks or data_volumes array cannot be read as a truncation and silently drop an interface or a disk. Send the whole config you want.
A template change decides what the pool launches next. The instances already running keep what they booted with, because a live VM cannot change flavor, tier, subnet or its tags in place. So between the edit and a roll, the pool legitimately holds members from two different templates. stale_instance_count is how many are on the older one, and a non-zero value is the signal that a template change has not been rolled out yet.
This is the same split between “edit the template” and “replace the instances” that a PATCH silently replacing every running member would erase — a destructive operation wearing the shape of an edit.

Rolling the pool

Answers 202 with the pool as it stands, and replaces every member not launched from the current template — including any that predate template tracking. Asynchronous, and deliberately so: each replacement is a VM boot, and a request that waited would time out long before a pool of any size finished.
1

One member per pass

The reconciler retires one stale member at a time, oldest slot first, so the roll walks the pool in a predictable order.
2

And only once the pool is whole

The next replacement waits until the pool is back at size with every member running. A template that does not boot therefore stalls the roll with the pool intact, instead of walking it down one instance at a time.
3

Capacity does not dip

For the length of the roll the pool runs one instance over its target, so a replacement is already serving before anything is retired. desired_count is not touched — the surge is derived, not written into what you asked for.
A pool already at max_count has nowhere to grow, so it replaces in place — retiring a member before its replacement is up, and dipping by one for each. Leave headroom between desired_count and max_count at create if you want rolls that never reduce capacity.
Watch refresh_in_progress and stale_instance_count for progress. The pool reads scaling for the duration and flips back to active only when nothing is stale — calling it settled mid-roll would tell you a refresh had finished while most of it had not started. Asking again while a roll is running is accepted and does not restart it.

Two sets of tags

A pool carries two tag maps and they answer different questions.

tags

Labels the pool resource. Read by IAM conditions as basalt:ResourceTag/<key> and used for cost attribution. Takes effect immediately, touches no instance, and replaces the whole set — an empty object clears them, an omitted field leaves them alone.

template.tags

Stamped on every replica the pool launches. Part of the launch config, so changing it affects future launches only and needs a refresh to reach what is already running.
Editing template.tags alone is the easiest way to end up with a pool whose members carry two different tag sets — which matters if an IAM policy or a cost report keys on them. stale_instance_count is how many are still on the old set.

One address for the whole pool

POST /v1/instance-pools/{pool_id}/floating-ips binds a floating IP you already allocated to the pool. One public IP, answered by every replica — an anycast address — as opposed to template.assign_public_ip, which gives each replica its own.
The operation names the pool because the pool is what makes the address work. Each member’s translation rule is matched per hypervisor, so two members on one hypervisor both match there: one answers and the other receives nothing, with no signal anywhere that half the capacity is dark.A pool spreads its replicas across hypervisors, and the address’s members are derived from the live ones — one per hypervisor. A replica sharing a hypervisor with an existing member is left out rather than added and silently starved, so the address can have fewer members than the pool has replicas. members on the floating IP says which.
Membership is maintained for you: a scale-out joins, a scale-in leaves, a replaced member is swapped. There is no per-replica attach to make, and the network service’s own attach and detach are refused on a pool’s address — use these two endpoints.

It is not a load balancer

With more than one member, the region’s edge picks one member per connection, by hashing the flow’s addresses and ports, and every packet of that connection goes to the same one. That spreads connections across independent instances and survives the loss of a host.
It does none of the things a load balancer does. There is no health checking of what runs inside the instance, so a replica whose application is broken keeps receiving its share of connections. Connections in progress to a member that goes away are not moved — they end. And there is no TLS termination, no request routing, and no way to weight members.

Requirements and removal

The floating IP must be unattached and yours, and the pool’s subnet must already route 0.0.0.0/0 to an internet gateway. Attaching is idempotent: re-attaching the same address to the same pool returns it unchanged. A 409 means the address is already attached to something, or already belongs to another pool. DELETE /v1/instance-pools/{pool_id}/floating-ips/{floating_ip_id} stops routing the address to the pool.
The address is not released. You allocated it, it stays yours and unattached, to reuse or to release with DELETE /v1/floating-ips/{floating_ip_id}. Detaching one the pool does not hold answers 204.
GET /v1/instance-pools/{pool_id}/floating-ips lists the shared addresses with their current members. Per-replica addresses are not here — those belong to the replica and are read from the instance’s NIC listing.

Deleting a pool

DELETE /v1/instance-pools/{pool_id} tears down every instance the pool owns and drops the pool. It is idempotent, and answers 204. Deleting the pool is the only way to remove its members: deleting a replica directly just frees its sequence number, and the pool spawns a replacement for it on the next pass.

Permissions

Every pool operation authorizes against crn:compute:<region>:<account>:instance-pool/<id> — that is the value an IAM policy statement must name to scope a permission to one pool.
A refresh authorizes as compute:UpdateInstancePool, the same action as a PATCH, not as an action of its own. Granting someone the ability to edit a pool therefore also grants them the ability to roll it, which replaces every running member.
Replicas are launched as the principal that created the pool, so that principal’s compute:CreateInstance permission — and its iam:PassRole on template.iam_role_id, if the template carries one — is what every later heal and scale-out runs under. See policies and roles.

Troubleshooting

active means member_count == desired_count, not that the members are up. Read live_count. A gap between them is members that exist and are not running — stopped, still booting, or wedged — and the pool does not replace a stopped member.
The roll waits for the pool to be back at size with every member running before retiring the next one. If the new template does not boot, the roll stalls there by design rather than emptying the pool — check the newest replica’s fault and its console output.stale_instance_count stops falling as soon as that happens, and refresh_in_progress stays true.
Expected. A template change decides what the pool launches next; the instances already running keep what they booted with. stale_instance_count counts them, and POST /v1/instance-pools/{pool_id}/refresh rolls them.
template on a PATCH replaces the stored config wholesale — anything omitted is cleared. Read the pool back, edit the whole template object you got, and send all of it.
Working as designed. Members are one per hypervisor, so replicas that share a hypervisor with an existing member are left out rather than added and silently receiving nothing. Spreading is best effort, so a pool larger than the fleet — or one scaled up when the fleet is full — can land co-resident replicas.
Three causes, and the message says which: the id is missing or malformed; the pool’s subnet has no default route to an internet gateway; or the region does not have shared pool addresses switched on. A 409 is different — that is an address already attached to something else, or already held by another pool.
The pool converges toward desired_count, so deleting a member is read as drift and refilled at the freed sequence number. Lower desired_count, or delete the pool.
desired_count must sit within min_count and max_count, and none may exceed the platform cap of 100. Both bounds default to desired_count when omitted and are fixed for the pool’s life — a pool created without max_count cannot later grow past the size it was created at.

Next

Instances

Everything a replica is: flavors, images, disks, interfaces and the lifecycle.

Images

Pinning a build so a pool’s replicas stay identical.