From c121a4190a44ac1c740f6bdb57dac6be1de25d58 Mon Sep 17 00:00:00 2001 From: matitos Date: Mon, 21 Sep 2026 12:12:28 +0000 Subject: [PATCH] Force-recreate containers on deploy and verify the served build The frontend container was serving the very first build (498-byte index.html) through every later deploy: `docker compose up -d` did not replace a config-identical service. Deploy now uses `build --pull` and `up --force-recreate`, the frontend image writes a .build-version marker, and the workflow fails the deploy if the served marker != the commit SHA. --- .gitea/workflows/deploy.yml | 28 +++++++++++++++++++++++----- frontend/Dockerfile | 5 +++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 6b461d4..c5b4352 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -178,14 +178,19 @@ jobs: done built=0 for i in 1 2 3 4 5; do - if docker compose build; then built=1; break; fi + # --pull refreshes base images; the build args carry the new commit + # so the cache is invalidated per release. + if docker compose build --pull; then built=1; break; fi echo "docker compose build failed (attempt $i); retrying in 30s" sleep 30 done [ "$built" = "1" ] || { echo "::error::compose build failed after retries"; exit 1; } - # Bring the new stack up and wait for healthchecks. No pre-`down`, so - # the old containers keep serving until the new ones are healthy. - if ! docker compose up -d --remove-orphans --wait --wait-timeout 300; then + # --force-recreate guarantees the containers are replaced with the + # freshly built images. Without it a config-identical service can be + # left running the previous image (stale UI after a deploy). + # No pre-`down`, so the old containers keep serving until the new ones + # are healthy. + if ! docker compose up -d --remove-orphans --force-recreate --wait --wait-timeout 300; then echo "::error::New stack is unhealthy; rolling back to previous images" docker compose down --remove-orphans || true for img in crypto_trading-backend crypto_trading-frontend; do @@ -193,7 +198,7 @@ jobs: docker tag "${img}:prev" "$img" fi done - docker compose up -d --remove-orphans --wait --wait-timeout 300 || true + docker compose up -d --remove-orphans --force-recreate --wait --wait-timeout 300 || true exit 1 fi @@ -202,6 +207,19 @@ jobs: docker compose ps docker compose logs --tail=80 + - name: Verify the deployed frontend matches this commit + run: | + # The container must serve the build stamped with this commit's SHA. + # Without this, a config-identical service could keep an old image and + # serve a stale UI even though the deploy "succeeded". + want="${CRYPTO_BUILD_VERSION:-unknown}" + got="$(docker compose exec -T frontend cat /usr/share/nginx/html/.build-version 2>/dev/null | tr -d '[:space:]' || true)" + echo "expected=${want} served=${got:-}" + if [ "$want" != "unknown" ] && [ "$got" != "$want" ]; then + echo "::error::frontend container is serving a stale build; failing deploy" + exit 1 + fi + - name: Health check (HTTP) run: | probe_backend() { diff --git a/frontend/Dockerfile b/frontend/Dockerfile index f9633d1..83404ec 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -19,4 +19,9 @@ ENV API_TOKEN= COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/templates/default.conf.template + +# Marker so the deploy can verify the served build matches the commit. +ARG BUILD_VERSION=unknown +RUN printf '%s' "${BUILD_VERSION}" > /usr/share/nginx/html/.build-version + EXPOSE 80