Bump SQL server JDK from Temurin 20 (EOL) to 21 LTS#1290
Conversation
c225166 to
c88a363
Compare
Temurin/OpenJDK 20 is a non-LTS release and no longer receives security updates. Move the SKDB SQL server Java toolchain to Temurin 21 LTS: - eclipse-temurin:20 → :21 in the server core and dev images - temurin-20-jdk → temurin-21-jdk (Adoptium apt) + JAVA_HOME in sql/Dockerfile - Gradle toolchain languageVersion 20 → 21 in the core and pg modules - CodeQL workflow's Kotlin analysis JDK 20 → 21 The Gradle wrapper (8.1.1) runs fine on JDK 21; verified locally by building the server-core and dev images and confirming temurin-21-jdk installs from the Adoptium noble repo at the expected JAVA_HOME path. The .sdkman sentinel version in sql/Dockerfile and sql/ts/tests/ service_common.mk is intentionally left at 20.0.2-tem: it is a build-time marker that lets the Makefile skip its SDKMAN install (the real JDK comes from apt), and it must match the already-published image, which the skdb-wasm CI job runs against. A comment now records this. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JDK 20 → 21 breaking-change check: no impactShort answer: no, we're not affected. Details of what I checked: Scope. The only JVM code in scope is the Kotlin SQL server ( Known 20 → 21 source-incompatibilities — all checked, none present:
Toolchain. Kotlin 2.1.20 fully supports JDK 21. 20 → 21 is a single feature release whose headline changes (virtual threads, pattern matching, sequenced collections) are all additive. Empirical. The server 🤖 Generated with Claude Code |
The skiplabs/* images CI runs on were published by a maintainer running bin/release_docker_ci_images.sh locally, with their own Docker Hub credentials. Nothing else ever rebuilt them, which is why skiplang-bin-builder sat a year stale carrying 3 critical / 68 high CVEs (#1338), and why skdb-base silently stopped building after the Ubuntu 24.04 bump until someone happened to rebuild it by hand (#1290) -- CI runs against the published images, so it cannot notice. Build and push the bake "ci" group from Actions instead, on three triggers: workflow_dispatch, push to main filtered to the paths that affect the images, and weekly. The path filter is what fixes the #1338 class of rot, where the Dockerfile pin was bumped and the republish forgotten. The weekly run is what picks up base image security updates, since with no layer cache every run re-resolves FROM and re-runs apt. Deliberately not triggered on skiplang/**: the compiler changes constantly, and republishing the CI images on every compiler commit is a policy change well beyond fixing the rot. Images can still lag main between publishes, exactly as they do today. Native runners rather than QEMU: arm64 hosted runners are free on public repos, and emulating a self-hosting compiler bootstrap is both ~4x slower and a risk worth not taking. Each runner builds its own platform and pushes by digest, then a merge job creates the multi-arch tags. Nothing rewrites a :latest tag until every platform of every image has pushed successfully. Verified locally against a registry on buildx v0.35.0, since the interesting parts cannot be exercised without pushing: a target that is both published and consumed as a named context (skdb-base builds FROM skip) keeps its own push-by-digest output; each per-platform push is an index carrying its attestation manifest inline; and imagetools create preserves both platforms' attestations into the merged tag. The buildx pin is deliberate. setup-buildx-action has no default version, and imagetools create silently dropped attestations before v0.30.0 -- a failure whose only symptom is the registry quietly lacking provenance.
Closes #1348. ## Problem The `skiplabs/*` images CI runs on were published by a maintainer running `bin/release_docker_ci_images.sh` locally, with their own Docker Hub credentials. Nothing else ever rebuilt them, and it shows: - `skiplang-bin-builder` sat ~a year stale carrying **3 critical / 68 high** CVEs (#1338), because the only thing that ever published it was a human remembering to. - `skdb-base` silently stopped building after the Ubuntu 24.04 bump and no one noticed until a manual rebuild happened to surface it (#1290) — CI runs *against* the published images, so it structurally cannot catch this. - `skiplang:latest` and `skip:latest` on Docker Hub are currently a week apart despite being stages of the *same* Dockerfile, so CI jobs run against inconsistent compiler builds. ## What this does Builds and pushes the images from GitHub Actions instead, on three triggers: - **`workflow_dispatch`** — on demand - **`push` to `main`**, path-filtered to the files the images are built from — this is what fixes the #1338 class of rot (pin bumped, republish forgotten) - **weekly `schedule`** — picks up base-image security updates, since with no layer cache every run re-resolves `FROM` and re-runs `apt` ### Scope: four toolchain images Publishes the new **`ci` group** in `docker-bake.hcl`: `skiplang`, `skiplang-bin-builder`, `skip`, `skdb-base`. These are toolchain images — their final stages hold compiled binaries and apt packages but no repo source, so any green `main` commit can rebuild them and republishing is not a release. `skdb` and `skdb-dev-server` are deliberately excluded: they bake in source and their tags carry release semantics. (`skiplabs/skdb` has in fact never existed on Docker Hub, despite being in bake's `default` group.) ### Source of truth The `ci` group is authoritative. The workflow derives its per-image tag overrides from `bake --print ci` at runtime rather than listing them, `release_docker_ci_images.sh` now takes its list from the same group (so the manual path and the automated one can't drift, and a manual publish finally includes `skiplang-bin-builder`), and a new `bin/check_ci_images.sh` asserts every image CircleCI pulls is in the group — replacing the old `grep .circleci/base.yml` derivation, which structurally could not express `skiplang-bin-builder`. ### Runner strategy Native `ubuntu-24.04` + `ubuntu-24.04-arm` in parallel (arm64 hosted runners are free on public repos), each pushing by digest, then a merge job creates the multi-arch tags. QEMU was rejected: ~4× slower and it would emulate a self-hosting compiler bootstrap. **Nothing rewrites a `:latest` tag until every platform of every image has pushed successfully**, and the merge validates all images before publishing any. Provenance (`mode=max`) + SBOM attestations are emitted via bake-action's `provenance`/`sbom` inputs, matching what `bin/docker_build.sh` already does for the manual path (#1350), so images published either way carry the same attestations. ## Verification The parts that can't be exercised without pushing were verified locally against a registry on buildx v0.35.0: - a target that is both published **and** consumed as a named context (`skdb-base` builds `FROM skip`) keeps its own push-by-digest output — settled at solve time, not just `bake --print`; - each per-platform push is an OCI index carrying its attestation manifest inline; - `imagetools create` preserves **both** platforms' attestations into the merged tag (verified: merged index = 2 platform manifests + 2 attestation manifests). `actionlint` (1.7.7) and `shellcheck` are clean on everything here. ## Notes for the reviewer - **Requires org secrets `DOCKERHUB_USERNAME` / `DOCKERHUB_TOKEN`** (scoped push token) before the first run — the repo has none today. The first run should be a watched `workflow_dispatch`, not the weekly schedule. - **Stacked on the actionlint fix** (first commit): CI currently installs actionlint **1.7.6** — the `v1.7.7` in the URL only selects the downloader script, which defaults to 1.7.6 — and 1.7.6 rejects `runs-on: ubuntu-24.04-arm`. That commit stands on its own but this workflow needs it to lint. - **Conscious tradeoff:** the workflow does not run CI against the freshly built images before overwriting `:latest`. That would roughly double the pipeline; the digest/attestation guards catch push-level failures but not "builds fine, breaks CI". `cancel-in-progress: false` and a watched first run are the mitigations. - Actions are pinned to major tags (`@v4`/`@v7`), matching repo convention (no dependabot). SHA-pinning would harden this first credential-holding workflow but the actions would then never update — happy to switch if preferred. - Expect `unknown/unknown` rows on Docker Hub for these images — that's how BuildKit stores attestation manifests, not a bug. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Summary
Temurin/OpenJDK 20 is a non-LTS release that stopped receiving security updates in 2023. This moves the SKDB SQL-server Java toolchain to Temurin 21 LTS, a natural follow-on to the base-image modernization in #1261.
Changes (all version-string bumps, no logic changes):
eclipse-temurin:20→:21— servercoreanddevimagestemurin-20-jdk→temurin-21-jdk(Adoptium apt) +JAVA_HOMEinsql/DockerfilelanguageVersion20→21—coreandpgmodules20→21The Gradle wrapper stays at 8.1.1 — it runs fine on JDK 21 (verified), so no wrapper bump is needed.
Note on the SDKMAN sentinel
The
.sdkman/candidates/java/20.0.2-temmarker insql/Dockerfileandsql/ts/tests/service_common.mkis intentionally left at20.0.2-tem. It is a build-time sentinel that lets the Makefile skip its SDKMAN install (the real JDK comes from apt), and it must match the already-published image that theskdb-wasmCI job runs in. A comment now documents this so future JDK bumps don't touch it. (An earlier revision bumped it and turnedskdb-wasmred —sdk: command not found— because the published image still had the old sentinel dir.)Test plan
Verified locally (Docker, amd64):
server-coreimage builds oneclipse-temurin:21— Kotlin compiles, jars,checkpasses with wrapper 8.1.1devimageskgwstage builds oneclipse-temurin:21temurin-21-jdkinstalls from the Adoptium noble apt repo;JAVA_HOME=/usr/lib/jvm/temurin-21-jdk-amd64resolves;java -version→21.0.11 LTS