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- 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 }}