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.
Snapshot phase (lines 79-92)
Section titled “Snapshot phase (lines 79-92)”Before anything changes, take four snapshots of the current state:
- Directory files (
snapshotDirectoryFiles) — every file under the deployment dir and app dir is captured into an in-memorymap[string][]byte. Covers the rendered compose file,vars.yaml, copied app sources, templated sidecar files. - Ingress (
snapshotIngress) — the current host/port allocations for this stack in the ingress tables. - Stack row attributes (
snapshotStackAttrs) — a struct copy of every field on theDeploymentModel(version, providers, TLS mode, overrides, HA, scale, DiskBytes, …). - 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).
Quota preflight (lines 97-111)
Section titled “Quota preflight (lines 97-111)”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.
Apply phase (inside applyUpdate)
Section titled “Apply phase (inside applyUpdate)”Runs the equivalent of install steps 6-16 but with the snapshot available for revert:
- If
--versionchanged, re-resolveAppModeland re-copy app sources over the appDir. (The old sources are infileSnapshotso a revert can restore them.) - Re-merge variables with new integrations/unintegrations.
- Re-render compose (
renderComposeFile) + sidecar YAML. - Re-ensure Docker networks.
- If
PullOnUpdateis set,docker pullthe new images. Failure returnsX200907PULFAL. pre_updatehook.compose up -d— Docker sees the new compose and recreates changed services with rolling/stop-first semantics per the stack’s runtime.post_updatehook.- Update tenant usage counters with the final deltas.
- Commit.
Revert phase (lines 156-198)
Section titled “Revert phase (lines 156-198)”If any step in applyUpdate returns an error — including context
cancellation — the revert runs as a best-effort:
revertUpdateis 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 -dwith the restored compose file. This recreates any services Docker changed during step 7 back to their original images/config.
restoreStackAttrs(stack, stackAttrSnapshot)restores every field on the in-memoryDeploymentModelto its pre-update value.db.Save()— persist the restored attrs.- Return the original update error.
Double-failure (revert also fails)
Section titled “Double-failure (revert also fails)”If the revert itself fails (e.g. Docker is down), SHC does not pretend nothing happened:
restoreStackAttrsis still called so the DB at least matches the restored files on disk (or attempts to).stack.Status = StackStatusErrorandstack.ErrorMessageis 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.
Events
Section titled “Events”| Step | Event | Meaning |
|---|---|---|
| Success | A200200STKUPD | Stack updated |
| Pull failed | X200907PULFAL | docker pull of new image failed |
| Revert attempted | A200003OPFAIL with operation="revert update" | Revert sub-step log |
| Revert failed | Stack ends in StackStatus.ERROR with both errors in error_message | — |
Contrast with install
Section titled “Contrast with install”| Aspect | Install | Update |
|---|---|---|
| Pre-state | No stack exists | Stack exists and may be running |
| Rollback destination | ”stack never existed” (delete everything) | Pre-update byte-identical state |
| How rollback is achieved | Reverse-order cleanup flags | Snapshot-and-restore |
| What “already-used” resources do | No pre-existing resources to conflict | Delta quota; must handle port/host changes |
| Failure leaves | (on --no-rollback) half-installed stack | Stack in StackStatus.ERROR with preserved original files |
See also
Section titled “See also”install.md- Architecture: app module
- Lifecycle hooks —
pre_update/post_update