Skip to main content

In-Place Upgrades

When the template a stack was launched from has been updated to a new version, you can adopt those changes without tearing the stack down. The upgrade engine diffs the running stack's snapshotted descriptor against the latest template version, classifies each resource's delta, and applies only the changes that are safe to make non-destructively.

How the upgrade engine works

The engine follows a three-step process:

  1. Diff. It compares the snapshotted descriptor stored on your stack at launch time against the current template version. Each resource in the descriptor receives an independent diff.
  2. Classify. Each resource's diff is classified as unchanged, in_place, or blocked according to the rules below. If any resource is blocked, the entire upgrade is blocked.
  3. Advance. For unblocked upgrades, the engine advances each resource with an in_place classification through the appropriate platform operation (for example, a compute plan resize or an app redeployment). Resources classified as unchanged are not touched.

How the engine classifies changes

Every resource in the diff receives one of three classifications:

ClassificationMeaning
unchangedThe resource's spec is identical in both versions. The engine does not touch it.
in_placeA safe, non-destructive change the engine can apply to the running resource.
blockedA change that would recreate, shrink, or fundamentally alter a stateful resource. A fresh stack is required.

If any resource is blocked, the entire upgrade is blocked. The engine will not apply the in_place changes either. You must launch a new stack to adopt those changes.

What can be upgraded in place

The set of spec keys the engine treats as in_place differs by resource kind:

KindIn-place keysAction label
appimage_ref, env, auth, domainredeploy_app
appplan_namescale_app
databaseplan_name (compute resize)scale_service
inference(none; any change is blocked in the current version)n/a
files(none; any change is blocked in the current version)n/a

Changes to any other spec key on any kind are blocked. The intent is conservative: the engine cannot recreate or shrink a stateful resource (a database or a bucket) in place, so those paths are entirely refused.

What is always blocked

The following changes are always blocked, regardless of kind:

  • Adding a resource. A new resource in the target template has no running counterpart. Adding it during an upgrade is not supported; launch a fresh stack.
  • Removing a resource. Removing an existing resource would leave no path to tear it down gracefully within the upgrade. Launch a fresh stack.
  • Changing a resource's kind. For example, changing a database resource to a files resource.
  • Changing a database engine, version, storage size, or storage tier. Any key on a database resource other than plan_name is blocked.
  • Any change to an inference or files resource. Support for in-place inference key reminting and files quota adjustment is planned but not yet available.

Step 1: Preview the upgrade

Always preview before applying. The preview computes and returns the classified plan without changing any state:

curl -X POST https://api.foundrydb.com/stacks/$STACK_ID/upgrade/preview \
-H "Authorization: Bearer $FOUNDRYDB_TOKEN"

Example response for an unblocked upgrade:

{
"from_version": "1.0.0",
"to_version": "1.1.0",
"changes": [
{
"symbolic_name": "db",
"kind": "database",
"change": "unchanged"
},
{
"symbolic_name": "app",
"kind": "app",
"change": "in_place",
"action": "redeploy_app",
"target_spec": {
"attachment_kind": "open-webui",
"image_ref": "ghcr.io/open-webui/open-webui:v0.10.2"
}
}
],
"new_monthly_cost": 87.00,
"current_monthly_cost": 87.00,
"cost_delta": 0.0,
"blocked": false,
"blocked_reasons": []
}

Example response for a blocked upgrade:

{
"from_version": "1.0.0",
"to_version": "2.0.0",
"changes": [
{
"symbolic_name": "db",
"kind": "database",
"change": "blocked",
"reason": "changing \"version\" on a database resource requires a fresh stack (it could recreate or resize stateful data)"
},
{
"symbolic_name": "app",
"kind": "app",
"change": "in_place",
"action": "redeploy_app"
}
],
"blocked": true,
"blocked_reasons": [
"db: changing \"version\" on a database resource requires a fresh stack (it could recreate or resize stateful data)"
]
}

When blocked is true, no upgrade path is available. Launch a new stack from the updated template and migrate your data manually.

Step 2: Apply the upgrade

Once you have reviewed the plan and accepted the new monthly cost:

curl -X POST https://api.foundrydb.com/stacks/$STACK_ID/upgrade \
-H "Authorization: Bearer $FOUNDRYDB_TOKEN" \
-H "Content-Type: application/json" \
-d '{"accepted_monthly_cost": 87.00}'

Response semantics:

HTTP statusMeaning
202 AcceptedUpgrade accepted. The reconciler is applying changes. Response body is a StackMigration record.
200 OK with {"status":"up_to_date"}No changes to apply. The stack is already on the latest template version.
409 ConflictAn upgrade is already in progress, or the accepted cost drifted from the current estimate by more than $0.01. Re-preview and resubmit.
422 Unprocessable EntityThe plan is blocked. Launch a fresh stack to adopt these changes.

Monitoring the upgrade

While the upgrade is in progress, the stack's status moves to Upgrading. The StackMigration record returned by the 202 response tracks the upgrade's lifecycle:

Migration statusMeaning
AcceptedUpgrade accepted, reconciler not yet started.
ApplyingThe reconciler is applying in-place changes.
CompletedAll changes applied. The stack is back to Running.
FailedA single step failed (see below).

Retrieve the stack to see the current status:

curl https://api.foundrydb.com/stacks/$STACK_ID \
-H "Authorization: Bearer $FOUNDRYDB_TOKEN"

Failure behaviour

A failed upgrade is scoped to the in-flight step only. The engine rolls back that one change and returns the stack to Running. It does not trigger a full stack teardown, does not touch other running resources, and does not delete any data.

The migration record's status moves to Failed and status_detail describes what went wrong (for example, "app redeployment failed: image pull error"). After reviewing the migration record, you can re-preview and reapply once you have resolved the issue, or leave the stack on its current template version and apply the upgrade later.

Because the stack returns to Running after a failed step, your application remains available throughout. The only observable effect is that the attempted in-place change (for example, the new image version) did not take effect.

Cost gate

The upgrade endpoint enforces the same cost gate as the launch endpoint. accepted_monthly_cost must be within $0.01 of the estimate returned by the upgrade preview. If the template's resources have changed price between preview and apply (because, for example, a plan tier changed list price), you receive 409 Conflict and must re-preview with the updated estimate.

This design ensures the upgrade does not silently increase your bill. Re-preview as many times as needed until the estimate is stable; only then submit the apply request.

Deciding between upgrade and a fresh stack

SituationRecommendation
Only app image updates or compute plan resizesUse in-place upgrade
Template version adds or removes a resourceLaunch a fresh stack
Database engine, version, storage size, or storage tier changedLaunch a fresh stack
Upgrade preview returns blocked: trueLaunch a fresh stack
You want a clean environment before switching trafficLaunch a fresh stack
You need zero data migrationUse in-place upgrade

When launching a fresh stack to adopt blocked changes, provision the new stack first, migrate your data, then delete the old stack. The old and new stacks run independently; you control the cutover timing.