Merge pull request #229 from jk-cluster/feat/minecraft-server-tag-override
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Failing after 12s
Publish Minecraft-Server Helm Chart / Publish Minecraft-Server Helm Chart (push) Failing after 13s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Failing after 4s
Publish Satisfactory Helm Chart / Publish Satisfactory Helm Chart (push) Failing after 5s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Failing after 4s
Publish Urlaubsverwaltung Helm Chart / Publish Urlaubsverwaltung Helm Chart (push) Failing after 4s

minecraft-server: an overridable image tag and a Java-line guard (5.0.0+up2026.8.2.java25)
This commit is contained in:
jklein authored and GitHub committed 2026-09-02 12:08:07 +00:00
commit f49bfa4af8
7 files changed
+262 -31

No files matched your search

+1 -1
View File
@@ -15,7 +15,7 @@ Collection of some self-made helm-charts. Each top-level directory is a standalo
| `homeassistant` | Home Assistant | StatefulSet; the only chart carrying Helm tests (`templates/tests/`) | | `homeassistant` | Home Assistant | StatefulSet; the only chart carrying Helm tests (`templates/tests/`) |
| `matrix-synapse` | Matrix Synapse | StatefulSet plus an nginx delegation Deployment/Service | | `matrix-synapse` | Matrix Synapse | StatefulSet plus an nginx delegation Deployment/Service |
| `minecraft-router` | itzg/mc-router | Deployment + routing ConfigMap + NodePort Service (TCP only, no ingress); `mappings` is a required value | | `minecraft-router` | itzg/mc-router | Deployment + routing ConfigMap + NodePort Service (TCP only, no ingress); `mappings` is a required value |
| `minecraft-server` | itzg/minecraft-server | StatefulSet + PVC + Service + init-script ConfigMap (no ingress); service type is a value (`ClusterIP` by default, routed via `minecraft-router`). Published once per Java line — the image tag comes from `.Chart.AppVersion`, so the Java line is chosen by picking the chart version, and `minecraft.version` ships a default matching that line. Since chart 2.0.0 the tag is an **immutable dated upstream release** (`appVersion: <release>-java8` / `-java17` / `-java21` / `-java25`, e.g. `2026.8.2-java8`) instead of the continuously rebuilt rolling line tag. Pilot for the strict `values.schema.json` (#68), which is now binding for every chart | | `minecraft-server` | itzg/minecraft-server | StatefulSet + PVC + Service + init-script ConfigMap (no ingress); service type is a value (`ClusterIP` by default, routed via `minecraft-router`). **Only the newest Java line is maintained** (chart 5.0.0, #158): the directory carries one line, older ones stay published as frozen artifacts (java8/java17 at `4.0.0`, java21 at `4.1.0`) and receive no further chart changes — publishing once per line out of one directory is what made the lines drift. To run an older JVM with the current chart, set `image.tagOverride`; the chart then checks that `minecraft.version` fits the Java line of the **effective** tag and fails the render with both values named. Two cases pass unchecked on purpose: a tag without a `java<N>` part and a `minecraft.version` that is not a plain numeric `x[.y[.z]]`. `image.tagOverride` is deliberately outside Renovate's reach — `renovate.json5` tracks `appVersion` only. Since chart 2.0.0 the tag is an **immutable dated upstream release** (`appVersion: <release>-java8` / `-java17` / `-java21` / `-java25`, e.g. `2026.8.2-java8`) instead of the continuously rebuilt rolling line tag. Pilot for the strict `values.schema.json` (#68), which is now binding for every chart |
| `outline` | Outline | StatefulSet, separate DB/Redis secrets | | `outline` | Outline | StatefulSet, separate DB/Redis secrets |
| `paperless-ngx` | Paperless-ngx | Largest chart: consume CronJob, SMB-connector PV/PVC, pre/post-consumption scripts ConfigMap | | `paperless-ngx` | Paperless-ngx | Largest chart: consume CronJob, SMB-connector PV/PVC, pre/post-consumption scripts ConfigMap |
| `patchmon` | PatchMon | Two Deployments (frontend + backend), OIDC secret | | `patchmon` | PatchMon | Two Deployments (frontend + backend), OIDC secret |
+14 -5
View File
@@ -5,16 +5,25 @@ icon: https://avatars.githubusercontent.com/u/988985
type: application type: application
# The Java line of this chart version is chosen through appVersion, which is # ONLY THE NEWEST JAVA LINE IS MAINTAINED (chart 5.0.0, issue #158).
# the image tag: the chart is published once per line (java8 / java17 / java21 / # appVersion is the image tag and therefore fixes the JVM. Up to chart 4.1.0 the
# java25) and minecraft.version in values.yaml must match the line published # chart was published once per line (java8 / java17 / java21 / java25) out of
# here. # 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>") # 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 # 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 # 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 # 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. # 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" appVersion: "2026.8.2-java25"
maintainers: maintainers:
+46 -16
View File
@@ -9,7 +9,9 @@
{{- if and (kindIs "float64" $startup.periodSeconds) (kindIs "float64" $startup.failureThreshold) -}} {{- if and (kindIs "float64" $startup.periodSeconds) (kindIs "float64" $startup.failureThreshold) -}}
{{- $budget = printf "%.0f seconds" (mulf $startup.periodSeconds $startup.failureThreshold) -}} {{- $budget = printf "%.0f seconds" (mulf $startup.periodSeconds $startup.failureThreshold) -}}
{{- end -}} {{- 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 -}} {{- $svc := printf "%s-%s-service" .Release.Name .Chart.Name -}}
{{- $sfs := printf "%s-%s-sfs" .Release.Name .Chart.Name -}} {{- $sfs := printf "%s-%s-sfs" .Release.Name .Chart.Name -}}
{{- $pod := printf "%s-%s-sfs-0" .Release.Name .Chart.Name -}} {{- $pod := printf "%s-%s-sfs-0" .Release.Name .Chart.Name -}}
@@ -26,7 +28,7 @@
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- $memPinned := not (kindIs "invalid" (index $givenReq "memory")) -}} {{- $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 ACCESS — A MINECRAFT SERVER, NOT A WEB SERVICE
This release has no Ingress, no certificate and no URL. Clients speak the 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 }} 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 JAVA LINE OF THIS RELEASE IS {{ $line | default "NOT RECOGNIZABLE FROM ITS TAG" | upper }}
The JVM is fixed by the image tag, which comes from the chart's appVersion The JVM is fixed by the image tag, not by a value of the game. This release
({{ .Chart.AppVersion }}), not from a value. This build runs runs
minecraft.version {{ .Values.minecraft.version }}.
Java 8 runs Minecraft up to 1.16.5, 1.18-1.20.4 need Java 17, 1.20.5-1.21.11 itzg/minecraft-server:{{ $tag }}
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 with minecraft.version {{ .Values.minecraft.version }}.
that minecraft.version fits the line: a mismatch renders and installs without {{- if $tagOverridden }}
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 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: nowhere else:
kubectl -n {{ .Release.Namespace }} logs {{ $pod }} kubectl -n {{ .Release.Namespace }} logs {{ $pod }}
So moving the server across a line boundary is a change of CHART VERSION, not The boundaries are Mojang's (javaVersion.majorVersion in the piston-meta
a change of minecraft.version: pick the published chart of the target line and version manifest, per release):
set minecraft.version to match it. Changing only minecraft.version leaves the
old JVM in place. 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" }} {{- if eq $replicas "0" }}
SCALED TO ZERO SCALED TO ZERO
+127
View File
@@ -183,6 +183,133 @@ last one silently won. Fail loudly instead.
{{- end -}} {{- end -}}
{{- 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 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. null / empty override falls back to the defaults instead of erasing them.
@@ -1,4 +1,5 @@
{{- include "minecraft-server.validateModpackSources" . }} {{- include "minecraft-server.validateModpackSources" . }}
{{- include "minecraft-server.validateJavaLine" . }}
apiVersion: apps/v1 apiVersion: apps/v1
kind: StatefulSet kind: StatefulSet
metadata: metadata:
@@ -58,7 +59,11 @@ spec:
{{- end }} {{- end }}
containers: containers:
- name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs - 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 imagePullPolicy: IfNotPresent
securityContext: securityContext:
{{- include "minecraft-server.containerSecurityContext" .Values.securityContext | nindent 12 }} {{- include "minecraft-server.containerSecurityContext" .Values.securityContext | nindent 12 }}
+11
View File
@@ -12,6 +12,17 @@
"scaleDown": { "scaleDown": {
"type": "boolean" "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": { "secrets": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
+57 -8
View File
@@ -4,6 +4,38 @@
# Services stay in place; scaleDown: false brings the workloads back up. # Services stay in place; scaleDown: false brings the workloads back up.
scaleDown: false 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: secrets:
# op:// item path for the 1Password operator. Only needs to carry a # op:// item path for the 1Password operator. Only needs to carry a
# "curseforge-api-key" key when curseforge.enabled is true and an # "curseforge-api-key" key when curseforge.enabled is true and an
@@ -223,14 +255,31 @@ minecraft:
memory: "4" memory: "4"
# Minecraft version to run. Pinned rather than "LATEST", which would silently # Minecraft version to run. Pinned rather than "LATEST", which would silently
# drift to a new release on every restart. # 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 # This value must match the Java line of the EFFECTIVE image tag - appVersion,
# version. Java 8 runs Minecraft up to 1.16.5, 1.18-1.20.4 need Java 17, # or image.tagOverride when that is set. Since chart 5.0.0 the chart checks it
# 1.20.5-1.21.11 need Java 21 and 26.1 and later need Java 25. The two upper # and FAILS THE RENDER on a mismatch, naming both values; before 5.0.0 a
# boundaries are read off Mojang's version manifest (piston-meta, field # mismatch installed silently and only the container failed afterwards.
# 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) # up to 1.16.5 Java 8
# on asks for 25. This chart publishes appVersion "2026.8.2-java25". # 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" version: "26.2"
# curseforge, ftb and curseforgeServerpack are mutually exclusive - enabling # curseforge, ftb and curseforgeServerpack are mutually exclusive - enabling