Lean, security-focused base images for Java (Temurin and distroless), Node.js (Alpine and distroless), and Python applications used across HMPPS.
| Image family | Repository | Example pull |
|---|---|---|
| Java (Temurin JRE) | ghcr.io/ministryofjustice/hmpps-eclipse-temurin |
docker pull ghcr.io/ministryofjustice/hmpps-eclipse-temurin:21-jre-jammy |
| Java (Distroless) | ghcr.io/ministryofjustice/hmpps-distroless-java |
docker pull ghcr.io/ministryofjustice/hmpps-distroless-java:25-jre |
| Node.js (Alpine) | ghcr.io/ministryofjustice/hmpps-node |
docker pull ghcr.io/ministryofjustice/hmpps-node:24-alpine |
| Node.js (Distroless) | ghcr.io/ministryofjustice/hmpps-distroless-node |
docker pull ghcr.io/ministryofjustice/hmpps-distroless-node:24 |
| Python | ghcr.io/ministryofjustice/hmpps-python |
docker pull ghcr.io/ministryofjustice/hmpps-python:python3.13-alpine |
Java:
21-jre-jammy25-jre-jammy21-jre(distroless)25-jre(distroless)
Node:
24-alpine24-alpine-runtime— same as24-alpinebut with package managers (npm, yarn, corepack) removed24(distroless)
Python:
python3.13-alpine
All images are built multi-arch: linux/amd64 and linux/arm64.
Each variant always has its raw variant tag (e.g. 21-jre-jammy). Additional dynamic tags are prefixed by the variant to avoid collisions:
| Tag Type | Example | When Present |
|---|---|---|
| Schedule date | 21-jre-jammy-20251120 |
Only on weekday 05:00 UTC scheduled build |
| Branch | 21-jre-jammy-initial-commit |
On branch builds (non-schedule) |
| PR | 21-jre-jammy-pr-123 |
On pull request builds (if enabled) |
| Git SHA | 21-jre-jammy-sha-<shortsha> |
All builds |
| Raw variant | 21-jre-jammy |
All builds |
The :latest tag is selectively enabled per matrix entry in CI. Consumers should prefer explicit variant tags.
- Weekday scheduled build: 05:00 UTC (creates date tags)
- Push to
main, PR, and manual dispatch builds are enabled - Multi-platform build/push via Buildx
- Slack notification on workflow failure
To upgrade image versions, update the matrix in .github/workflows/build-images.yml instead of editing Dockerfiles.
The workflow matrix controls:
image_name: output repository suffix (for exampleeclipse-temurin,distroless-node,python)context: Docker build context directorydockerfile: Dockerfile pathtag_prefix: raw variant tag and prefix for dynamic tagspublish_latest: whether to publishlatestfor that matrix entry
Example matrix entry:
- image_name: eclipse-temurin
context: images/java/eclipse-temurin
dockerfile: images/java/eclipse-temurin/Dockerfile.java25
tag_prefix: 25-jre-jammy
publish_latest: trueHow updates work:
- Existing tag refresh: keep the same
tag_prefix; scheduled rebuilds (cron) run withpull: true, so the latest upstream base layers are pulled automatically. - New tag adoption: update
dockerfile(and/or itsFROMimage tag), then keep or changetag_prefixto the published tag you want. - New variant publish: add a new matrix row with
image_name,context,dockerfile,tag_prefix, andpublish_latest.
When moving to a new tag, verify:
- The upstream tag in the Dockerfile
FROMexists and is supported for your target architecture (linux/amd64,linux/arm64). - The selected
context/dockerfilepaths match repository layout. - Consumers pin to the explicit variant tag (for example
25-jre-jammy) rather than relying onlatest.
All Dockerfiles use a multi-stage build pattern to ensure OS security patches are always applied fresh:
FROM base-image AS base
# ... setup steps ...
FROM base AS security-upgrades
RUN apk upgrade --no-cache # or apt-get upgrade for UbuntuThe CI workflow uses BuildKit's --no-cache-filter=security-upgrades to skip the cache for this stage, ensuring apk upgrade / apt-get upgrade always fetches the latest patches — even when other layers are cached.
Distroless images use prep/runtime stages and do not include a security-upgrades stage.