From db647bcd0ad76d2e544c7a14df0cc9e4f024dc0a Mon Sep 17 00:00:00 2001 From: kua-deploy-split Date: Thu, 21 May 2026 18:35:37 -0400 Subject: [PATCH] fix(self-recreate): capture built image SHA via docker images tag, normalize SHA comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prior expected_image_sha was captured via docker compose images, which returns the image of the existing (pre-recreate) container — not the freshly-built one. Switch to docker images ${project}-${service}:latest --quiet --no-trunc which returns the post-build image SHA. Also normalize sha256: prefix in completeSelfRecreate comparison so docker compose images output (sha256-prefixed) and docker inspect output (also sha256-prefixed) match cleanly. --- server.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 52e4c24..74dd67d 100644 --- a/server.js +++ b/server.js @@ -708,7 +708,11 @@ ${detail}`); const selfRecreate = appName === SELF_APP_NAME && isLocal(server) && stateless.includes(SELF_APP_NAME); if (selfRecreate) { // Capture the freshly-built image SHA for post-restart verification. - const builtSha = (await run(`docker compose -p ${composeProject} -f ${deployDir}/docker-compose.yml images --quiet ${SELF_APP_NAME} 2>/dev/null | head -1`)).stdout.trim() || null; + // `docker compose images` returns the image used by the EXISTING container + // (still the OLD one before recreate). For the just-built image, query the + // image tag that compose builds into: ${project}-${service}:latest. + const builtImageTag = `${composeProject}-${SELF_APP_NAME}:latest`; + const builtSha = (await run(`docker images ${builtImageTag} --quiet --no-trunc | head -1`)).stdout.trim() || null; steps[steps.length - 1] = { step: 'deploy', status: 'running', @@ -1443,7 +1447,9 @@ async function completeSelfRecreate() { } } catch { /* docker unreachable — leave progress in pending; next startup retries */ } - const imageMatches = expectedSha && runningSha && expectedSha.endsWith(runningSha.replace(/^sha256:/, '')); + // Normalize: strip sha256: prefix from both sides for tolerant comparison. + const normSha = s => (s || '').replace(/^sha256:/, '').trim(); + const imageMatches = expectedSha && runningSha && normSha(expectedSha) === normSha(runningSha); const freshlyStarted = startedAtStr && recreateStartedAt && new Date(startedAtStr) >= recreateStartedAt; const ok = !!(imageMatches && freshlyStarted && state === 'running');