Added copa to the images build#247
Draft
bluvulture wants to merge 29 commits into
Draft
Conversation
Changed publishing process to publish only patched images
Contributor
There was a problem hiding this comment.
Pull request overview
Updates the Docker image release workflow to introduce an automated “scan + patch” stage (Trivy + Copa) before pushing images to registries, aiming to publish images after OS-level vulnerability remediation.
Changes:
- Defaults manual (
workflow_dispatch) runs to not publish images unless explicitly enabled. - Installs Trivy and Copa in the workflow, and starts a BuildKit daemon to support Copa patching.
- Changes the build/publish flow to build locally, scan + patch the image, then tag and push all tags manually.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+63
to
+71
| sudo apt-get install -y wget apt-transport-https gnupg lsb-release | ||
| wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null | ||
| echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee /etc/apt/sources.list.d/trivy.list | ||
| sudo apt-get update | ||
| sudo apt-get install -y trivy | ||
|
|
||
| # Install Copa | ||
| COPA_VERSION=$(curl -s https://api.github.com/repos/project-copacetic/copacetic/releases/latest | jq -r '.tag_name' | sed 's/^v//') | ||
| curl -fsSL -o copa.tar.gz "https://github.com/project-copacetic/copacetic/releases/download/v${COPA_VERSION}/copa_${COPA_VERSION}_linux_$(dpkg --print-architecture).tar.gz" |
| -p 127.0.0.1:8888:8888/tcp \ | ||
| --name buildkitd \ | ||
| --entrypoint buildkitd \ | ||
| moby/buildkit:latest \ |
Comment on lines
+69
to
+74
| # Install Copa | ||
| COPA_VERSION=$(curl -s https://api.github.com/repos/project-copacetic/copacetic/releases/latest | jq -r '.tag_name' | sed 's/^v//') | ||
| curl -fsSL -o copa.tar.gz "https://github.com/project-copacetic/copacetic/releases/download/v${COPA_VERSION}/copa_${COPA_VERSION}_linux_$(dpkg --print-architecture).tar.gz" | ||
| tar -xzf copa.tar.gz copa | ||
| sudo mv copa /usr/local/bin/copa | ||
| rm copa.tar.gz |
Comment on lines
+62
to
+71
| sudo apt-get update | ||
| sudo apt-get install -y wget apt-transport-https gnupg lsb-release | ||
| wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null | ||
| echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee /etc/apt/sources.list.d/trivy.list | ||
| sudo apt-get update | ||
| sudo apt-get install -y trivy | ||
|
|
||
| # Install Copa | ||
| COPA_VERSION=$(curl -s https://api.github.com/repos/project-copacetic/copacetic/releases/latest | jq -r '.tag_name' | sed 's/^v//') | ||
| curl -fsSL -o copa.tar.gz "https://github.com/project-copacetic/copacetic/releases/download/v${COPA_VERSION}/copa_${COPA_VERSION}_linux_$(dpkg --print-architecture).tar.gz" |
| sudo apt-get install -y trivy | ||
|
|
||
| # Install Copa | ||
| COPA_VERSION=$(curl -s https://api.github.com/repos/project-copacetic/copacetic/releases/latest | jq -r '.tag_name' | sed 's/^v//') |
Comment on lines
+62
to
+71
| sudo apt-get update | ||
| sudo apt-get install -y wget apt-transport-https gnupg lsb-release | ||
| wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null | ||
| echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee /etc/apt/sources.list.d/trivy.list | ||
| sudo apt-get update | ||
| sudo apt-get install -y trivy | ||
|
|
||
| # Install Copa | ||
| COPA_VERSION=$(curl -s https://api.github.com/repos/project-copacetic/copacetic/releases/latest | jq -r '.tag_name' | sed 's/^v//') | ||
| curl -fsSL -o copa.tar.gz "https://github.com/project-copacetic/copacetic/releases/download/v${COPA_VERSION}/copa_${COPA_VERSION}_linux_$(dpkg --print-architecture).tar.gz" |
Comment on lines
+71
to
+74
| curl -fsSL -o copa.tar.gz "https://github.com/project-copacetic/copacetic/releases/download/v${COPA_VERSION}/copa_${COPA_VERSION}_linux_$(dpkg --print-architecture).tar.gz" | ||
| tar -xzf copa.tar.gz copa | ||
| sudo mv copa /usr/local/bin/copa | ||
| rm copa.tar.gz |
v4.2 was released and the matrix was not updated yet. The 5.x entry with an empty version-override had no practical effect: every other rolling-branch entry pairs a plain-only row (hardened: false with a vN-dev version-override) with a stable release row. The plain 5.x with no override duplicated the dev row without providing the dev version, so jobs ran against the branch tip but tagged their images without a meaningful version string. Removing it keeps the pattern consistent. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines
+66
to
+67
| - name: Install Copa and Trivy | ||
| run: | |
Comment on lines
+92
to
+93
| - name: Start buildkit daemon | ||
| run: | |
| # Post-patch vulnerability gate, run on the hardened image only. | ||
| # The plain image is intentionally ungated. A failure here aborts the | ||
| # step before any push, so nothing ships for this variant. | ||
| FAIL_SEVERITY="$FAIL_ON_SEVERITY" |
Comment on lines
+182
to
+185
| # Build and load the plain image locally. The plain image is | ||
| # published as-is under the unsuffixed tags; it is never patched. | ||
| PLAIN_IMAGE="${IMAGE_NAME}:${TAG}" | ||
| docker build --load \ |
| For our stable release tags we publish each image in two flavors so you can choose your trade-off: | ||
|
|
||
| - **plain** (default, unsuffixed) – the image exactly as built from the Dockerfile, e.g. `php8.5-debug-v5`. | ||
| - **hardened** (`-hardened` suffix) – the same image with known OS-level CVEs patched in via [Copacetic (Copa)](https://github.com/project-copacetic/copacetic), e.g. `php8.5-debug-v5-hardened`. Every hardened image is scanned with [Trivy](https://github.com/aquasecurity/trivy) and must pass a `CRITICAL,HIGH` vulnerability gate before it's published. |
The single "Configure and build images" step was doing four unrelated things: building, patching, gating, and pushing. At ~200 lines it was hard to follow and made it impossible to skip Copa/Trivy work on jobs that do not need it. Split into three steps: "Build plain images" -- builds every image variant and writes per- variant state (plain_image.txt, plain_tags.txt, base_tag.txt, version.txt, tag.txt) to .docker-state/<variant>/ for later steps to consume. PHP_SUB_VERSION is now fetched once before the loop instead of once per variant. "Scan, patch, and gate hardened images" -- reads the state files, runs Copa and Trivy for hardened: true jobs only. Reduces post-patch Trivy from three invocations to two: one JSON scan (artifact + gate source) and one table scan (human display). The gate now reads the existing JSON via jq instead of running a third scan with --exit-code. Writes hardened_image.txt and hardened_tags.txt for the next step. "Tag, push, and aggregate" -- reads state for all variants, applies tags, pushes in parallel, aggregates logical tags, and cleans up per variant to keep disk usage bounded across the loop. Also fixes VERSION_MAJOR extraction: the old VERSION replacement substitution stripped every digit after a dot (e.g. v5.10 became v5.1 instead of v5). Using parameter expansion strips only the last component. The regex guarding the block is also tightened so the unescaped dot no longer matches any character.
Previously the "Install Copa and Trivy" and "Start buildkit daemon" steps ran on all 28 matrix jobs, even those with hardened: false that never call copa or trivy. Each wasted ~30-60s installing packages and starting a container that would never be used. Adding if: matrix.build.hardened to both steps skips them on plain-only jobs. The "Stop buildkit daemon" step is also guarded the same way since there is nothing to stop when buildkitd was never started.
The filename passed to grep contains dots, which grep treats as wildcards in regex mode. With grep -F (fixed string), the match is literal, so copa_0.14.1_linux_amd64.tar.gz cannot accidentally match copa_0X14Y1_linux_amd64.tar.gz or any other near-miss in the checksums file.
When PUSH is false (workflow_dispatch without publish: true) the aggregated_tags.txt file is never written. Without this option the upload-artifact action would error on the missing file even though the step is correctly gated to only run when publishing. Ignore silences that spurious failure without masking genuine upload problems.
Comment on lines
+183
to
+186
| env: | ||
| ARCH_TAG: ${{ contains(matrix.runner, 'arm') && 'arm64' || 'amd64' }} | ||
| FAIL_ON_SEVERITY: ${{ inputs.fail_on_severity || 'CRITICAL,HIGH' }} | ||
| TRIVY_DB_REPOSITORY: ${{ env.TRIVY_DB_REPOSITORY }} |
Comment on lines
+9
to
+10
| Today, for every matrix build marked `imagePatch: true` (the stable releases: | ||
| `v1.6`, `v2.3`, `v3.8`, `v4.1`, `v5.1`), the release workflow scans the freshly |
Comment on lines
+190
to
+193
| set -eux | ||
| mkdir -p trivy-reports | ||
|
|
||
| GHCR_TAG="ghcr.io/pimcore/pimcore:${TAG}" | ||
| GHCR_TAG_DETAILED="ghcr.io/pimcore/pimcore:${TAG_DETAILED}" | ||
| read -r -a imageVariants < .docker-state/variants.txt |
Comment on lines
+9
to
+10
| Today, for every matrix build marked `imagePatch: true` (the stable releases: | ||
| `v1.6`, `v2.3`, `v3.8`, `v4.1`, `v5.1`), the release workflow scans the freshly |
berfinyuksel
approved these changes
Jun 19, 2026
variants.txt was written space-separated with echo "${imageVariants[*]}"
and read back with read -r -a. Switch to one-variant-per-line with
printf '%s\n' to match the newline-separated format used by every other
state file, and use mapfile -t consistently at all three read sites.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 2>&1 redirect swallowed jq stderr, so a malformed or missing REPORT_JSON would cause jq to exit non-zero and silently skip the gate, potentially allowing a broken hardened image to ship. Keep stdout redirected to /dev/null (output is unused) but let stderr surface so any jq error aborts the step. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both images were removed explicitly before the loop, then removed again inside the loop because ALL_TAGS already contains them as its first entries (from plain_tags.txt and hardened_tags.txt). The redundant pre-loop rmi calls are harmless due to || true but confusing. Remove them and let the single loop cover everything. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| - { tag: 'v3.8', php: '8.3', distro: bookworm, version-override: "", latest-tag: true, hardened: true } | ||
| - { tag: '3.x', php: '8.2', distro: bookworm, version-override: "v3-dev", latest-tag: false, hardened: false } | ||
| - { tag: '3.x', php: '8.3', distro: bookworm, version-override: "v3-dev", latest-tag: false, hardened: false } | ||
| - { tag: 'v4.2', php: '8.4', distro: bookworm, version-override: "", latest-tag: true, hardened: true } |
Comment on lines
+9
to
+12
| Today, for every matrix build marked `imagePatch: true` (the stable releases: | ||
| `v1.6`, `v2.3`, `v3.8`, `v4.1`, `v5.1`), the release workflow scans the freshly | ||
| built image with Trivy, patches OS-level CVEs with Copa, and then **replaces the | ||
| plain image in place** under the same tags (`release.yml` lines ~191–219). The |
copa patch -t was passed "${BASE_TAG}-${VERSION}-hardened-${ARCH_TAG}"
(e.g. php8.5-v5-dev-hardened-amd64), so Copa produced an image named
php8.5-v5-dev-hardened-amd64:latest. The subsequent docker image inspect
"${HARDENED_IMAGE}" looked for pimcore/pimcore:php8.5-v5-dev-hardened-amd64
and always failed even on a successful patch.
Pass "${HARDENED_IMAGE}" directly — it already carries the full
${IMAGE_NAME}: prefix — so Copa tags its output consistently with what
the rest of the step expects.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The matrix was bumped from v4.1 to v4.2 in a prior commit; the design doc still referenced v4.1 in the stable-release list. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines
+84
to
+86
| # Verify checksum before extracting | ||
| EXPECTED_SHA=$(grep -F "copa_${COPA_VERSION}_linux_${COPA_ARCH}.tar.gz" copacetic_checksums.txt | awk '{print $1}') | ||
| ACTUAL_SHA=$(sha256sum copa.tar.gz | awk '{print $1}') |
Comment on lines
+82
to
+83
| curl -fsSL -o copa.tar.gz "https://github.com/project-copacetic/copacetic/releases/download/v${COPA_VERSION}/copa_${COPA_VERSION}_linux_${COPA_ARCH}.tar.gz" | ||
| curl -fsSL -o copacetic_checksums.txt "https://github.com/project-copacetic/copacetic/releases/download/v${COPA_VERSION}/copacetic_checksums.txt" |
Comment on lines
+321
to
+324
| # Clean up per variant to reclaim disk space before the next variant. | ||
| # ALL_TAGS already includes PLAIN_IMAGE and HARDENED_IMAGE as their | ||
| # first entries, so a single loop covers everything. | ||
| for additional_tag in "${ALL_TAGS[@]}"; do |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request updates the
release.ymlworkflow to add automated vulnerability scanning and patching to the Docker image build process, and refines how images are published and cleaned up. The main improvements are the integration of Trivy for vulnerability scanning, Copa for patching OS-level vulnerabilities, and enhanced image management during the build and publish steps.Security automation and image build improvements:
Vulnerability scanning and patching:
Build environment enhancements:
Publishing and workflow behavior:
Publishing control:
publishinput is changed fromtruetofalse, making image publishing opt-in for manual workflow dispatches.Image management and cleanup:
Other workflow refinements:
apt-get updatecall from the build step, as it is now handled during tool installation.