name: Build Docker images (PR test) # Reusable workflow called from build.yml on PRs to verify the three # embedded Dockerfiles (default, ultra-lite, fat) still build cleanly, # optionally against a freshly-built base image when the PR touches the # base Dockerfile. on: workflow_call: inputs: docker-base-changed: description: "Whether the docker base image changed (forwarded from files-changed)." required: false type: string default: "false" dockerfiles-changed: description: "Whether any Dockerfile changed (forwarded from files-changed). Gates the slow arm64 build leg." required: false type: string default: "false" permissions: contents: read jobs: # TODO: extract a pre-matrix `prepare` job that runs once and produces # shared artifacts for the three matrix entries below to consume: # 1. `task backend:build` — currently runs 3× in parallel with # identical env (DISABLE_ADDITIONAL_FEATURES=true, # STIRLING_PDF_DESKTOP_UI=false). Build once, upload the JAR as an # artifact, matrix entries download. # 2. The base-image `docker build` (gated on docker-base-changed) — # currently runs 3× in parallel against the same Dockerfile and # context. Build once, `docker save` to an artifact, matrix entries # `docker load` before the embedded build. # Saves ~2 full backend builds + 2 base-image builds per PR that touches # docker. May also be reusable from backend-build.yml's jdk-25 + # spring-security=true matrix entry if `task backend:build` and # `task backend:build:ci` produce equivalent JARs (verify before wiring). test-build-docker-images: runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - docker-rev: docker/embedded/Dockerfile artifact-suffix: Dockerfile cache-scope: stirling-pdf-latest - docker-rev: docker/embedded/Dockerfile.ultra-lite artifact-suffix: Dockerfile.ultra-lite cache-scope: stirling-pdf-ultra-lite - docker-rev: docker/embedded/Dockerfile.fat artifact-suffix: Dockerfile.fat cache-scope: stirling-pdf-fat steps: - name: Harden Runner uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - name: Checkout Repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Login to GitHub Container Registry uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} - name: Convert repository owner to lowercase id: repoowner run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT - name: Free disk space on runner run: | echo "Disk space before cleanup:" && df -h sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/boost docker system prune -af || true echo "Disk space after cleanup:" && df -h - name: Set up JDK 25 uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: java-version: "25" distribution: "temurin" - name: Cache Gradle User Home uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }} restore-keys: | gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25- gradle-${{ runner.os }}-${{ runner.arch }}- - name: Install Task uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0 - name: Build application run: task backend:build env: MAVEN_USER: ${{ secrets.MAVEN_USER }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }} DISABLE_ADDITIONAL_FEATURES: true STIRLING_PDF_DESKTOP_UI: false - name: Set up QEMU uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 - name: Build base image locally (PR base change only) if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true' run: | docker build -t stirling-pdf-base:pr-test -f docker/base/Dockerfile docker/base - name: Set base image and platform for this build id: build-params # Pass workflow inputs through env vars rather than expanding `${{ }}` # directly into the shell — defense-in-depth against template injection # if any upstream provider of these values ever becomes less trusted. # GITHUB_EVENT_NAME is already provided by the runner. env: DOCKER_BASE_CHANGED: ${{ inputs.docker-base-changed }} DOCKERFILES_CHANGED: ${{ inputs.dockerfiles-changed }} run: | if [ "$GITHUB_EVENT_NAME" = "pull_request" ] && [ "$DOCKER_BASE_CHANGED" = "true" ]; then # Base Dockerfile changed: build against the locally-built base, # which only exists for amd64. echo "base_image=stirling-pdf-base:pr-test" >> "$GITHUB_OUTPUT" echo "platforms=linux/amd64" >> "$GITHUB_OUTPUT" elif [ "$DOCKERFILES_CHANGED" = "true" ]; then # A Dockerfile changed: also verify the arm64 build (slow QEMU leg). echo "base_image=stirlingtools/stirling-pdf-base:latest" >> "$GITHUB_OUTPUT" echo "platforms=linux/amd64,linux/arm64/v8" >> "$GITHUB_OUTPUT" else # No Dockerfile change: amd64 only. arm64 is exercised on the base # image publish and on release, not on every code PR. echo "base_image=stirlingtools/stirling-pdf-base:latest" >> "$GITHUB_OUTPUT" echo "platforms=linux/amd64" >> "$GITHUB_OUTPUT" fi # Base-changed PRs build the embedded image with the local docker driver # so the locally-built stirling-pdf-base:pr-test (in the daemon image # store) resolves. A buildx container builder cannot see it and would try # to pull it from a registry, which fails. Single-platform, no gha cache. - name: Build ${{ matrix.docker-rev }} against local base (PR base change) if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true' run: | DOCKER_BUILDKIT=1 docker build \ --build-arg BASE_IMAGE=${{ steps.build-params.outputs.base_image }} \ --file ./${{ matrix.docker-rev }} \ --tag stirling-pdf-embedded:pr-test \ . # PRs that did NOT change the base use the buildx container builder # (multi-platform + gha cache) against the published base image. - name: Build ${{ matrix.docker-rev }} if: inputs.docker-base-changed != 'true' uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: builder: ${{ steps.buildx.outputs.name }} context: . file: ./${{ matrix.docker-rev }} push: false cache-from: type=gha,scope=${{ matrix.cache-scope }} cache-to: type=gha,mode=max,scope=${{ matrix.cache-scope }} platforms: ${{ steps.build-params.outputs.platforms }} build-args: | BASE_IMAGE=${{ steps.build-params.outputs.base_image }} provenance: true sbom: true - name: Upload Reports if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: reports-docker-${{ matrix.artifact-suffix }} path: | build/reports/tests/ build/test-results/ build/reports/problems/ retention-days: 3 if-no-files-found: warn test-build-unoserver-image: runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 with: egress-policy: audit - name: Checkout Repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up QEMU uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 - name: Build docker/unoserver/Dockerfile uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: builder: ${{ steps.buildx.outputs.name }} context: . file: ./docker/unoserver/Dockerfile push: false load: true cache-from: type=gha,scope=stirling-unoserver cache-to: type=gha,mode=max,scope=stirling-unoserver platforms: linux/amd64 tags: stirling-unoserver:pr-test provenance: false sbom: false - name: Smoke test the built image run: | set -eu docker run -d --name unoserver-smoke \ -e UNOSERVER_RECYCLE_INTERVAL_SECONDS=0 \ stirling-unoserver:pr-test deadline=$((SECONDS + 60)) while [ $SECONDS -lt $deadline ]; do status=$(docker inspect -f '{{.State.Health.Status}}' unoserver-smoke 2>/dev/null || echo "starting") if [ "$status" = "healthy" ]; then echo "unoserver became healthy" docker logs unoserver-smoke | tail -30 docker rm -f unoserver-smoke exit 0 fi sleep 3 done echo "unoserver did not become healthy in time" docker logs unoserver-smoke || true docker rm -f unoserver-smoke || true exit 1