You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
CoderWorkspacePool: prebuilt workspaces with a scale subresource #153
Goal: manage Coder prebuilds from Kubernetes, with a scale subresource so kubectl scale, HPA or KEDA can resize the pool.
Feasibility gate (first step): today the prebuild count (DesiredPrebuildInstances) is part of a template preset, which comes from the template's Terraform. Before any API design, prove whether the count can be changed without publishing a new template version, and how the current number of ready prebuilds can be read. If it cannot, record the blocker here instead of building a scale API that does not really scale.
Prebuilds are a Premium feature; the controller must check the license.
Owner: maintainer desk.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: medium
I will not build a scale API for prebuilds yet. Coder has no API that resizes a prebuild pool in place.
Findings
I read the Coder source at v2.35.8, v2.36.6, v2.37.3 and main (cc0c868c55b2). I did not test runtime behavior on a licensed deployment.
The count changes only with a new template version. Coder writes a preset's desired_instances only when it imports a template version (coderd/provisionerdserver/provisionerdserver.go:2781 on main). No HTTP handler updates it. The prebuild write endpoints are the deployment-wide pause (PUT /api/v2/prebuilds/settings) and preset invalidation (POST /api/v2/templates/{template}/prebuilds/invalidate). Neither changes the count.
A new version per resize rebuilds the pool. Coder keeps prebuilds only for the active template version. When a new version becomes active, the reconciler deletes every prebuild of the old version and builds the new count (coderd/prebuilds/preset_snapshot.go:337 and :399). Old-version prebuilds cannot be claimed after the promotion (coderd/database/queries/prebuilds.sql:28). Each resize also:
marks workspaces built from the previous version as outdated,
adds a template version and two audit entries,
runs an import job on the provisioners,
competes with GitOps re-applies of CoderTemplate and with codertemplates/promote.
The REST API has no per-preset prebuild status. Coder's Prometheus metrics (coderd_prebuilt_workspaces_*) have per-preset desired, running and eligible gauges. The workspace API (owner:prebuilds) can rebuild running, agent-ready and in-progress counts. It cannot show schedule-aware desired counts, expiry, backoff or the failure hard limit.
License detection is simple. GET /api/v2/entitlements returns the feature workspace_prebuilds. The feature is usable when enabled is true and the entitlement is entitled or grace_period.
What works today
Calendar-based pool sizes: add scheduling to the prebuilds block of coder_workspace_preset (Coder v2.24.0 or later, provider v2.6.0 or later). Coder changes the count by hour, weekday and month without a new version. A change to the schedule needs a new version.
Static pool sizes from Git: set instances in the template files of a CoderTemplate. Each change publishes a version and rebuilds the pool, like any other template change.
Observation: enable Coder's Prometheus endpoint (CODER_PROMETHEUS_ENABLE) and read the coderd_prebuilt_workspaces_* metrics.
What will unblock this issue
An upstream API that sets a per-preset count override without a new template version, and that returns per-preset status. A sketch (not filed upstream):
PATCH /api/v2/templates/{template}/prebuilds/presets/{preset} (preset ID) with {"desired_instances_override": 6}. null clears it. The override wins over the Terraform count and schedules until it is cleared. Coder refuses presets without a prebuilds block and presets that are not on the active version. The override belongs to one template version, so a client re-applies it after a promotion.
GET /api/v2/templates/{template}/prebuilds/presets with effective desired, running, eligible, in progress and preset health.
Related upstream issues: coder/coder#20549 (per-template prebuild control) and coder/coder#19927 (prebuild counts through template variables).
Decision
I will not build CoderWorkspacePool with a scale subresource until a Coder release has such an API.
I will not build a read-only status view now. It cannot show schedule-aware desired counts or why a pool does not fill. The Prometheus metrics already show Coder's own numbers.
A smaller exact item stays possible: show each version's preset definitions (names, parameters, configured base instances) on CoderTemplateVersion. I will open it only on user demand.
Recheck trigger: a Coder release that adds a runtime count API for prebuild presets.
Follow-ups for pool PR 12 (activation gate), collected from the final assessments of #359, #364, #379 and #380. The pool kind is dormant, so none of them blocks a merge today. Each one must be fixed or explicitly accepted before activation.
The member cap check is exact only while member reconciles run one at a time. Set MaxConcurrentReconciles=1 for the pool and member controllers explicitly, or make the check race-free.
The status write in deleteFailedMembers does not store observedGeneration. The status shows a stale value until the final write of the reconcile.
A member that becomes used while its pool is deleted is garbage-collected before the pool releases it. The workspace stays in Coder, but the Kubernetes object goes.
Consent-loss deletes act on a cached read in the resolve step. A stale cache can delete warm members once more than needed. No workspace leaks.
Decide whether failures during a Coder outage count toward the failure limit. Today, failed members are deleted without counting while the pool's inputs do not resolve.
More follow-ups for pool PR 12, from the final assessment of #387 and #388 (claims). The pool kind is dormant, so none of them blocks a merge today. Each one must be fixed or explicitly accepted before activation.
Review comment 4277607232: two pool reconciles that run at the same time can bind two members to one claim. This needs two operator replicas at once. Planned fix: reserve the chosen member in the claim status before the member update.
Review comment 4277607237: a new pool that reuses a deleted pool's name with another owner overwrites the claim's ownerName. Planned fix: keep the stored name.
Add a CoderWorkspaceClaim watch that maps each claim to its pool. Without it, a new claim does not wake a Ready pool.
The pool's member watch must map bound members by the pool label or spec.poolName. After a bind, their controller reference points at the claim.
A claimant who renames a bound workspace makes the member fail with OwnershipUnknown. Decide whether to accept a rename.
Claims RBAC, including coderworkspaceclaims/finalizers, because the bind sets blockOwnerDeletion.
Add a test where a bound member gets a 404 or a 5xx from Coder and its claim stays Bound.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: medium
Activity
ThomasK33 commentedon Oct 3, 2026
Feasibility gate result: blocked upstream
I will not build a scale API for prebuilds yet. Coder has no API that resizes a prebuild pool in place.
Findings
I read the Coder source at v2.35.8, v2.36.6, v2.37.3 and main (
cc0c868c55b2). I did not test runtime behavior on a licensed deployment.desired_instancesonly when it imports a template version (coderd/provisionerdserver/provisionerdserver.go:2781on main). No HTTP handler updates it. The prebuild write endpoints are the deployment-wide pause (PUT /api/v2/prebuilds/settings) and preset invalidation (POST /api/v2/templates/{template}/prebuilds/invalidate). Neither changes the count.coderd/prebuilds/preset_snapshot.go:337and:399). Old-version prebuilds cannot be claimed after the promotion (coderd/database/queries/prebuilds.sql:28). Each resize also:CoderTemplateand withcodertemplates/promote.coderd_prebuilt_workspaces_*) have per-preset desired, running and eligible gauges. The workspace API (owner:prebuilds) can rebuild running, agent-ready and in-progress counts. It cannot show schedule-aware desired counts, expiry, backoff or the failure hard limit.GET /api/v2/entitlementsreturns the featureworkspace_prebuilds. The feature is usable whenenabledis true and the entitlement isentitledorgrace_period.What works today
schedulingto theprebuildsblock ofcoder_workspace_preset(Coder v2.24.0 or later, provider v2.6.0 or later). Coder changes the count by hour, weekday and month without a new version. A change to the schedule needs a new version.instancesin the template files of aCoderTemplate. Each change publishes a version and rebuilds the pool, like any other template change.CODER_PROMETHEUS_ENABLE) and read thecoderd_prebuilt_workspaces_*metrics.What will unblock this issue
An upstream API that sets a per-preset count override without a new template version, and that returns per-preset status. A sketch (not filed upstream):
PATCH /api/v2/templates/{template}/prebuilds/presets/{preset}(preset ID) with{"desired_instances_override": 6}.nullclears it. The override wins over the Terraform count and schedules until it is cleared. Coder refuses presets without aprebuildsblock and presets that are not on the active version. The override belongs to one template version, so a client re-applies it after a promotion.GET /api/v2/templates/{template}/prebuilds/presetswith effective desired, running, eligible, in progress and preset health.Related upstream issues: coder/coder#20549 (per-template prebuild control) and coder/coder#19927 (prebuild counts through template variables).
Decision
CoderWorkspacePoolwith a scale subresource until a Coder release has such an API.instances) onCoderTemplateVersion. I will open it only on user demand.Source lines at each tag
DesiredInstances(provisionerdserver.go)enterprise/coderd/coderd.go)enterprise/coderd/coderd.go)isActive/handleInactiveTemplateVersion(preset_snapshot.go)prebuilds.sql)FeatureWorkspacePrebuilds(codersdk/deployment.go)Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:mediumfeat(api): add the dormant CoderWorkspacePool API types
feat(api): add the dormant CoderWorkspacePool API types
🤖 feat: create CoderWorkspacePool members under the three caps, with …
ThomasK33 commentedon Oct 14, 2026
Follow-ups for pool PR 12 (activation gate), collected from the final assessments of #359, #364, #379 and #380. The pool kind is dormant, so none of them blocks a merge today. Each one must be fixed or explicitly accepted before activation.
MaxConcurrentReconciles=1for the pool and member controllers explicitly, or make the check race-free.deleteFailedMembersdoes not storeobservedGeneration. The status shows a stale value until the final write of the reconcile.CoderControlPlanefrom the pool controller.parametersHashis unsalted.checkCreate404 handling (CoderWorkspacePoolMember checkCreate: trust only Coder's own 404 on the name lookup (before PR 12) #355), the CoderWorkspacePoolMember: recognize a template-wide last_used_at write in a one-member pool #349 rule, thebindExtendDeadlinebound, theOwnershipUnknowncontrol plane check, and the flapping agent timer.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:mediumThomasK33 commentedon Oct 15, 2026
More follow-ups for pool PR 12, from the final assessment of #387 and #388 (claims). The pool kind is dormant, so none of them blocks a merge today. Each one must be fixed or explicitly accepted before activation.
ownerName. Planned fix: keep the stored name.CoderWorkspaceClaimwatch that maps each claim to its pool. Without it, a new claim does not wake aReadypool.spec.poolName. After a bind, their controller reference points at the claim.OwnershipUnknown. Decide whether to accept a rename.coderworkspaceclaims/finalizers, because the bind setsblockOwnerDeletion.Bound.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:medium