Fix health endpoint, align with the documented manual runbook

The health router is mounted at prefix="/api" in domaindingo's main.py, so
/health 404s. Checking it would have failed every deploy and rolled back a
perfectly good image.

Also adopts three conventions from the vault's DomainDingo Development note:
recreate a single named service (--no-deps --force-recreate), validate with
compose config --quiet before touching the running container, and write the
rollback record in the documented format and location.
This commit is contained in:
fisher
2026-08-23 08:53:26 +00:00
parent 62c62baf05
commit 952de1ea8e
3 changed files with 89 additions and 39 deletions
+61 -18
View File
@@ -236,14 +236,19 @@ readonly DEPLOY_IMAGE
# --------------------------------------------- remember what is running now ---
PREVIOUS_IMAGE="$(
container_image="$(docker inspect --format '{{.Image}}' "$CD_CONTAINER" 2>/dev/null || true)"
[[ -n "$container_image" ]] && image_digest_ref "$container_image" || true
)"
readonly PREVIOUS_IMAGE
RunningImageId="$(docker inspect --format '{{.Image}}' "${CD_CONTAINER}" 2>/dev/null || true)"
PREVIOUS_IMAGE=""
PREVIOUS_REVISION=""
if [[ -n "${RunningImageId}" ]]; then
PREVIOUS_IMAGE="$(image_digest_ref "${RunningImageId}")"
PREVIOUS_REVISION="$(docker image inspect \
--format '{{index .Config.Labels "org.opencontainers.image.revision"}}' \
"${RunningImageId}" 2>/dev/null || true)"
fi
readonly PREVIOUS_IMAGE PREVIOUS_REVISION
if [[ -n "$PREVIOUS_IMAGE" ]]; then
log "current ${PREVIOUS_IMAGE}"
if [[ -n "${PREVIOUS_IMAGE}" ]]; then
log "current ${PREVIOUS_IMAGE} (revision ${PREVIOUS_REVISION:0:12})"
else
log "current (nothing running -- first deploy, or container absent)"
fi
@@ -251,15 +256,44 @@ fi
# ----------------------------------------------------------------- deploy ---
compose_up() {
local image="$1"
# `up -d` recreates only what actually changed. `down` is deliberately not
# used here: it causes avoidable downtime, and `down -v` would destroy the
# data volumes these stacks depend on.
( cd "$CD_STACK_DIR" \
&& env "${CD_IMAGE_ENV_VAR}=${image}" \
"${CD_PULL_POLICY_ENV_VAR}=missing" \
timeout "$CD_COMPOSE_TIMEOUT_SECONDS" \
docker compose -f "$CD_COMPOSE_FILE" -p "$CD_COMPOSE_PROJECT" up -d --no-build )
local Image="$1"
# Matches the manual runbook in the vault's "DomainDingo Development" note:
# recreate exactly one service, leave its dependencies alone, and never run
# `docker compose down` -- that causes avoidable downtime, and `down -v`
# would destroy the bind-mounted data these stacks depend on.
#
# `--pull always` is deliberately absent: the image is already pinned to a
# digest and pulled, so there is nothing left to resolve.
( cd "${CD_STACK_DIR}" \
&& env "${CD_IMAGE_ENV_VAR}=${Image}" \
"${CD_PULL_POLICY_ENV_VAR}=never" \
timeout "${CD_COMPOSE_TIMEOUT_SECONDS}" \
docker compose -f "${CD_COMPOSE_FILE}" -p "${CD_COMPOSE_PROJECT}" \
up -d --no-build --no-deps --force-recreate "${CD_COMPOSE_SERVICE}" )
}
compose_validate() {
local Image="$1"
( cd "${CD_STACK_DIR}" \
&& env "${CD_IMAGE_ENV_VAR}=${Image}" \
"${CD_PULL_POLICY_ENV_VAR}=never" \
docker compose -f "${CD_COMPOSE_FILE}" -p "${CD_COMPOSE_PROJECT}" \
config --quiet )
}
# Write the rollback record where the manual runbook looks for it, in the same
# format, so an operator following that note can recover from an automated
# deploy without knowing this tool exists.
write_rollback_record() {
local Image="$1" Revision="$2" Dir="${CD_STACK_DIR}/${CD_ROLLBACK_RECORD_DIR}"
local Record="${Dir}/${CD_COMPOSE_SERVICE}-rollback-$(date -u +%Y%m%d-%H%M%S).env"
[[ -d "${Dir}" ]] || { warn "no rollback record directory at ${Dir}"; return 0; }
( umask 077
printf 'DD_ROLLBACK_IMAGE=%s\nDD_ROLLBACK_REVISION=%s\nDD_ROLLBACK_CAPTURED_AT=%s\n' \
"${Image}" "${Revision}" "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" > "${Record}" )
log "rollback recorded at ${Record}"
}
check_health() {
@@ -289,14 +323,23 @@ sys.exit(0 if str(data.get(key, "")).lower() == want.lower() else 1)
}
if [[ $DRY_RUN -eq 1 ]]; then
log "dry run: would run docker compose up -d with ${CD_IMAGE_ENV_VAR}=${DEPLOY_IMAGE}"
log "dry run: would recreate service ${CD_COMPOSE_SERVICE} with ${CD_IMAGE_ENV_VAR}=${DEPLOY_IMAGE}"
log "dry run: would health-check ${CD_HEALTH_URL}"
log "dry run complete -- no changes made"
exit 0
fi
# Catch a broken compose file or a bad interpolation before the running
# container is touched.
if ! compose_validate "${DEPLOY_IMAGE}"; then
notify fail "compose config is invalid for ${SHA:0:12} -- nothing was changed"
die "docker compose config failed; the running stack was not touched"
fi
[[ -n "${PREVIOUS_IMAGE}" ]] && write_rollback_record "${PREVIOUS_IMAGE}" "${PREVIOUS_REVISION}"
log "deploying ${DEPLOY_IMAGE}"
if ! compose_up "$DEPLOY_IMAGE"; then
if ! compose_up "${DEPLOY_IMAGE}"; then
notify fail "docker compose up failed for ${SHA:0:12} -- stack left as-is"
die "docker compose up failed"
fi