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

Go to Compute → Instance pools and choose Create instance pool. After Details comes a Scaling card with Min, Desired and Max; the rest of the form is the launch template, card for card the same as creating one instance — Flavor, Image, Boot volume, Data volumes, Networking, IAM role, User data.Min is the floor and Max is the ceiling. You can change both later with Scale.The two tag maps below are named for the difference between them: Instance tags are stamped on every replica the pool launches, while Pool tags label the pool itself and reach none of its instances.
template configures the members launched by the pool. Select one or more top-level subnets in the same account, region and VPC. Each member has one interface; new members go into the least-populated selected subnet. The template controls its security groups, address families and optional per-member floating IPs. A template.flavor is required.
A template cannot carry a fixed address inside addresses or a fixed 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.

IAM role references and responses

Create and replacement requests accept template.iam_role as a UUID, CRN or exact name string for a role in your account. Attaching the role requires iam:PassRole and instance trust authorization; see Roles and instance identity. Pool responses return an optional role summary inside template, instead of iam_role_id. This response excerpt shows the attachment identity:
The reference contains only id, crn and optional name, and is visible with pool read access without iam:GetRole. template.iam_role is null when no role is attached. If a referenced role is deleted, its identity remains and its name is omitted. Opening the role’s IAM details still requires iam:GetRole.

Subnet references and responses

Create and update requests use top-level subnets string selectors. Responses return subnets, vpc, template.flavor, template.image, template.iam_role and template.security_groups as resource references with id, crn and optional name. Fetch a subnet for its CIDRs and routing details. Convert references to id or crn when sending requests. Response excerpt:

The image is resolved once

template.image 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. A replacement takes over the freed number. GET /v1/instance-pools/{pool_id}/instances returns full Instance objects in instances, plus pagination meta, just like the top-level instance list. Each object includes its state, addresses, flavor, image and role; you do not need a separate instance request for each member. Read the replica sequence from metadata["basalt:pool:sequence_num"]. Its value is a string, including "0" for the first slot. Filter this list with name, crn, current_state, flavor or image. It also accepts limit and marker. While meta.has_more is true, request the next page using meta.marker as marker, keeping the same filters and limit. Collect instances from every page to list all matching members.
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.volumes creates a disk 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.floating_ip_assignment gives each member its own floating IP on its single interface. Addresses are allocated on scale-out and released on scale-in; each counts against your floating_ips quota. Every selected subnet must support the explicitly requested address families and their internet routes. That is a different thing from the pool’s shared address — see below.

Sizing and convergence

All three sizing fields are mutable integers. The resulting values must satisfy 0 ≤ min_count ≤ desired_count ≤ max_count ≤ 100. On create only, omitted bounds default to desired_count. On PATCH, omitted bounds keep their stored values. If you omit desired_count, the current target is clamped into the new bounds: raising the minimum above it raises the target; lowering the maximum below it lowers the target. A target already inside the bounds stays unchanged. If you explicitly send desired_count, it must fit the resulting bounds. Invalid sizing returns 400 without applying any part of the update, including tags or a template sent with it. Zero is valid, including both bounds at zero. Sizing does not replace the launch template or request a refresh. These controls apply to customer-managed pools. Pools owned by a managed service cannot be resized through the instance-pool API; use that service’s controls.

Automatic scaling

An autoscaling policy adjusts desired_count within your configured bounds. The same policy is available on load balancers. Creating or updating a policy does not change the launch template. CPU target tracking requires min_count of at least 1.
Open the pool and choose Scale. Set Minimum count, Maximum count and Desired count, then enable Automatic scaling. Choose a Metric source and set its target; for CPU, use Target CPU (%). Choose Scale to save. The Scaling tab shows the policy and recent scaling decisions.
The target is CPU utilization as a percentage of the members’ allocated vCPUs. Members must be running and past their warmup period before demand is evaluated. Scaling waits while capacity is converging, members are retiring, or a refresh is in progress. You can also scale from a metric you publish to Telemetry, such as queue depth. The policy owner needs telemetry:ReadMetrics; that permission is checked again while the policy runs. Only metrics in the pool’s account and region are eligible.
A queue depth of 750 with a target of 100 jobs per instance recommends eight instances, before bounds and step limits are applied. Samples are aggregated within each series first, then across series. expected_series must match the number of selected series; a missing series prevents scale-in. Counter metrics can use sample_aggregation: "rate" to account for resets before rates are combined across series. Publish a fresh zero when the queue is empty. Missing, stale or incomplete data never means zero and cannot remove capacity. Custom demand metrics can grow a pool from zero; CPU policies cannot. With several metrics, the largest valid capacity recommendation wins. Missing data still permits a valid scale-out recommendation from another metric. Default warmup is 180 seconds, cooldown is 60 seconds, and scale-down stabilization is 300 seconds. Each decision adds at most four instances or removes at most one. These limits and drain_seconds (default 120) are configurable. Cooldown and stabilization remain in effect across service restarts. Read autoscaling_status.reason and autoscaling_status.history for the current wait condition and recent capacity changes. You can still change sizing manually. Automatic evaluation resumes after the cooldown. To stop automatic changes, submit the policy with enabled: false; its metric configuration is retained when you send it back. Policies are replaced as a whole, so include the metric configuration when disabling one. Scale-in first marks a member as retiring and withdraws it from load balancer backend sets and shared floating IPs. Deletion waits for withdrawal acknowledgement and the drain grace period. This does not invoke an application shutdown hook; long-running jobs must tolerate instance termination. Long-lived TCP, UDP and WebSocket sessions can end at the drain deadline. Changes to shared floating IP forwarding membership can also change connection placement during withdrawal.

Adjusting the bounds

The following sequence starts with minimum 2, desired 3 and maximum 6. It raises the target to 4, lowers it to 2, then scales to zero. Scaling to zero retires all members; their disks follow delete_on_termination.
Open the pool and choose Scale to open Scale instance pool. Set Minimum count to 4, keep Maximum count at 6 and leave Desired count unchanged. Choose Scale; the target becomes 4.Reopen Scale, set Minimum count to 2 and Maximum count to 2, and leave Desired count unchanged; the target becomes 2. To scale to zero, reopen the dialog and set both bounds to 0, again leaving Desired count unchanged. The console omits an unchanged desired value so the API can adjust it automatically.
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 means an active error fault — read every entry in faults — and is still reconciled: the pool keeps being retried. deleting is a teardown in flight.
Scaling codes (POOL_LAUNCH_FAILED, POOL_SCALE_OUT_FAILED, POOL_SCALE_IN_FAILED) clear when the pool reaches its target. A later resize does not clear them first: 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 current_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, min_count, max_count, autoscaling, 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.
The pool’s Settings tab edits three parts of it: Tags on the pool, and Instance tags and Instance metadata on the launch template. Minimum count, Maximum count and Desired count are under Scale on the header.Open Edit template to change the launch configuration and selected subnets. Saving Instance tags or Instance metadata preserves the pool’s subnet selection.
A new template replaces the stored configuration completely. Include every template field you want future members to use. Top-level subnets is preserved when omitted.
When building a replacement from a pool response, convert the role summary to one string reference: for example, set template.iam_role to the summary’s id or crn. Do not send the summary object back. Omitting iam_role from a replacement clears the attachment for future launches. The top-level subnets set is independent of the template. Omit it to preserve placement. Sending it replaces the eligible subnets for future launches; existing members move only when you explicitly refresh the pool. Reordering the same subnets does not mark members stale. 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

  1. Open Instance pools and select your pool.
  2. Choose Roll instances beside Scale.
  3. Confirm replacement of every running member on an older or unknown template. Members on the current template are retained. Without surge headroom, the roll waits; use Scale to raise Maximum count above Desired count (up to 100).
  4. Watch Roll in progress and Stale instances. A zero stale count does not mean the roll has finished while Roll in progress remains.
Refresh reloads the page’s data. It does not roll instances.
It 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 retirement waits until the pool is above desired 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

With headroom, the pool launches 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.
When max_count == desired_count, a refresh waits instead of retiring a stale member below desired capacity. This also applies if a bounds update removes headroom during a roll. Normal scaling and healing continue; lowering desired can still retire members as part of scale-in.Raise max_count above desired_count to let the roll proceed. For example, with desired 3 and maximum 3, update maximum to 4 and leave desired unchanged using the sizing controls. The waiting refresh resumes automatically. At the platform cap of 100, adding headroom requires lowering desired first, which reduces your requested capacity.
Watch refresh_in_progress and stale_instance_count for roll progress, and member_count and live_count for capacity. A zero stale count only means no members use an old template. After the refresh flag clears, the pool may still need to remove its surge member and converge to desired. Wait for the flag to be false, status: "active", and both counts to equal desired before treating capacity as settled. 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.floating_ip_assignment, which gives each replica its own.
The pool’s Floating IPs tab lists its shared addresses and offers Attach floating IP, which picks from the addresses you hold that are attached to nothing.One address, every replica explains membership and readiness. Each address has an Interfaces link to its paginated interfaces view, with Health and Reason for each member. An empty pool retains ownership of its shared addresses.
The floating IP’s attached_to references the pool even when its interfaces collection is empty. All live replicas can become members, including replicas on the same host. GET /v1/floating-ips/{floating_ip_id}/interfaces returns canonical interface objects plus health and reason. See Reading the bindings. 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.
Without a configured floating IP health_check, health indicates guest liveness, not application readiness. Booting members remain listed as unhealthy and receive no traffic until admitted; images that never contact the instance metadata service are admitted after a few minutes. Configure a readiness check if traffic must wait for your application. Unhealthy members receive no traffic, and if all fail the address goes dark.Connections in progress to a member that goes away end. There is no TLS termination, request routing, or way to weight members.

Requirements and removal

The floating IP must be unattached (attached_to: null and an empty interfaces collection) and yours, and every selected pool 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. In the console it is the row action on the Floating IPs tab, confirmed as Detach floating IP.
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 in floating_ips, plus pagination meta, using the same response shape as the top-level floating IP list. It accepts name, crn, limit and marker. Floating IPs have no name, so supplying name returns an empty list. While meta.has_more is true, pass meta.marker as marker on the next request, preserving the filters and limit, and collect floating_ips from every page. Per-replica addresses are not here — those belong to the replica and are read from the instance’s NIC listing.

Deleting a pool

Delete pool is on the pool’s Settings tab, in the danger zone. The confirmation restates the cost — terminates every VM in the pool — and needs the pool name typed back.
It 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/<name> — 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, 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.
First compare max_count with desired_count. If there is no surge headroom, raise maximum above desired (up to 100). The refresh stays requested and resumes without another refresh call. Bounds changes during a refresh can put it into this waiting state; normal scaling continues.With headroom, the roll waits for a replacement above desired 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 faults and its console output.stale_instance_count stops falling as soon as that happens, and refresh_in_progress stays true.
Expected. When building a replacement from a pool response, convert the role summary to one string reference: for example, set template.iam_role to the summary’s id or crn. Do not send the summary object back. Omitting iam_role from a replacement clears the attachment for future launches.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 and build the complete replacement request, converting the role summary to a string reference as described under Changing the template.
Compare the pool’s live replicas with the address’s embedded member summaries. Membership converges as replicas are placed and removed; members can share a host. Check each member’s health and reason separately: booting or failing members stay listed but receive no traffic. An empty member list does not clear the pool’s attached_to ownership.
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.
Use whole numbers with 0 ≤ min_count ≤ max_count ≤ 100. An explicit desired_count must fall within those bounds. On create, omitted bounds default to desired; on update, they preserve the stored bounds. Omit desired on update to clamp it automatically. Invalid sizing applies none of the update. To grow beyond the original maximum, raise max_count before or together with desired.

Next

Instances

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

Images

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