dotCMS / dotCMS/core

task(docker): Implement immutable digest pinning and lifecycle management for java-base image

Open
#34,782 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

dotCMS : Containerization stale
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Description

The dotcms/java-base image is currently tagged only by Java version (e.g., dotcms/java-base:21.0.8-ms). This tag is mutable — every rebuild overwrites it regardless of what changed. Any consumer referencing this tag silently pulls a different image after a rebuild, with no record in git of what changed or when.

This issue implements a complete lifecycle management strategy: content-addressed immutable tags, image manifest digest pinning in the runtime Dockerfile, a scheduled weekly rebuild for security freshness, a validation gate before any tag is published, and an automated PR flow to update the pinned reference.

This is a follow-on to #34781, which restructures the Dockerfiles. Digest pinning completes the reproducibility story.


The Problem

Mutable tags provide no reproducibility guarantee

Dated tags (21.0.8-ms-20260302) look immutable but are not — a retry or hotfix rebuild on the same day overwrites them. The same tag string can refer to different image content at different times.

The runtime Dockerfile currently takes SDKMAN_JAVA_VERSION and constructs FROM dotcms/java-base:${SDKMAN_JAVA_VERSION}. With no digest pin:

  • Two builds from the same commit may use different base images
  • A failing build cannot be reproduced if the base image has been rebuilt since
  • There is no git-traceable record of which base image a given runtime build consumed
  • A breaking upstream change (apt package, pgdg script, SDKMAN availability) silently becomes the floating tag and breaks all downstream builds
Upstream sources that can change without a Dockerfile edit
Source Risk
FROM ubuntu:24.04 Canonical pushes new layer to the tag; content changes silently
apt upgrade -y Picks up all package updates at build time; same Dockerfile can produce different images
wget -O - https://get.sdkman.io | bash SDKMAN has retired older Java builds from its archive; script behaviour can change
pgdg setup script Fetched from network; repo key or script could change

Solution

1. Two-tier immutable tagging

Every successful java-base build pushes two immutable tags plus one floating alias:

Tag Type Purpose
21.0.8-ms-20260302 Immutable dated Human-readable snapshot; the tag recorded in .approved-ref
21.0.8-ms-{dockerfile-hash} Immutable content-addressed Correlates tag back to exact Dockerfile; derived from sha256sum docker/java-base/Dockerfile (first 12 chars)
21.0.8-ms Mutable floating Convenience alias for local development only; never used in Dockerfiles or CI pins
2. Image manifest digest pinning via .approved-ref

The truly immutable reference is the image manifest digest — a SHA256 of the actual image layers that cannot be overwritten regardless of what happens to the tag.

A file docker/java-base/.approved-ref is the single source of truth for which base image the runtime build uses. It stores the full reference combining the human-readable dated tag and the immutable digest:

dotcms/java-base:21.0.8-ms-20260302@sha256:74de5a500b17c8f2a1e3d4b9c7f8e2a1d3b5c9e7f2a4b6d8e1f3a5b7c9e2d4f6

The tag is context. The digest is the actual pin. If they ever diverge (someone pushed different content to the tag), Docker uses the digest and ignores the tag.

Runtime Dockerfile:

ARG BASE_IMAGE_REF=UNSET
FROM ${BASE_IMAGE_REF} AS container-base

CI pipeline reads the file and passes it as a build-arg:

- name: Read approved base image ref
  run: echo "BASE_IMAGE_REF=$(cat docker/java-base/.approved-ref)" >> $GITHUB_ENV

- name: Build runtime image
  uses: docker/build-push-action@v6
  with:
    build-args: BASE_IMAGE_REF=${{ env.BASE_IMAGE_REF }}
3. Validation gate before any tag is published

Every build — whether triggered by a Dockerfile change or a scheduled rebuild — runs smoke tests inside the image before any tags are pushed. If validation fails, no tags are updated and CI alerts immediately:

docker run --rm $IMAGE_ID java -version 2>&1 | grep -q "21\."
docker run --rm $IMAGE_ID /usr/bin/pg_dump --version
docker run --rm $IMAGE_ID dpkg -l libmimalloc2.0 libarchive-tools libtcnative-1
docker run --rm $IMAGE_ID env | grep -E "^JAVA_HOME="

This prevents upstream breakage (bad apt package, SDKMAN failure, pgdg script change) from becoming the floating tag and silently breaking all downstream builds.

4. Scheduled weekly rebuild for security freshness

A cron-triggered workflow rebuilds java-base weekly from the current Dockerfile. This picks up:

  • apt security patches applied by apt upgrade -y
  • Ubuntu 24.04 base layer updates pushed by Canonical

After a successful rebuild and validation:

  1. Push the dated tag (21.0.8-ms-{yyyymmdd}) and update the floating tag
  2. Capture the manifest list digest from build output (must use manifest list, not platform-specific digest, for multi-arch compatibility)
  3. Write the new image:tag@sha256:digest reference to .approved-ref
  4. Open an automated PR with this single-file change

The PR is the gate — auto-merge if CI passes, or require human review if preferred. If validation failed, no PR is opened and the previous approved ref remains in use.

5. Multi-arch digest

The workflow builds both linux/amd64 and linux/arm64. The .approved-ref file must store the manifest list digest (the multi-arch index), not a platform-specific digest. docker/build-push-action outputs this as steps.build.outputs.digest automatically.


Files

  • docker/java-base/.approved-ref (new file)
  • docker/java-base/Dockerfile (add Dockerfile hash label to image metadata)
  • dotCMS/src/main/docker/original/Dockerfile (replace SDKMAN_JAVA_VERSION ARG with BASE_IMAGE_REF ARG)
  • .github/workflows/cicd_manual_build-java-base.yml (add hash tag, validation gate, digest capture, .approved-ref update)
  • .github/workflows/cicd_scheduled_java-base-rebuild.yml (new scheduled workflow)
  • Runtime image CI pipeline (read .approved-ref and pass as build-arg)

Acceptance Criteria

  • docker/java-base/.approved-ref exists and contains a image:tag@sha256:digest reference
  • cicd_manual_build-java-base.yml pushes both the dated immutable tag and the Dockerfile hash tag in a single run
  • Validation gate runs before any tags are pushed; build fails loudly if validation fails and no tags are updated
  • The manifest list digest (not a platform-specific digest) is captured and written to .approved-ref
  • A scheduled weekly rebuild workflow exists and produces a PR updating .approved-ref on success
  • Runtime Dockerfile uses ARG BASE_IMAGE_REF and FROM ${BASE_IMAGE_REF}
  • CI pipeline reads .approved-ref and passes it as --build-arg BASE_IMAGE_REF
  • Two builds from the same git commit always use the same base image regardless of when they run
  • A Dockerfile change that has not been built and pushed causes the runtime CI build to fail at FROM with a clear missing-digest error
  • Rebuilding the same dated tag does not affect builds already pinned via digest

Priority

Medium

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with docker/java-base/Dockerfile, .github/workflows/cicd_manual_build-java-base.yml, and dotCMS/src/main/docker/original/Dockerfile; then trace the runtime image CI pipeline that consumes the base image. Check how the existing workflow builds and tags images before adding the scheduled workflow and validation flow. Done means the acceptance criteria pass, including digest-pinned runtime builds and automated .approved-ref updates.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, github-actions, java
Domain
build-system, ci-cd, devops, infrastructure
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.