This repository has been archived on 2026-09-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
itsthevine/.gitea/workflows/build.yml
T
austinandClaude Opus 5 cf1254f901 CI: a pull request must not touch the :latest tag
Gating only the push was not enough. This runner builds on the host's Docker daemon — the same daemon the
live container runs on — so `docker build -t …:latest` IS a deployment: Watchtower compares the running
container's image against :latest, finds they differ, and recreates the container from the locally built
image.

That is not hypothetical. The PR builds for this branch deployed unmerged code to itsthevine.com several
times this evening, and each time Watchtower's next pull of the older registry :latest reverted it, so the
site flapped between the two. One of those deploys ran V4 against the production database, which is why
the wedding-row correction had to be a new migration rather than an edit to V4.

A PR now builds pr-<number>, which nothing watches; Trivy scans whatever was built; the push step is
unchanged and still only runs off a PR. Nothing was lost — 40 products, 6 categories and 2 enquiries are
all present, and the old image tolerates the newer schema (it warns that the schema is ahead of its
migrations and carries on).

The other five apps on the platform share this workflow and this daemon, so they have the same hole —
including for Renovate's PRs, which build before they automerge. Not touched here; each needs the same
three lines and its own green run.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-26 17:13:03 -05:00

93 lines
4.5 KiB
YAML

name: build-and-publish
on:
push:
branches: [main]
# Files that can't change the image — skip a pointless rebuild + redeploy for a docs/config commit.
paths-ignore: ["renovate.json", "**.md"]
# Runs on PRs too so the check below gates the MERGE, not just the image. Branch protection requires it.
pull_request:
branches: [main]
# Lets rebuild-all-apps.sh force a rebuild to roll out an urgent platform fix immediately.
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Tests gate the build AND the merge. They can't run inside `docker build` — Testcontainers needs a
# Docker daemon and a build has none — so Maven runs as a sibling container, mounting the workspace
# volume act_runner gave this job (matched on GITHUB_WORKSPACE, since it's mounted at the full repo
# path, not /workspace) and sharing the host network so published test ports resolve as localhost.
- name: Test
run: |
set -euo pipefail
VOL=$(docker inspect "$(hostname)" \
--format "{{range .Mounts}}{{if eq .Destination \"$GITHUB_WORKSPACE\"}}{{.Name}}{{end}}{{end}}")
if [ -z "$VOL" ]; then
echo "could not find this job's workspace volume — refusing to skip the tests" >&2
exit 1
fi
docker run --rm --network host \
-v "$VOL":/w \
-v /var/run/docker.sock:/var/run/docker.sock \
-e TESTCONTAINERS_RYUK_DISABLED=true \
-e MAVEN_USER="${{ secrets.REGISTRY_USER }}" \
-e MAVEN_TOKEN="${{ secrets.REGISTRY_TOKEN }}" \
-w /w \
maven:3.9-eclipse-temurin-25 \
mvn -B -ntp -s .gitea/ci-settings.xml -DskipFrontend=true verify
# Build + Trivy on every run (PRs included), so a broken Dockerfile or a new HIGH/CRITICAL CVE
# blocks the merge.
#
# A PULL REQUEST MUST NOT TOUCH THE :latest TAG. This runner builds on the host's Docker daemon —
# the same daemon the live container runs on — so retagging :latest IS a deployment: Watchtower
# compares the running container's image against :latest, finds they differ, and recreates the
# container from the locally built image. Gating only the push was not enough; on 2026-07-26 the PR
# builds for this branch deployed unmerged code to itsthevine.com several times over, and each time
# Watchtower's next pull of the (older) registry :latest reverted it. One of those deploys ran a
# migration against the production database.
#
# So a PR builds pr-<number> instead, which nothing watches. Trivy scans whatever was built, and the
# push step below still only runs off a PR.
- name: Choose the image tag
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "IMAGE_TAG=pr-${{ github.event.number }}" >> "$GITHUB_ENV"
else
echo "IMAGE_TAG=latest" >> "$GITHUB_ENV"
fi
- name: Build image
env:
DOCKER_BUILDKIT: "1"
MAVEN_USER: ${{ secrets.REGISTRY_USER }}
MAVEN_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
docker build \
--secret id=maven_user,env=MAVEN_USER \
--secret id=maven_token,env=MAVEN_TOKEN \
--build-arg GIT_SHA=${{ github.sha }} \
-t "git.thebennett.net/thevine/itsthevine:$IMAGE_TAG" \
-t "git.thebennett.net/thevine/itsthevine:${{ github.sha }}" .
- name: Scan image (Trivy)
run: |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --no-progress \
"git.thebennett.net/thevine/itsthevine:$IMAGE_TAG" || true
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:latest image --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed \
--pkg-types library --exit-code 1 --no-progress \
"git.thebennett.net/thevine/itsthevine:$IMAGE_TAG"
# Publish only on a real push to main (or manual dispatch) — never from a pull request.
- name: Push image
if: github.event_name != 'pull_request'
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.thebennett.net -u "${{ secrets.REGISTRY_USER }}" --password-stdin
docker push git.thebennett.net/thevine/itsthevine:latest
docker push git.thebennett.net/thevine/itsthevine:${{ github.sha }}