Third of three line publications that carry the #99 null-safety to every Java
line. Templates and schema are the same for all three lines; only the
line-selecting fields change:
version: 4.0.0+up2026.8.2.java8 -> 4.0.0+up2026.8.2.java17
appVersion: "2026.8.2-java8" -> "2026.8.2-java17"
minecraft.version: "1.12.2" -> "1.20.1" (Java 17 runs
Minecraft 1.18-1.20.4)
What the diff does NOT show, same as for java8: the java17 line was last
published as 3.0.0+up2026.8.2.java17, and since then main also moved the
serverpack init image from alpine:3.22 to alpine:3.24 (commit 5a35ea4, which
landed on the java21 line only). That bump rides along here.
It is invisible in the diff against main, because relative to main the file
already says 3.24 - the java8 publication in #159 brought it there. Relative to
what a java17 release actually runs today it is a base-image change, so it is
named here rather than left to be discovered.
Measured against ci/minecraft-server/test-values.yaml with the serverpack init
container enabled, compared structurally (documents paired by kind/name,
recursive over keys) instead of line by line:
3.0.0+up2026.8.2.java17 (published) -> this chart:
exactly ONE difference, initContainers[0].image alpine:3.22 -> alpine:3.24.
main (java8) -> this chart:
exactly TWO differences, both the line selection itself -
image itzg/minecraft-server:2026.8.2-java8 -> :2026.8.2-java17 and
env VERSION 1.12.2 -> 1.20.1.
Hardening, the three exec probes, resources and the init.sh chown are unchanged,
which is what the null-safety was built to do: it changes what an ERASED block
renders, not what the defaults render.
At file level the line invariant holds exactly - against main's tree the only
differing files are Chart.yaml and values.yaml, in the three line fields plus
the comment naming the appVersion. _helpers.tpl, values.schema.json and both
templates are byte-identical across the lines.
The null-safety itself is not re-swept - #156 proved it on identical templates.
Confirmed once on this build that it carries: erasing both security contexts
renders byte-identical to the defaults.
Left untouched on purpose: the float64 quantity finding (#157), because
multiplyQuantity is the same copy in all 21 charts and a single-chart fix would
create drift, and seccompProfile (#84), which changes runtime behaviour and
belongs in its own change.
Refs #99
257 lines
12 KiB
YAML
257 lines
12 KiB
YAML
# Scale the whole chart down without uninstalling it: scaleDown: true renders
|
|
# every Deployment/StatefulSet with replicas: 0 (and suspends CronJobs) and
|
|
# takes precedence over any per-workload replicas value. PVCs, Secrets and
|
|
# Services stay in place; scaleDown: false brings the workloads back up.
|
|
scaleDown: false
|
|
|
|
secrets:
|
|
# op:// item path for the 1Password operator. Only needs to carry a
|
|
# "curseforge-api-key" key when curseforge.enabled is true and an
|
|
# "rcon-password" key when server.rcon.enabled is true.
|
|
#
|
|
# Documented limit (chart 3.0.0): changing this path rolls the pod (the
|
|
# StatefulSet hashes the rendered OnePasswordItem into checksum/secret-ref),
|
|
# but changing the item's CONTENT in the vault does not. The operator updates
|
|
# the Kubernetes Secret, while the running pod keeps the values it read at
|
|
# startup - the keys are consumed as environment variables, which Kubernetes
|
|
# never refreshes in a running container.
|
|
#
|
|
# The operator's own "operator.1password.io/auto-restart: true" annotation
|
|
# does NOT close this gap here, and is therefore deliberately not set: it only
|
|
# restarts Deployments. Verified in the operator source (main, 9c3b1b9,
|
|
# 2026-08-11): restartWorkloadsWithUpdatedSecrets lists nothing but
|
|
# appsv1.DeploymentList, and getPodTemplate has a single case for
|
|
# *appsv1.Deployment and returns "unsupported type" for everything else. This
|
|
# chart is a StatefulSet, so the annotation would do nothing while suggesting
|
|
# the opposite.
|
|
#
|
|
# So after rotating rcon-password or curseforge-api-key in the vault, restart
|
|
# the workload by hand (kubectl rollout restart statefulset/...) - and expect
|
|
# the full startup cost of ~124 s measured for ftb-skies on Longhorn.
|
|
minecraftServerRef: null
|
|
|
|
server:
|
|
# Replica count for this workload; 0 is allowed and scales it down.
|
|
# The chart-global scaleDown flag takes precedence.
|
|
replicas: 1
|
|
|
|
# Port the Minecraft server listens on inside the container. It is passed to
|
|
# the image as SERVER_PORT, used as the containerPort and as the Service's
|
|
# target, and the probes go through the image's mc-health script, which reads
|
|
# SERVER_PORT too - so an override stays consistent everywhere.
|
|
port: 25565
|
|
|
|
service:
|
|
# ClusterIP is the default because routing to these servers is done by the
|
|
# static mappings of the minecraft-router chart, which reaches the server
|
|
# over its in-cluster DNS name. Set NodePort to expose the server directly
|
|
# on a node port instead (then outwardPort applies).
|
|
# NOTE: before chart 1.0.0 this was implied by "mcRouterIntegration.enabled",
|
|
# which also added a misleading mc-router auto-discovery annotation. The
|
|
# annotation is gone; only the service type remains, now named for what it
|
|
# actually does.
|
|
type: ClusterIP
|
|
# Port the Service exposes in the cluster.
|
|
port: 25565
|
|
# Node port used only when type is NodePort. Must be inside the cluster's
|
|
# node-port range (30000-32767 by default).
|
|
outwardPort: 30565
|
|
|
|
# The \u00A7 sequences are Java-properties escapes for the Minecraft colour
|
|
# code character and must stay literal backslash sequences - server.properties
|
|
# is a Java properties file and unescapes them itself.
|
|
motd: 'Running \u00A72\u00A7o%MODPACK_NAME% \u00A70version \u00A7r%env:MODPACK_VERSION%'
|
|
|
|
rcon:
|
|
# RCON is off by default. Only when this is true does the chart set
|
|
# ENABLE_RCON, expose the RCON container port and wire RCON_PASSWORD from
|
|
# the secret - before chart 1.0.0 the password was pulled from the secret
|
|
# unconditionally although RCON was hardcoded off, which made the secret
|
|
# key mandatory for a switched-off feature.
|
|
enabled: false
|
|
port: 25575
|
|
|
|
# Init container that downloads and unpacks a CurseForge server pack; only
|
|
# used when curseforgeServerpack.enabled is true. Pinned per repo convention
|
|
# (auxiliary images are never unpinned): before chart 1.0.0 this was a bare
|
|
# "alpine", i.e. an implicit :latest from Docker Hub.
|
|
initImage: alpine:3.24
|
|
|
|
resources:
|
|
# Limits are optional: memory and ephemeral-storage limits default to
|
|
# limitFactor x the request, an explicitly set limit always wins, and a
|
|
# CPU limit is only set when configured explicitly under "limits"
|
|
# (CPU is compressible - throttling is preferable to an OOM kill).
|
|
limitFactor: 3
|
|
requests:
|
|
cpu: "2"
|
|
ephemeral-storage: "100Mi"
|
|
# Deliberately unset: when no memory request is given, the chart derives
|
|
# it from minecraft.resources.memory (the JVM heap) so that the heap and
|
|
# the container's memory request/limit can never drift apart. With the
|
|
# default heap of 4G this yields a 4Gi request and, via limitFactor 3, a
|
|
# 12Gi limit - exactly the values that were hardcoded before chart 1.0.0.
|
|
# Set it explicitly to decouple the two.
|
|
memory: null
|
|
|
|
# Extra environment variables for the server container, appended to the ones
|
|
# the chart always sets. Values are rendered through tpl and support the value,
|
|
# secretKeyRef and configMapKeyRef forms:
|
|
# - name: SOME_ENV
|
|
# value: "{{ .Release.Name }}"
|
|
# - name: FROM_SECRET
|
|
# valueFrom:
|
|
# secretKeyRef:
|
|
# name: "some-secret"
|
|
# key: "someKey"
|
|
# - name: FROM_CONFIGMAP
|
|
# valueFrom:
|
|
# configMapKeyRef:
|
|
# name: "some-configmap"
|
|
# key: "someKey"
|
|
env: []
|
|
|
|
# All three probes run the image's own mc-health script, which performs a real
|
|
# Minecraft server-list ping against SERVER_PORT via mc-monitor - so "Ready"
|
|
# means the server answers players, not merely that the container process is
|
|
# alive. (Before chart 1.0.0 this chart had no probes at all, which is why a
|
|
# pod counted as Ready the moment the container started.)
|
|
livenessProbe:
|
|
exec:
|
|
command: ["mc-health"]
|
|
periodSeconds: 30
|
|
timeoutSeconds: 10
|
|
failureThreshold: 3
|
|
readinessProbe:
|
|
exec:
|
|
command: ["mc-health"]
|
|
periodSeconds: 15
|
|
timeoutSeconds: 10
|
|
failureThreshold: 3
|
|
# The startup probe gates liveness and readiness, so its budget has to cover
|
|
# the slowest legitimate start: on a fresh volume the image downloads the
|
|
# server jar and, for FTB/CurseForge modpacks, the whole modpack before the
|
|
# world even begins to load. Budget 15s * 80 = 20 minutes; the liveness probe
|
|
# only starts counting afterwards, so a slow start can no longer be killed.
|
|
startupProbe:
|
|
exec:
|
|
command: ["mc-health"]
|
|
periodSeconds: 15
|
|
timeoutSeconds: 10
|
|
failureThreshold: 80
|
|
|
|
securityContext:
|
|
pod:
|
|
runAsNonRoot: true
|
|
# uid/gid 1000 is the image's "minecraft" user. The image's entrypoint
|
|
# normally starts as root and drops to this user via gosu; starting as the
|
|
# user directly makes it take the non-root branch, which additionally skips
|
|
# the entrypoint's recursive chown of /data and its write to
|
|
# /etc/nsswitch.conf - the latter would fail on a read-only root filesystem.
|
|
runAsUser: 1000
|
|
runAsGroup: 1000
|
|
# fsGroup keeps the world volume writable for the minecraft user.
|
|
# OnRootMismatch (instead of Always) avoids a recursive chown of the whole
|
|
# world on every single pod start - on a 32Gi world that is minutes of
|
|
# startup time. It still runs once if the volume root does not already
|
|
# carry the group.
|
|
fsGroup: 1000
|
|
fsGroupChangePolicy: OnRootMismatch
|
|
container:
|
|
allowPrivilegeEscalation: false
|
|
# Works because the chart mounts an emptyDir at /tmp - the image writes the
|
|
# server-icon scratch file and mc-server-runner's console named pipe there.
|
|
# Everything else the server writes goes to the PVC at /data.
|
|
readOnlyRootFilesystem: true
|
|
runAsNonRoot: true
|
|
capabilities:
|
|
drop:
|
|
- ALL
|
|
add: []
|
|
|
|
minecraft:
|
|
# Valid values are: peaceful, easy, normal, and hard
|
|
difficulty: "easy"
|
|
whitelist:
|
|
# Boolean, wired to the image's ENABLE_WHITELIST in BOTH positions since
|
|
# chart 2.0.0 - which is what makes "false" actually switch the whitelist
|
|
# off. server.properties lives on the PVC, so before 2.0.0 a release that
|
|
# had once written white-list=true kept it forever: the chart only ever
|
|
# emitted whitelist variables while enabled was true and had no way to say
|
|
# "off". Now enabled: false emits ENABLE_WHITELIST=false, and the image
|
|
# rewrites white-list=false into the existing file.
|
|
#
|
|
# >>> enabled: true WITHOUT players locks the server down completely. <<<
|
|
# That is the image's own semantics and it is intended: white-list=true with
|
|
# an empty whitelist.json means nobody can join - not even the ops. Set
|
|
# players whenever this is turned on, or leave it at false.
|
|
#
|
|
# The default is false, matching both the image and vanilla Minecraft, which
|
|
# start with white-list=false. (Before chart 2.0.0 the default was true with
|
|
# players: null; that combination did nothing back then, because an empty
|
|
# WHITELIST never enabled anything - under the new semantics it would be a
|
|
# locked-out server, so the default is flipped.)
|
|
enabled: false
|
|
# How the image treats an ALREADY EXISTING whitelist.json on the volume:
|
|
# SKIP leaves it alone, MERGE adds players to it, SYNCHRONIZE makes it match
|
|
# players exactly (removing everyone else). Only applies while enabled.
|
|
overridePolicy: "MERGE"
|
|
# Comma-separated List of Players. Only passed to the image (as WHITELIST)
|
|
# while enabled is true - a non-empty WHITELIST turns the whitelist on in
|
|
# the image regardless of ENABLE_WHITELIST, so leaving it out is what keeps
|
|
# enabled: false switched off even for a release that still lists players.
|
|
players: null
|
|
management:
|
|
# Comma-separated List of Players. The image resolves every name against
|
|
# the external Playerdb API and aborts startup if one does not resolve.
|
|
opPlayers: null
|
|
maxPlayers: 10
|
|
forceGameMode: true
|
|
# Valid values are: creative, survival, adventure, spectator (only for Minecraft 1.8 or later)
|
|
gameMode: survival
|
|
worldSaveName: world
|
|
serverName: mc-server
|
|
resources:
|
|
storage: "32Gi"
|
|
# Storage class of the world PVC. Was hardcoded to "longhorn" before chart
|
|
# 1.0.0. Changing it on an existing release has no effect on the already
|
|
# bound volume - storageClassName is immutable once the PVC exists.
|
|
storageClassName: longhorn
|
|
# JVM heap in gigabytes (passed to the image as MEMORY). Also drives the
|
|
# container's memory request/limit unless server.resources.requests.memory
|
|
# is set explicitly.
|
|
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 and
|
|
# 1.20.5+ need Java 21. This chart publishes appVersion "2026.8.2-java17".
|
|
version: "1.20.1"
|
|
|
|
# curseforge, ftb and curseforgeServerpack are mutually exclusive - enabling
|
|
# more than one is rejected by the chart (before 1.0.0 it silently rendered a
|
|
# duplicate TYPE env entry and the last one won).
|
|
curseforge:
|
|
enabled: false
|
|
forceReload: false
|
|
modpack:
|
|
slug: null
|
|
versionId: null
|
|
exclude: null
|
|
excludeOverride: null
|
|
include: null
|
|
ftb:
|
|
enabled: false
|
|
modpackId: null
|
|
versionId: null
|
|
curseforgeServerpack:
|
|
enabled: false
|
|
# Both values below are rendered into the init.sh ConfigMap. Since chart 3.0.0
|
|
# a change here rolls the pod (checksum/config on the StatefulSet), which is
|
|
# what makes it take effect at all - init.sh is mounted with subPath and only
|
|
# read at pod start. The restart is not free: ~124 s measured for ftb-skies on
|
|
# Longhorn.
|
|
downloadUrl: null
|
|
workingDirectory: '/data/serverpack'
|