minecraft-server: an overridable image tag and a Java-line guard (5.0.0+up2026.8.2.java25)

Only the newest Java line is maintained from now on. Publishing the chart once
per line out of one directory is what made the lines drift apart (#158): a
change reached whichever line happened to lie in the directory. java8 and java17
stay published as frozen artifacts at 4.0.0, java21 at 4.1.0, and none of them
gets another chart change.

Running an older JVM is now a decision at rollout time instead of a chart
version: image.tagOverride replaces the tag that otherwise comes from
appVersion. It is not called image.tag because it decouples the running image
from the chart version, and it is deliberately outside Renovate's reach -
renovate.json5 keeps tracking appVersion only.

The pairing of tag and minecraft.version is now checked at render time and fails
with both values named, the line each one belongs to, and where the tag came
from. Until now nothing checked it: a mismatch installed silently and only the
container failed afterwards. The boundaries are Mojang's, swept over all 102
releases of the piston-meta manifest (javaVersion.majorVersion): up to 1.16.5
Java 8, 1.17-1.17.1 Java 16, 1.18-1.20.4 Java 17, 1.20.5-1.21.11 Java 21, 26.1
and later Java 25. The 1.17 line was the one gap the chart's own notes did not
cover.

Two cases pass unchecked on purpose, documented in values.yaml and NOTES.txt: a
tag without a recognizable java<N> part, and a minecraft.version that is not a
plain numeric x[.y[.z]]. A guard that rejects what it does not understand gets
worked around instead of respected.

Major, because an installation that already runs a mismatched pair stops
rendering. It does not run today either - it now fails earlier and visibly.
This commit is contained in:
jklein committed 2026-09-02 13:56:25 +02:00
1 parent 63662c8ea8
commit 9c0039685b
7 files changed
+262 -31

No files matched your search

+14 -5
View File
@@ -5,16 +5,25 @@ icon: https://avatars.githubusercontent.com/u/988985
type: application
# The Java line of this chart version is chosen through appVersion, which is
# the image tag: the chart is published once per line (java8 / java17 / java21 /
# java25) and minecraft.version in values.yaml must match the line published
# here.
# ONLY THE NEWEST JAVA LINE IS MAINTAINED (chart 5.0.0, issue #158).
# appVersion is the image tag and therefore fixes the JVM. Up to chart 4.1.0 the
# chart was published once per line (java8 / java17 / java21 / java25) out of
# this single directory, which is what made the lines drift apart: whatever
# change landed while a line happened to lie in the directory reached that line
# only. That is over. This directory carries exactly one line - the newest -
# and it is the only one that gets chart changes.
# The older lines stay published as frozen artifacts and are still installable:
# java8 and java17 at 4.0.0, java21 at 4.1.0. They will not be updated again.
# To run an older JVM with the CURRENT chart, set image.tagOverride in values
# instead of installing a frozen chart version; the chart then checks that
# minecraft.version fits the line of that tag and fails the render if it does
# not.
# Since chart 2.0.0 the tag is a DATED upstream release ("<release>-java<N>")
# instead of the rolling line tag ("java<N>"): the rolling tags are rebuilt
# continuously, so a pod restart could pull a different image than the last one
# without anything being recorded anywhere. The dated tags are immutable, so the
# image is now pinned by the chart version like every other chart in this repo.
version: 4.1.0+up2026.8.2.java25
version: 5.0.0+up2026.8.2.java25
appVersion: "2026.8.2-java25"
maintainers:
+46 -16
View File
@@ -9,7 +9,9 @@
{{- if and (kindIs "float64" $startup.periodSeconds) (kindIs "float64" $startup.failureThreshold) -}}
{{- $budget = printf "%.0f seconds" (mulf $startup.periodSeconds $startup.failureThreshold) -}}
{{- end -}}
{{- $line := regexFind "java[0-9]+" (printf "%v" .Chart.AppVersion) -}}
{{- $tag := include "minecraft-server.imageTag" . -}}
{{- $line := include "minecraft-server.javaLine" . -}}
{{- $tagOverridden := ne (include "minecraft-server.imageTagSource" .) ".Chart.AppVersion" -}}
{{- $svc := printf "%s-%s-service" .Release.Name .Chart.Name -}}
{{- $sfs := printf "%s-%s-sfs" .Release.Name .Chart.Name -}}
{{- $pod := printf "%s-%s-sfs-0" .Release.Name .Chart.Name -}}
@@ -26,7 +28,7 @@
{{- end -}}
{{- end -}}
{{- $memPinned := not (kindIs "invalid" (index $givenReq "memory")) -}}
minecraft-server {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
minecraft-server {{ $tag }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
ACCESS — A MINECRAFT SERVER, NOT A WEB SERVICE
This release has no Ingress, no certificate and no URL. Clients speak the
@@ -76,25 +78,53 @@ ACCESS — A MINECRAFT SERVER, NOT A WEB SERVICE
kubectl -n {{ .Release.Namespace }} port-forward svc/{{ $svc }} {{ .Values.server.service.port }}:{{ .Values.server.service.port }}
THIS CHART IS PUBLISHED ONCE PER JAVA LINE — THIS IS {{ $line | default "an unrecognized line" | upper }}
The JVM is fixed by the image tag, which comes from the chart's appVersion
({{ .Chart.AppVersion }}), not from a value. This build runs
minecraft.version {{ .Values.minecraft.version }}.
THE JAVA LINE OF THIS RELEASE IS {{ $line | default "NOT RECOGNIZABLE FROM ITS TAG" | upper }}
The JVM is fixed by the image tag, not by a value of the game. This release
runs
Java 8 runs Minecraft up to 1.16.5, 1.18-1.20.4 need Java 17, 1.20.5-1.21.11
need Java 21 and 26.1 and later need Java 25 (read off Mojang's version
manifest, javaVersion.majorVersion per release). Nothing in this chart checks
that minecraft.version fits the line: a mismatch renders and installs without
a word, and only the container fails afterwards — CrashLoopBackOff once the
startup budget{{ if $budget }} ({{ $budget }}){{ end }} is spent, with the reason in the log and
itzg/minecraft-server:{{ $tag }}
with minecraft.version {{ .Values.minecraft.version }}.
{{- if $tagOverridden }}
That tag comes from image.tagOverride, NOT from the chart's appVersion
({{ .Chart.AppVersion }}). So the chart version no longer says what this release runs:
"helm list" reports {{ .Chart.Version }} while the pod is on the tag
above. That is the price of the override, and the reason it is not called
image.tag.
{{- else }}
That tag comes from the chart's appVersion; image.tagOverride is empty, so
the chart version tells the whole truth about the image.
{{- end }}
Since chart 5.0.0 THAT PAIRING IS CHECKED: if the line of the tag above does
not fit minecraft.version, the render fails and names both values. Two cases
are let through on purpose — a tag without a recognizable "java<N>" part, and
a minecraft.version that is not a plain numeric x[.y[.z]] (a snapshot, a
pre-release, "LATEST"). In those two the old failure mode still applies: the
release installs, and only the container fails afterwards — CrashLoopBackOff
once the startup budget{{ if $budget }} ({{ $budget }}){{ end }} is spent, with the reason in the log and
nowhere else:
kubectl -n {{ .Release.Namespace }} logs {{ $pod }}
So moving the server across a line boundary is a change of CHART VERSION, not
a change of minecraft.version: pick the published chart of the target line and
set minecraft.version to match it. Changing only minecraft.version leaves the
old JVM in place.
The boundaries are Mojang's (javaVersion.majorVersion in the piston-meta
version manifest, per release):
up to 1.16.5 Java 8
1.17 - 1.17.1 Java 16
1.18 - 1.20.4 Java 17
1.20.5 - 1.21.11 Java 21
26.1 and later Java 25
ONLY THE NEWEST LINE IS MAINTAINED. This chart directory carries one line and
gets chart changes only for that one; java8 and java17 stay frozen at chart
4.0.0, java21 at 4.1.0. So moving the server across a line boundary is no
longer a change of chart version: set image.tagOverride to a tag of the target
line AND minecraft.version to a release of it, in the same upgrade. Changing
only one of the two now fails the render instead of leaving a broken pod
behind.
{{- if eq $replicas "0" }}
SCALED TO ZERO
+127
View File
@@ -183,6 +183,133 @@ last one silently won. Fail loudly instead.
{{- end -}}
{{- end -}}
{{/*
The tag of the itzg/minecraft-server image this release actually runs.
Default: .Chart.AppVersion, exactly as before chart 5.0.0. image.tagOverride
replaces it when set - that is the one way to run a Java line other than the
one this chart version publishes, now that only the newest line is maintained
(see Chart.yaml). The value is deliberately NOT called image.tag: it is an
emergency exit, not a knob to turn in passing, and it is not managed by
Renovate (renovate.json5 tracks appVersion only).
*/}}
{{- define "minecraft-server.imageTag" -}}
{{- $override := "" -}}
{{- if kindIs "map" .Values.image -}}
{{- if not (kindIs "invalid" .Values.image.tagOverride) -}}
{{- $override = printf "%v" .Values.image.tagOverride | trim -}}
{{- end -}}
{{- end -}}
{{- if $override -}}{{ $override }}{{- else -}}{{ .Chart.AppVersion }}{{- end -}}
{{- end -}}
{{/*
Where the effective tag came from - used in the guard's message so the reader
knows which of the two values to change.
*/}}
{{- define "minecraft-server.imageTagSource" -}}
{{- $override := "" -}}
{{- if kindIs "map" .Values.image -}}
{{- if not (kindIs "invalid" .Values.image.tagOverride) -}}
{{- $override = printf "%v" .Values.image.tagOverride | trim -}}
{{- end -}}
{{- end -}}
{{- if $override -}}image.tagOverride{{- else -}}.Chart.AppVersion{{- end -}}
{{- end -}}
{{/*
The Java line the effective image tag carries ("java25", "java17", ...), or the
empty string when the tag does not name one.
regexFind rather than a suffix comparison, so flavoured tags keep working:
"2026.8.2-java25", "java21-alpine" and "latest-java17-jdk" all resolve.
*/}}
{{- define "minecraft-server.javaLine" -}}
{{- regexFind "java[0-9]+" (include "minecraft-server.imageTag" .) -}}
{{- end -}}
{{/*
The Java line minecraft.version requires, or the empty string when the value is
not a version this chart can compare.
The boundaries are Mojang's, not this chart's: they are javaVersion.majorVersion
from the piston-meta version manifest, swept over all 102 releases on 2026-09-02.
The transitions are exactly four, and monotonic in release order:
1.0 - 1.16.5 java 8 (1.6.1-1.6.4 predate the field and carry none)
1.17 - 1.17.1 java 16
1.18 - 1.20.4 java 17
1.20.5 - 1.21.11 java 21
26.1 and later java 25
DELIBERATE GAP: a minecraft.version that is not a plain numeric "x", "x.y" or
"x.y.z" - a snapshot ("26w13a"), a pre-release ("1.21.11-pre1"), "LATEST" -
yields the empty string and is let through unchecked. The chart cannot compare
it, and a guard that rejects everything it does not understand gets worked
around instead of respected. The mismatch then still surfaces the old way:
CrashLoopBackOff once the startup budget is spent, with the reason in the log.
*/}}
{{- define "minecraft-server.requiredJavaLine" -}}
{{- $v := printf "%v" .Values.minecraft.version | trim -}}
{{- if regexMatch "^[0-9]+(\\.[0-9]+){0,2}$" $v -}}
{{- $n := len (splitList "." $v) -}}
{{- $sv := $v -}}
{{- if eq $n 1 -}}{{- $sv = printf "%s.0.0" $v -}}{{- else if eq $n 2 -}}{{- $sv = printf "%s.0" $v -}}{{- end -}}
{{- if semverCompare "< 1.17.0" $sv -}}java8
{{- else if semverCompare "< 1.18.0" $sv -}}java16
{{- else if semverCompare "< 1.20.5" $sv -}}java17
{{- else if semverCompare "< 26.1.0" $sv -}}java21
{{- else -}}java25
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Guard: minecraft.version must fit the Java line of the effective image tag.
Until chart 5.0.0 nothing checked this - a mismatch rendered and installed
without a word, and only the container failed afterwards. It now fails at render
time, which is a behaviour change for an installation that already runs a
mismatched pair (it does not run: it fails earlier and visibly instead).
The check runs ALWAYS, not only when image.tagOverride is set: Helm merges user
values into the chart defaults indistinguishably, so a template cannot tell a
deliberately set minecraft.version from the chart's own default.
DELIBERATE GAP, the second one: an image tag without a recognizable java<N> part
is let through - see minecraft-server.javaLine. Same reasoning as for an
uncomparable minecraft.version.
*/}}
{{- define "minecraft-server.validateJavaLine" -}}
{{- $tag := include "minecraft-server.imageTag" . -}}
{{- $line := include "minecraft-server.javaLine" . -}}
{{- $required := include "minecraft-server.requiredJavaLine" . -}}
{{- if and $line $required -}}
{{- if ne $line $required -}}
{{- $source := include "minecraft-server.imageTagSource" . -}}
{{- $swapped := regexReplaceAll "java[0-9]+" $tag $required -}}
{{- fail (printf (join "\n" (list
"minecraft-server: the image tag and minecraft.version belong to different Java lines."
""
" image tag %-22s is the %s line (from %s)"
" minecraft.version %-22s needs the %s line"
""
"Java 8 runs Minecraft up to 1.16.5, 1.17-1.17.1 need Java 16, 1.18-1.20.4 need"
"Java 17, 1.20.5-1.21.11 need Java 21, and 26.1 and later need Java 25 (Mojang's"
"version manifest, javaVersion.majorVersion per release)."
""
"Change one of the two:"
" - set minecraft.version to a release of the %s line, or"
" - set image.tagOverride to an itzg/minecraft-server tag of the %s line"
" (this tag with its line swapped would be \"%s\" - check that upstream"
" published it before using it)."))
(printf "%q" $tag) $line $source
(printf "%q" (printf "%v" .Values.minecraft.version)) $required
$line $required $swapped) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Merge a user-supplied values sub-tree onto a dict of chart defaults so that a
null / empty override falls back to the defaults instead of erasing them.
@@ -1,4 +1,5 @@
{{- include "minecraft-server.validateModpackSources" . }}
{{- include "minecraft-server.validateJavaLine" . }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
@@ -58,7 +59,11 @@ spec:
{{- end }}
containers:
- name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
image: "itzg/minecraft-server:{{ .Chart.AppVersion }}"
{{- /* Tag from .Chart.AppVersion unless image.tagOverride replaces
it; validateJavaLine above rejects a tag whose Java line does
not fit minecraft.version. Template comment on purpose - a
YAML comment here would change the rendered manifest. */}}
image: "itzg/minecraft-server:{{ include "minecraft-server.imageTag" . }}"
imagePullPolicy: IfNotPresent
securityContext:
{{- include "minecraft-server.containerSecurityContext" .Values.securityContext | nindent 12 }}
+11
View File
@@ -12,6 +12,17 @@
"scaleDown": {
"type": "boolean"
},
"image": {
"description": "May be null / empty: the effective tag then falls back to the chart's appVersion, which is the pre-5.0.0 behaviour.",
"type": ["object", "null"],
"additionalProperties": false,
"properties": {
"tagOverride": {
"description": "Tag of the itzg/minecraft-server image. Empty or null means the chart's appVersion. Deliberately not called \"tag\": it decouples the running image from the chart version and is not managed by Renovate. The Java line of the effective tag is validated against minecraft.version at render time; a tag without a recognizable \"java<N>\" part is let through on purpose.",
"type": ["string", "null"]
}
}
},
"secrets": {
"type": "object",
"additionalProperties": false,
+57 -8
View File
@@ -4,6 +4,38 @@
# Services stay in place; scaleDown: false brings the workloads back up.
scaleDown: false
image:
# Tag of the itzg/minecraft-server image. Empty means "use the chart's
# appVersion" (2026.8.2-java25 here), which is what every release before chart
# 5.0.0 did unconditionally.
#
# WHY THIS EXISTS. Since chart 5.0.0 only the NEWEST Java line is maintained
# (see Chart.yaml). The older lines stay published as frozen artifacts - java8
# and java17 at chart 4.0.0, java21 at 4.1.0 - and never receive another chart
# change. To run an older JVM with the CURRENT chart, set the tag here instead
# of installing a frozen chart version:
#
# image:
# tagOverride: "2026.8.2-java21"
# minecraft:
# version: "1.21.11"
#
# It is called tagOverride, not tag, because it is an emergency exit: it
# decouples the running image from the chart version, so the release no longer
# says in its metadata what it actually runs. Prefer moving to the current
# line; use this when a modpack pins you to an older Minecraft version.
#
# DELIBERATELY NOT MANAGED BY RENOVATE. renovate.json5 tracks appVersion in
# Chart.yaml (partitioned by its "java<N>" compatibility group) and nothing
# else. A tag set here is never bumped by anything - it is yours to review.
#
# The Java line of the effective tag (this value, or appVersion when it is
# empty) is checked against minecraft.version at render time; a mismatch fails
# the render with both values named. Two cases pass unchecked on purpose: a
# tag without a recognizable "java<N>" part, and a minecraft.version that is
# not a plain numeric x[.y[.z]] (snapshots, pre-releases, "LATEST").
tagOverride: ""
secrets:
# op:// item path for the 1Password operator. Only needs to carry a
# "curseforge-api-key" key when curseforge.enabled is true and an
@@ -223,14 +255,31 @@ minecraft:
memory: "4"
# Minecraft version to run. Pinned rather than "LATEST", which would silently
# drift to a new release on every restart.
# This value must match the Java line of the chart version: the image tag
# comes from .Chart.AppVersion, so the JVM is fixed per published chart
# version. Java 8 runs Minecraft up to 1.16.5, 1.18-1.20.4 need Java 17,
# 1.20.5-1.21.11 need Java 21 and 26.1 and later need Java 25. The two upper
# boundaries are read off Mojang's version manifest (piston-meta, field
# javaVersion.majorVersion per release), not estimated: 1.21.11 (2025-12-09)
# is the last release asking for 21, and every release from 26.1 (2026-03-24)
# on asks for 25. This chart publishes appVersion "2026.8.2-java25".
#
# This value must match the Java line of the EFFECTIVE image tag - appVersion,
# or image.tagOverride when that is set. Since chart 5.0.0 the chart checks it
# and FAILS THE RENDER on a mismatch, naming both values; before 5.0.0 a
# mismatch installed silently and only the container failed afterwards.
#
# up to 1.16.5 Java 8
# 1.17 - 1.17.1 Java 16 (never published as a chart line here)
# 1.18 - 1.20.4 Java 17
# 1.20.5 - 1.21.11 Java 21
# 26.1 and later Java 25
#
# These are Mojang's boundaries, not this chart's: javaVersion.majorVersion
# from the piston-meta version manifest, swept over all 102 releases on
# 2026-09-02. There are exactly four transitions and they are monotonic in
# release order (1.6.1-1.6.4 predate the field and declare none).
#
# Only a plain numeric x[.y[.z]] is compared. A snapshot ("26w13a"), a
# pre-release ("1.21.11-pre1") or "LATEST" is let through unchecked - see
# image.tagOverride for why the two gaps are deliberate.
#
# Quote the value. Unquoted YAML reads 1.20 as the number 1.2, and the chart
# would pass "1.2" to the image as VERSION.
#
# This chart publishes appVersion "2026.8.2-java25".
version: "26.2"
# curseforge, ftb and curseforgeServerpack are mutually exclusive - enabling