Flow: app uninstall
Step-by-step trace of shc uninstall <stack>. Source of truth:
subsystems/lifecycle/lifecycle.go’s
UninstallStack. For orchestrated multi-stack uninstall (e.g. during
clone/move or
tenant teardown), see
UninstallStacksOrdered in the same file, summarized at the end.
Uninstall is not transactional. Unlike install (which rolls back on failure) and update (which reverts from a snapshot), uninstall uses a best-effort forward pass: every step is individually guarded, a failure in one step is logged but never blocks later steps. This is by design — “user said delete this” should make progress even when parts of the system are already broken.
Inputs
Section titled “Inputs”func UninstallStack( service *StackService, name string, environment string, // default: "default" removeData bool, // if true, also remove the deployment dir operationID string,) error1. Lookup + configure (lines 84-88)
Section titled “1. Lookup + configure (lines 84-88)”GetStack(name, environment)⇒X404906STKNOFif absent.configure(stack)loads per-stack config overlays into theStackServiceso subsequent compose invocations see the right storage / vault / DNS providers.
2. Detect VApp (line 90)
Section titled “2. Detect VApp (line 90)”A deployment with a non-null ParentDeploymentID is a
VApp. This affects a few later
steps (no network removal, special unintegration path).
3. Resource pre-tally (lines 92-104)
Section titled “3. Resource pre-tally (lines 92-104)”For non-VApp stacks, read the rendered compose file (if any) and
extract CPUMillicores + MemoryBytes so the usage counter
decrement at step 14 uses the same numbers the install/update
increments used. Wrapped in error handling — if the compose file
is missing or malformed, proceed with zeros rather than refusing to
uninstall.
4. Load app definition (lines 106-109)
Section titled “4. Load app definition (lines 106-109)”LoadAppDefinition(appDir) — needed so later steps know which
hooks, unintegration jobs, and integrations the app declared. For
VApps, read from the parent stack’s appDir.
5. DNS record cleanup (lines 111-113)
Section titled “5. DNS record cleanup (lines 111-113)”For each host allocated to this stack (GetAllocatedHosts), ask
the stack’s DNS provider to remove the record. Failures inside
deleteDNSRecord are logged but don’t abort — you might be
deleting a stack whose DNS provider is offline.
6. pre_uninstall hook (line 115)
Section titled “6. pre_uninstall hook (line 115)”User-defined pre_uninstall hook. Unlike install, a hook failure
does not abort uninstall — runHookSafe logs and returns.
7. VApp unintegration (lines 117-132)
Section titled “7. VApp unintegration (lines 117-132)”If this is a VApp, run runVAppUnintegration which undoes the
changes the VApp made to its parent stack (e.g. the acme VApp
removes its Traefik config). Failures are logged as X200003OPFAIL;
a transaction rollback is attempted; the stack row is re-fetched so the
next step has a clean session.
8. Unregister jobs + delete schedules (lines 134-135)
Section titled “8. Unregister jobs + delete schedules (lines 134-135)”UnregisterStackJobs— remove this stack’s jobs from the runtime job registry.DeleteSchedules— remove every row inScheduleModelthat references this stack.
Neither is wrapped in error handling here; if they fail, the whole uninstall aborts (but the stack is still marked in the DB so a retry works).
9. compose down (lines 137-154)
Section titled “9. compose down (lines 137-154)”ComposeDown with:
RemoveVolumes=removeData— only wipe volumes when the user passed--remove-data.RemoveOrphans=true— always.
A failure is logged as X200905CMPDWN but does not abort — the
user asked to uninstall, and the next steps (network cleanup,
integration unwinding, DB row deletion) must still happen or the
stack becomes a zombie.
10. Deployment network removal (lines 156-171)
Section titled “10. Deployment network removal (lines 156-171)”For non-VApp stacks, call RemoveDeploymentNetwork to remove
the per-stack Docker network. The tenant-wide overlay is not removed
— other stacks in the tenant still need it.
Failure is logged as X200906NETCLN and does not abort.
11. post_uninstall hook (line 173)
Section titled “11. post_uninstall hook (line 173)”Same semantics as pre_uninstall — runHookSafe, failure logged.
12. Integration unwinding (lines 175-224)
Section titled “12. Integration unwinding (lines 175-224)”For every IntegrationModel row where this stack is either the
provider or the consumer:
- Run the declared unintegration jobs (
runUnintegrationJobs). - Delete the integration row.
Each step is guarded; a failure is logged as X200003OPFAIL with an
operation tag identifying which step, a best-effort transaction rollback
is attempted, and a needsStackRefresh flag is set so the next DB
read gets a fresh session. After the loop, one db.Save() persists
any successful deletes; failure here is also logged but not fatal.
13. Stack row delete (lines 231-239)
Section titled “13. Stack row delete (lines 231-239)”db.Delete(&stack). This is the one step where failure
aborts — if we can’t delete the row, return the error so the caller knows
the stack is still nominally installed.
14. Remove deployment dir (lines 241-247)
Section titled “14. Remove deployment dir (lines 241-247)”Only if removeData=true. Failure logged as X200049RMTREE;
the DB row is already gone so the stack doesn’t reappear.
15. Usage counter decrement (lines 249-258)
Section titled “15. Usage counter decrement (lines 249-258)”UpdateUsageOnStackDeletion drops the tenant’s CPU / memory /
disk usage counters by the numbers from step 3. VApps are counted
separately (isVApp=isVApp).
Ordered multi-stack uninstall
Section titled “Ordered multi-stack uninstall”UninstallStacksOrdered is used when multiple stacks need to come
down as a group (e.g. during clone/move, or tenant teardown). Key
properties:
- Topological ordering:
ComputeUninstallOrder(fromdaemon/clone_adapters.go, ce module — the reverse ofComputeInstallOrder) returns a list respecting plug/socket dependencies. Consumer stacks come down before the providers they depend on. - Per-stack best-effort: each
UninstallStackcall is wrapped in its own error handling. A failure is captured inUninstallResult(with the error message), logged asA400401STKUOF, and the loop continues to the next stack. - Summary event: at the end,
A200402STKUOCcarriesSuccessCountandTotalCount.
Events
Section titled “Events”| Step | Event | Meaning |
|---|---|---|
| Single uninstall success | A200400STKUNS | Stack uninstalled |
| Ordered: per-stack success | A200401STKUNO | Stack N of M uninstalled |
| Ordered: per-stack failure | A400401STKUOF | Stack N of M failed |
| Ordered: final summary | A200402STKUOC | N of M succeeded |
| Compose-down failure (non-fatal) | X200905CMPDWN | logged |
| Network-remove failure (non-fatal) | X200906NETCLN | logged |
| Rmtree failure (non-fatal) | X200049RMTREE | logged |
| Any guarded sub-step failure | X200003OPFAIL with operation tag | logged |
Contrast
Section titled “Contrast”| Install | Update | Uninstall | |
|---|---|---|---|
| Failure strategy | Rollback (reverse-order cleanup) | Revert (restore snapshot) | Best-effort forward |
| What happens on mid-flight failure | Stack rolled back, user sees original error | Stack reverted to pre-update state | Logs accumulate; uninstall proceeds |
| What can leave zombies | Nothing (unless --no-rollback) | Nothing (revert itself can fail → StackStatus.ERROR) | External DNS, external vault secrets, hook side-effects |