fix: run geo metrics incrementally #404
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Build the zvg-immo image and push it to GitHub Container Registry. | |
| # | |
| # Image is consumed by the production Ansible deployment | |
| # (roles/zvg-immo/ in the ansible repo) which `docker compose pull`s the tag | |
| # specified in its docker-compose.yml.j2 template (default: `:latest` on | |
| # default branch, or a pinned `:sha-<short>` for reproducible deploys). | |
| # | |
| # Multi-arch amd64+arm64, but unlike haex-claude-proxy's workflow this does | |
| # NOT use QEMU to cross-build arm64 on an amd64 runner — this app's build | |
| # (pnpm install + Vite/Nitro build) is heavy enough that QEMU emulation | |
| # regularly hangs for hours instead of just being slow. Each platform builds | |
| # natively on its own runner (ubuntu-24.04-arm is a free GitHub-hosted | |
| # runner for public repos) and a final job merges both into one multi-arch | |
| # manifest — the documented docker/build-push-action pattern for this. | |
| name: Build zvg-immo image | |
| on: | |
| push: | |
| branches: | |
| - main | |
| tags: | |
| - "v*" | |
| paths: | |
| - "Dockerfile" | |
| - "app.vue" | |
| - "nuxt.config.ts" | |
| - "tsconfig.json" | |
| - "package.json" | |
| - "pnpm-lock.yaml" | |
| - "assets/**" | |
| - "components/**" | |
| - "composables/**" | |
| - "i18n/**" | |
| - "layouts/**" | |
| - "lib/**" | |
| - "pages/**" | |
| - "public/**" | |
| - "scripts/**" | |
| - "server/**" | |
| - "types/**" | |
| - ".github/workflows/build-image.yml" | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| runner: ubuntu-latest | |
| slug: amd64 | |
| - platform: linux/arm64 | |
| runner: ubuntu-24.04-arm | |
| slug: arm64 | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # Standard OCI labels (org.opencontainers.image.revision/.created/...) | |
| # derived from git context — without this the built image carries no | |
| # labels at all, so there's no way to tell which commit is actually | |
| # deployed short of comparing timestamps. Only the labels output is | |
| # used here; tags are computed separately in the merge job below. | |
| - name: Extract image metadata | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/zvg-immo | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=tag | |
| type=sha,format=short | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Build and push by digest | |
| id: build | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| file: Dockerfile | |
| platforms: ${{ matrix.platform }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha,scope=${{ matrix.slug }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.slug }} | |
| outputs: type=image,name=ghcr.io/${{ github.repository_owner }}/zvg-immo,push-by-digest=true,name-canonical=true,push=true | |
| # Gate on the failure mode that took the site down: a broken SSR render | |
| # that returns 500 on "/" while /api/* stays healthy. Runs the freshly | |
| # built (native-arch) image and requires "/" to return 200. A failure | |
| # here fails the matrix job, which skips the `merge` job below — so a | |
| # broken build never gets the `:latest` tag that podman auto-update pulls. | |
| # ZVG_SKIP_BOOT_TASKS=1 keeps the container from crawling any upstream | |
| # portal during the test. | |
| - name: Smoke test — homepage SSR must render | |
| run: | | |
| set -euo pipefail | |
| img="ghcr.io/${{ github.repository_owner }}/zvg-immo@${{ steps.build.outputs.digest }}" | |
| docker pull "$img" | |
| docker run -d --name smoke -e ZVG_SKIP_BOOT_TASKS=1 -p 3000:3000 "$img" | |
| # Seed a minimal list cache so "/" renders against data instead of | |
| # blocking on a cold-cache live crawl of every upstream portal — that | |
| # would hang the test and needlessly hammer the portals from CI. | |
| docker exec smoke sh -c 'mkdir -p /app/.cache_zvg/list && printf "%s" "{\"platform\":\"smoke\",\"source\":\"smoke\",\"countries\":[\"de\"],\"regions\":[\"Bayern\"],\"fetchedAt\":\"2026-01-01T00:00:00.000Z\",\"totalReported\":0,\"auctions\":[]}" > /app/.cache_zvg/list/de-by.json' | |
| for i in $(seq 1 30); do | |
| if curl -fsS -o /dev/null http://localhost:3000/api/regions; then break; fi | |
| if [ "$i" = 30 ]; then echo "::error::server never became ready"; docker logs smoke; exit 1; fi | |
| sleep 2 | |
| done | |
| code=$(curl -s -o /tmp/home.html -w '%{http_code}' --max-time 20 http://localhost:3000/) | |
| echo "GET / -> $code" | |
| if [ "$code" != "200" ]; then | |
| echo "::error::homepage SSR did not return 200 (got $code)" | |
| head -c 800 /tmp/home.html || true | |
| docker logs smoke || true | |
| exit 1 | |
| fi | |
| docker rm -f smoke | |
| - name: Export digest | |
| run: | | |
| mkdir -p /tmp/digests | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "/tmp/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: digests-${{ matrix.slug }} | |
| path: /tmp/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| merge: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Download digests | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: /tmp/digests | |
| pattern: digests-* | |
| merge-multiple: true | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Compute image tags | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/zvg-immo | |
| tags: | | |
| type=ref,event=branch | |
| type=ref,event=tag | |
| type=sha,format=short | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| - name: Create manifest list and push | |
| working-directory: /tmp/digests | |
| run: | | |
| docker buildx imagetools create \ | |
| $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ | |
| $(printf 'ghcr.io/${{ github.repository_owner }}/zvg-immo@sha256:%s ' *) | |
| env: | |
| DOCKER_METADATA_OUTPUT_JSON: ${{ steps.meta.outputs.json }} | |
| - name: Inspect image | |
| run: | | |
| docker buildx imagetools inspect ghcr.io/${{ github.repository_owner }}/zvg-immo:${{ steps.meta.outputs.version }} |