From eee6ce5581c72db5df0c4c9428575f0ca53ab052 Mon Sep 17 00:00:00 2001 From: CallMeTechie <34693633+CallMeTechie@users.noreply.github.com> Date: Wed, 24 Jun 2026 07:42:23 +0200 Subject: [PATCH] chore(deploy): prune dangling images after a successful auto-update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each :latest pull leaves the previous gatecontrol image dangling. update.sh never removed them, so the Docker host filled up over time — incident 2026-06-23: 187 images, root fs at 100%, 'docker pull' failed with 'no space left on device', and the */5 auto-update silently stalled on the old version. Add a prune_images() helper (docker image prune -f — dangling only, tagged/in-use images are never touched) and call it after a successful recreate in both the manual and auto paths. Applied to the served template and the repo copy; the live /opt/gatecontrol/update.sh was synced to this version too (it had also drifted — it was missing the ensure_guacd sidecar step). --- src/services/systemSetup/templates/update.sh | 12 ++++++++++-- update.sh | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/services/systemSetup/templates/update.sh b/src/services/systemSetup/templates/update.sh index 4ddc7441..f760199c 100755 --- a/src/services/systemSetup/templates/update.sh +++ b/src/services/systemSetup/templates/update.sh @@ -97,6 +97,14 @@ ensure_guacd() { ) } +prune_images() { + # Reclaim disk after a successful update: each :latest pull leaves the previous + # image dangling. Without pruning the host fills up over time (incident + # 2026-06-23: 187 images → root fs 100% → pulls failed → auto-update stalled). + # -f removes ONLY dangling (untagged) images, so tagged/in-use images are safe. + docker image prune -f >>"$LOG" 2>&1 || true +} + needs_update() { # echoes "yes" if running image != :latest local latest running latest="$(docker image inspect "$IMAGE" --format '{{.Id}}' 2>/dev/null || true)" @@ -116,7 +124,7 @@ if [ "$MODE" = "manual" ]; then err) log "could not resolve :latest digest"; write_state failed manual; exit 1 ;; esac log "manual trigger — recreating" - if recreate; then ensure_guacd || true; write_state updated manual; else log "recreate/health failed"; write_state failed manual; exit 1; fi + if recreate; then ensure_guacd || true; prune_images || true; write_state updated manual; else log "recreate/health failed"; write_state failed manual; exit 1; fi exit 0 fi @@ -128,4 +136,4 @@ case "$(needs_update)" in err) log "could not resolve :latest digest"; write_state failed auto; exit 1 ;; esac log "auto: update needed — recreating" -if recreate; then ensure_guacd || true; write_state updated auto; else log "recreate/health failed"; write_state failed auto; exit 1; fi +if recreate; then ensure_guacd || true; prune_images || true; write_state updated auto; else log "recreate/health failed"; write_state failed auto; exit 1; fi diff --git a/update.sh b/update.sh index 4ddc7441..f760199c 100755 --- a/update.sh +++ b/update.sh @@ -97,6 +97,14 @@ ensure_guacd() { ) } +prune_images() { + # Reclaim disk after a successful update: each :latest pull leaves the previous + # image dangling. Without pruning the host fills up over time (incident + # 2026-06-23: 187 images → root fs 100% → pulls failed → auto-update stalled). + # -f removes ONLY dangling (untagged) images, so tagged/in-use images are safe. + docker image prune -f >>"$LOG" 2>&1 || true +} + needs_update() { # echoes "yes" if running image != :latest local latest running latest="$(docker image inspect "$IMAGE" --format '{{.Id}}' 2>/dev/null || true)" @@ -116,7 +124,7 @@ if [ "$MODE" = "manual" ]; then err) log "could not resolve :latest digest"; write_state failed manual; exit 1 ;; esac log "manual trigger — recreating" - if recreate; then ensure_guacd || true; write_state updated manual; else log "recreate/health failed"; write_state failed manual; exit 1; fi + if recreate; then ensure_guacd || true; prune_images || true; write_state updated manual; else log "recreate/health failed"; write_state failed manual; exit 1; fi exit 0 fi @@ -128,4 +136,4 @@ case "$(needs_update)" in err) log "could not resolve :latest digest"; write_state failed auto; exit 1 ;; esac log "auto: update needed — recreating" -if recreate; then ensure_guacd || true; write_state updated auto; else log "recreate/health failed"; write_state failed auto; exit 1; fi +if recreate; then ensure_guacd || true; prune_images || true; write_state updated auto; else log "recreate/health failed"; write_state failed auto; exit 1; fi