Skip to content
SHC Docs

Flow: app upgrade / update

Step-by-step trace of shc update <stack> — changing the version, variables, integrations, resource limits, ingress, or providers of an already-installed stack. Source of truth: modules/app/install_async.go’s RunUpdateWithObserver.

The key architectural difference from install: update runs against an existing, possibly running, possibly externally accessed stack. Rollback is therefore not “delete the stack” but “restore everything to its pre-update state.” The whole flow is structured around a snapshot → apply → revert-on-failure pattern.


Before anything changes, take four snapshots of the current state:

  1. Directory files (snapshotDirectoryFiles) — every file under the deployment dir and app dir is captured into an in-memory map[string][]byte. Covers the rendered compose file, vars.yaml, copied app sources, templated sidecar files.
  2. Ingress (snapshotIngress) — the current host/port allocations for this stack in the ingress tables.
  3. Stack row attributes (snapshotStackAttrs) — a struct copy of every field on the DeploymentModel (version, providers, TLS mode, overrides, HA, scale, DiskBytes, …).
  4. HA / scale are applied to the in-memory object first so the rest of the flow sees them; the snapshot captured their old values.

If any of the new-state flags is set, the corresponding fields are updated on the in-memory DeploymentModel (but not committed yet).

If --disk changes the disk request upward, run quotaService.EnforceQuota for just the delta. CPU / memory deltas are computed but enforced later inside applyUpdate because they depend on re-rendering the compose to know the final numbers.

Runs the equivalent of install steps 6-16 but with the snapshot available for revert:

  1. If --version changed, re-resolve AppModel and re-copy app sources over the appDir. (The old sources are in fileSnapshot so a revert can restore them.)
  2. Re-merge variables with new integrations/unintegrations.
  3. Re-render compose (renderComposeFile) + sidecar YAML.
  4. Re-ensure Docker networks.
  5. If PullOnUpdate is set, docker pull the new images. Failure returns X200907PULFAL.
  6. pre_update hook.
  7. compose up -d — Docker sees the new compose and recreates changed services with rolling/stop-first semantics per the stack’s runtime.
  8. post_update hook.
  9. Update tenant usage counters with the final deltas.
  10. Commit.

If any step in applyUpdate returns an error — including context cancellation — the revert runs as a best-effort:

  1. revertUpdate is called with all four snapshots.
    • Restore ingress bookkeeping from the snapshot.
    • Restore every file in the deployment dir and app dir (restoreFilesSnapshot) — so the rendered compose and copied sources are byte-identical to before.
    • Re-run compose up -d with the restored compose file. This recreates any services Docker changed during step 7 back to their original images/config.
  2. restoreStackAttrs(stack, stackAttrSnapshot) restores every field on the in-memory DeploymentModel to its pre-update value.
  3. db.Save() — persist the restored attrs.
  4. Return the original update error.

If the revert itself fails (e.g. Docker is down), SHC does not pretend nothing happened:

  • restoreStackAttrs is still called so the DB at least matches the restored files on disk (or attempts to).
  • stack.Status = StackStatusError and stack.ErrorMessage is set to a string containing both errors, so the operator can see “update failed X, revert also failed Y.”
  • db.Save() on this error state. A commit failure here is logged at debug level and swallowed — the original update error takes priority.

The original updateErr is always the one that is returned.

StepEventMeaning
SuccessA200200STKUPDStack updated
Pull failedX200907PULFALdocker pull of new image failed
Revert attemptedA200003OPFAIL with operation="revert update"Revert sub-step log
Revert failedStack ends in StackStatus.ERROR with both errors in error_message
AspectInstallUpdate
Pre-stateNo stack existsStack exists and may be running
Rollback destination”stack never existed” (delete everything)Pre-update byte-identical state
How rollback is achievedReverse-order cleanup flagsSnapshot-and-restore
What “already-used” resources doNo pre-existing resources to conflictDelta quota; must handle port/host changes
Failure leaves(on --no-rollback) half-installed stackStack in StackStatus.ERROR with preserved original files