Skip to content

Custom resources

Generated from config/crd/bases/*.yaml -- the same schema kubectl explain and kubectl apply --validate read. Five namespaced kinds in spawnery.cloud/v1alpha1.

A few fields are Kubernetes' own types, embedded by reference rather than declared in this repository: they appear as a single row naming the type and linking to the Go package reference for k8s.io/api/core/v1 instead of being expanded here.

Network

Scope: Namespaced    Plural: networks    Short names: mcnet

Network is the root resource of a Minecraft network.

What kubectl get shows

ColumnTypeJSONPath
Server Groupsinteger.status.serverGroups
Playersinteger.status.onlinePlayers
Agedate.metadata.creationTimestamp

spec

NetworkSpec describes one Minecraft network. Exactly one Network may exist per namespace; further ones are rejected with an Accepted=False condition.

FieldTypeRequiredDefaultDescription
commandsobjectCommands opens in-game commands that reach past the operator's own objects. Absent, all of them are off.
commands.executebooleanExecute lets an administrator on a proxy run a console command on one server or on every server of a group with /cloud execute. Off, the operator refuses every such request.
defaultsobjectDefaults are inherited by all groups of this network.
defaults.feedFormatstring<gray>»</gray> <gradient:aqua:green>Spawnery</gradient> <dark_gray>|</dark_gray> <gray>$EVENT_MESSAGEFeedFormat is the line the agent's chat output wears: announcements about the cloud and replies to a /cloud command alike.
moreMiniMessage. $EVENT_MESSAGE is replaced by what the line has to say; everything around it is yours.

Changing it rolls no pod and takes effect within a resync interval. A value that MiniMessage cannot parse makes the agent send the message alone.
defaults.imagePullSecretsarrayImagePullSecrets are attached to every managed pod.
defaults.imagePullSecrets[].namestringName of the referent. This field is effectively required, but due to backwards compatibility is allowed to be empty. Instances of this type with an empty value here are almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
defaults.minecraftVersionstringMinecraftVersion documents the version the images of this network carry.
defaults.resourcesResourceRequirements (Kubernetes, not expanded here)Resources are the default container resources.
defaults.schedulingobjectScheduling is the default pod placement.
defaults.scheduling.affinityAffinity (Kubernetes, not expanded here)Affinity expresses scheduling preferences and constraints.
defaults.scheduling.nodeSelectormap[string]stringNodeSelector restricts pods to nodes carrying all these labels.
defaults.scheduling.tolerations[]Toleration (Kubernetes, not expanded here)Tolerations allow pods onto tainted nodes.
forwardingSecretRefobject✓ForwardingSecretRef names the Secret holding the Velocity modern forwarding secret under the key "secret".
moreGenerate the value at random. Every pod carries a salted eight-byte digest of it in the spawnery.cloud/forwarding-hash label, readable by anyone who can read pods, so a memorable secret can be guessed offline. It is the whole of the authentication between the proxy and the backends.
forwardingSecretRef.namestring (length >= 1)✓Name of the referenced object.
schedulingobjectScheduling is what this network's groups may ask of the scheduler.
moreAbsent, nothing is allowed: a group that sets spec.scheduling, or a proxy group exposed by HostPort, is not accepted.
scheduling.allowedAffinityNamespacesarray of stringAllowedAffinityNamespaces are the namespaces a pod-affinity or pod-anti-affinity term may name besides the group's own. A namespaceSelector can name any namespace and is never allowed.
scheduling.allowedNodeSelectorKeysarray of stringAllowedNodeSelectorKeys are the node label keys a group may select on, in nodeSelector and in node-affinity terms alike.
scheduling.allowedTolerationKeysarray of stringAllowedTolerationKeys are the taint keys a group may tolerate. A toleration with no key matches every taint and is never allowed.
scheduling.hostPortRangeobjectHostPortRange bounds expose.hostPort.port on this network's proxy groups. Without it no HostPort group is accepted.
scheduling.hostPortRange.maxinteger (int32) (1-65535)✓
scheduling.hostPortRange.mininteger (int32) (1-65535)✓
updateobjectUpdate is how the network's groups change over to a new spec.
update.maxConcurrentChangeoversinteger (int32) (>= 1)MaxConcurrentChangeovers is how many server and proxy groups may change over at the same time. A group changing over runs one server more than its size until its last stale server is gone; a change that reaches every group at once needs that room for every group at once. Unset means no cap.
voiceobjectVoice relays Simple Voice Chat through this network's proxies. Present means on: every ProxyGroup relays, and the backends' policy admits UDP backendPort from the proxies. The SVC plugin itself is not installed by the operator, and SVC's own Velocity plugin must not be.
voice.backendPortinteger (int32) (1-65535)24454BackendPort is the UDP port SVC listens on in each backend pod. The operator writes it nowhere; it has to match the plugin's own configuration.

status

Read by observers; the operator writes it and reads nothing back from it.

FieldTypeRequiredDefaultDescription
conditionsarrayConditions follow the standard Kubernetes condition contract.
conditions[].lastTransitionTimestring (date-time)✓lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
conditions[].messagestring (length 0-32768)✓message is a human readable message indicating details about the transition. This may be an empty string.
conditions[].observedGenerationinteger (int64) (>= 0)observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
conditions[].reasonstring (length 1-1024; pattern ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$)✓reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
conditions[].statusstring (one of: True, False, Unknown)✓status of the condition, one of True, False, Unknown.
conditions[].typestring (length 0-316; pattern ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$)✓type of condition in CamelCase or in foo.example.com/CamelCase.
forwardingSecretHashstring (pattern ^([a-f0-9]{16})?$)ForwardingSecretHash is podspec.ForwardingHash over this network's forwarding secret as the operator last read it. The pod builders stamp it onto every pod they create (podspec.LabelForwardingHash); a pod whose stamp differs is running on the previous secret. A read failure leaves the previous value in place.
moreThe pattern guards the pod label it is copied into unchecked.
onlinePlayersinteger (int32)OnlinePlayers is the sum of players across all server groups.
proxyGroupsinteger (int32)ProxyGroups is the number of ProxyGroups referencing this network.
serverGroupsinteger (int32)ServerGroups is the number of ServerGroups referencing this network.

ServerGroup

Scope: Namespaced    Plural: servergroups    Short names: mcgroup

ServerGroup is a group of interchangeable Minecraft servers.

What kubectl get shows

ColumnTypeJSONPath
Typestring.spec.type
Phasestring.status.phase
Readyinteger.status.readyReplicas
Replicasinteger.status.replicas
Playersinteger.status.onlinePlayers
Free Slotsinteger.status.freeSlots
Boostedinteger.status.boostedReplicas
Agedate.metadata.creationTimestamp

spec

ServerGroupSpec describes a group of Minecraft servers.

FieldTypeRequiredDefaultDescription
attributesmap[string]stringAttributes is what plugins are told about this group. See GroupAttributes. Editing it replaces and restarts nothing.
changeoverStageinteger (int32)ChangeoverStage orders this group's changeover against the network's other groups: a group waits while any group of a lower stage is still changing over. Groups of one stage change over together, within Network.spec.update.maxConcurrentChangeovers. Not for type OnDemand.
configOverlayobjectConfigOverlay names a ConfigMap whose keys are configuration files to merge over the rendered defaults — "server.properties", "paper-global.yml" or "paper-world-defaults.yml", in the target's own dialect. Any other key is refused.
morepaper-world-defaults.yml is written only when an overlay names it, and this is its only route: a mount under /data/config keeps the server from starting.

It outranks the rendered defaults and is outranked by the operationally critical fields.

A key Paper does not declare is refused, since Paper silently ignores it. The declared keys come from its captured default configuration, so a Paper bump can refuse a new key until that capture is updated. server.properties keys are not checked.
configOverlay.namestring (length >= 1)✓Name of the referenced object.
displayNamestring (length 0-64)DisplayName is what this group is called where a person reads it: a scoreboard, a chat message, a playtime key.
moreThe operator carries it and reads none of it. Empty, a plugin shows the group's own name.
drainobject{timeoutSeconds: 60}Drain bounds how long players may be moved off a server.
drain.timeoutSecondsinteger (int32) (>= 1)✓TimeoutSeconds is the upper bound for the drain.
enforcePlayableSlotsbooleanEnforcePlayableSlots refuses a login once a server holds as many players as its playable slots, except for players with the permission spawnery.join.full.<group>, who never take a seat. Read at runtime over the agent channel; changing it restarts no server.
envarrayEnv are extra environment variables for the server container, appended to the ones the operator sets. A name may not begin with SPAWNERY_.
moreJVM options go in JAVA_TOOL_OPTIONS. The entrypoint's own command-line flags win over the same option there, so a group can add a -D but not displace the heap and GC flags.

Editing it replaces every server of the group, like an image bump. A valueFrom reference is digested, not its value: rotating the Secret or ConfigMap reaches only new pods.
env[].namestring✓Name of the environment variable. May consist of any printable ASCII characters except '='.
env[].valuestringVariable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".
env[].valueFromEnvVarSource (Kubernetes, not expanded here)Source for the environment variable's value. Cannot be used if value is not empty.
extraFilesobjectExtraFiles names a volume whose tree is copied into this group's servers on every start, replacing what the server wrote there. A world does not belong in it. See ExtraFiles.
extraFiles.claimNamestring (length >= 1)ClaimName is a PersistentVolumeClaim in this object's own namespace.
moreIt must be ReadWriteMany: every pod of a group mounts it.
extraFiles.imagestring (length >= 1)Image names an OCI image whose filesystem root is what the claim's root would be. It is mounted read-only as an image volume and copied as a claim is, pulled with the pod's imagePullSecrets. A digest reference rolls the group whenever it changes; a tag does not.
extraFiles.pullPolicystring (one of: Always, IfNotPresent, Never)PullPolicy for Image. Empty means IfNotPresent.
extraPluginsobjectExtraPlugins names a volume whose plugins and their configuration are copied into this group's servers on every start. See ExtraPlugins.
extraPlugins.claimNamestring (length >= 1)ClaimName is a PersistentVolumeClaim in this object's own namespace.
moreIt must be ReadWriteMany, even for a single-replica group: a ReadWriteOnce claim would leave the second server Pending on a volume affinity error.
extraPlugins.imagestring (length >= 1)Image names an OCI image whose filesystem root is what the claim's root would be. It is mounted read-only as an image volume and copied as a claim is, pulled with the pod's imagePullSecrets. A digest reference rolls the group whenever it changes; a tag does not.
extraPlugins.pullPolicystring (one of: Always, IfNotPresent, Never)PullPolicy for Image. Empty means IfNotPresent.
failedRetentionSecondsinteger (int32) (>= 0)3600FailedRetentionSeconds is how long a Failed server is kept for diagnosis. A Pending pod of a Failed server is stopped at once, so it neither holds its requests on a node nor takes room that frees up ahead of the server that replaces it; a pod that comes up after its server failed is stopped as soon as another server of the group is Ready. The Server object stays for the retention.
finishedRetentionSecondsinteger (int32) (>= 0)300FinishedRetentionSeconds is how long a Finished server is kept.
imagestring (length >= 1)✓Image is the Paper base image. A digest reference is recommended.
joinPermissionobjectJoinPermission limits who may join this group's servers. A proxy routes a player around a group they may not join; the server refuses the login. On an OnDemand group only the proxy checks it. Agents older than 0.18.0 ignore the rule, so the game and proxy images must carry 0.18.0 or later.
joinPermission.modestring (one of: Required, DenyOnly)RequiredMode Required admits only players who hold the node. DenyOnly admits everyone except players for whom the node is explicitly set to false.
joinPermission.nodestring (length 0-128; pattern ^[a-z0-9_.-]+$)Node is the permission node. Empty means spawnery.join.<group name>.
maxInstancesinteger (int32) (>= 0)MaxInstances is how many members this group may have at once.
moreA fleet ceiling, not a per-player quota. Zero closes the group: new starts are refused and every world stays where it is. Required, with no default.
maxPlayersinteger (int32) (>= 1)✓MaxPlayers is the player capacity of a single server of this group.
memberResourcesobjectMemberResources lets a startServer call choose resources for one member, within these bounds. Without it a start may not set any value. Only for type OnDemand.
memberResources.maxmap[string]object✓Max bounds every request and every limit a start sets, and the group's own spec.resources combined with them. memory is required, cpu is optional; a resource not named here cannot be set at a start.
memberResources.nonHeapobject (pattern ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$)2GiNonHeap is what a member's JVM holds outside the heap. When a start sets a memory limit and no heap, the heap is the limit minus NonHeap.
mountsarrayMounts are extra ConfigMap, Secret and PersistentVolumeClaim mounts.
mounts[].configMapConfigMapVolumeSource (Kubernetes, not expanded here)ConfigMap source.
mounts[].mountPathstring (pattern ^/.*)✓MountPath is the absolute path inside the container.
mounts[].namestring (length >= 1)✓Name of the volume inside the pod.
mounts[].persistentVolumeClaimobjectPersistentVolumeClaim source. See MountClaim.
mounts[].persistentVolumeClaim.claimNamestring (length >= 1)✓ClaimName is a PersistentVolumeClaim in this object's own namespace.
mounts[].persistentVolumeClaim.writablebooleanWritable mounts the claim read-write. It defaults to false.
moreNothing coordinates writers: two groups that both write the same claim get what two processes writing one filesystem get.

Flipping it replaces the group's servers.
mounts[].secretSecretVolumeSource (Kubernetes, not expanded here)Secret source.
mounts[].subPathstringSubPath mounts one file or one subdirectory of the source instead of the whole of it, so that MountPath can be a file.
moreWithout it, a mount whose path names a file gets a *directory* there holding the source's keys as separate files.

**A ConfigMap or Secret mounted through subPath does not update** in a running pod; without subPath it does, eventually. No edit to a ConfigMap's contents rolls anything.
networkRefobject✓NetworkRef names the Network this group belongs to.
networkRef.namestring (length >= 1)✓Name of the referenced object.
playableSlotsinteger (int32)PlayableSlots is how many seats of each server count as capacity: what spareSlots, status.freeSlots and a connect to the group measure. Unset, every seat up to maxPlayers counts. A plugin can set its own server's figure at runtime, which wins over this one.
moremaxPlayers stays the limit a server enforces, so the seats between the two are room for players the group is not sized by, such as spectators -- unless enforcePlayableSlots makes this number the limit too.
replicasinteger (int32) (>= 0)Replicas is the fixed number of persistent servers. Ephemeral groups are sized by scaling instead.
moreLowering it takes the top ordinal whoever is on it. Its players are moved by the ordinary drain, and anyone still connected when spec.drain.timeoutSeconds passes is disconnected with the pod. Empty the ordinal first or raise spec.drain.timeoutSeconds beforehand.
resourcesResourceRequirements (Kubernetes, not expanded here)Resources overrides Network.spec.defaults.resources.
scalingobjectScaling configures slot-based scaling. Ephemeral only. Editing it replaces no server.
scaling.maxReplicasinteger (int32) (>= 1)✓MaxReplicas caps the number of servers.
scaling.minReplicasinteger (int32) (>= 0)✓MinReplicas is the number of servers kept running at all times.
scaling.scaleDownStabilizationSecondsinteger (int32) (>= 0)300ScaleDownStabilizationSeconds is how long a server must be empty before it is eligible for scale-down.
scaling.spareSlotsinteger (int32) (>= 0)✓SpareSlots is the number of free player slots kept available, counted in playable seats when the group sets playableSlots.
schedulingobjectScheduling overrides Network.spec.defaults.scheduling.
scheduling.affinityAffinity (Kubernetes, not expanded here)Affinity expresses scheduling preferences and constraints.
scheduling.nodeSelectormap[string]stringNodeSelector restricts pods to nodes carrying all these labels.
scheduling.tolerations[]Toleration (Kubernetes, not expanded here)Tolerations allow pods onto tainted nodes.
storageobjectStorage configures where members' worlds live: a claim per member, or the object store with backend ObjectStore. Persistent and OnDemand only.
storage.accessModesarray of string[ReadWriteOnce]AccessModes are immutable once set.
storage.annotationsmap[string]stringAnnotations are copied onto each data claim when it is created, e.g. for a volume autoresizer's per-claim ceiling. Existing claims are not changed.
storage.backendstring (one of: Claim, ObjectStore)Backend is where a member's world lives between runs. Claim, the default, keeps it on a PersistentVolumeClaim per member. ObjectStore keeps it in the object store configured for the operator (chart value worldSync) and gives /data from a directory on the node that runs the member; it needs type OnDemand and keep, and synchronises exactly what keep matches. It also needs spawnery's game images 0.24.3 or later, whose bootstrapper holds the server until the world is on disk and whose entrypoint leaves the node agent's files alone: an older or a custom image starts on a half-downloaded world and can corrupt it, or fails to resume a world at all. Size, storageClassName, accessModes and annotations are ignored under ObjectStore and stay valid for switching back. The backend may change either way; nothing is moved across.
storage.keeparray of string (at least 1 item(s))Keep lists the paths on the data claim, relative to /data, that survive a start. When set, each start first deletes everything on the claim that no entry matches, then renders and copies as before. Unset, everything is kept.
moreAn entry is a path whose segments may use * and ? (path.Match per segment). A matched directory is kept whole. Mount points, their parent directories and the root lost+found are never deleted. The start is refused when a path no entry keeps is, or holds, a level.dat* file, a region directory or an .mca file, unless extraFiles and extraPlugins ship every file and directory at and below it at the same path, which the copy then writes back, or a replace entry matches it.
storage.replacearray of string (at least 1 item(s))Replace lists paths on the data claim, relative to /data, that the sources own. A start deletes them like any other path keep does not match, without the check for worlds, and the copy after it writes back what extraFiles and extraPlugins ship now. Meant for world templates the server copies from and never loads, whose shipped form changes between releases. Same syntax as keep; where both match, keep wins. Needs keep.
storage.retentionobjectRetention keeps older generations of each member's world restorable, with the prune options of Proxmox Backup Server, applied in the order last, hourly, daily, weekly, monthly, yearly. Each counts only periods that hold a generation and skips a period an earlier option already kept one from. Periods are UTC. The current generation is always kept and counts as one of last. Unset, or all zero, a world keeps only its current generation. A change reaches each world at its next upload and restarts no member. Needs backend ObjectStore.
storage.retention.dailyinteger (int32) (>= 0)Daily keeps the newest generation of each of that many days.
storage.retention.hourlyinteger (int32) (>= 0)Hourly keeps the newest generation of each of that many hours.
storage.retention.lastinteger (int32) (>= 0)Last keeps the newest generations; the current one counts as one.
storage.retention.maxAgeDaysinteger (int32) (>= 1)MaxAgeDays drops an older generation, whatever the counts keep, once the generation that replaced it is older than that many days. The operator applies it hourly to worlds nobody plays. Unset, no limit.
storage.retention.monthlyinteger (int32) (>= 0)Monthly keeps the newest generation of each of that many months.
storage.retention.weeklyinteger (int32) (>= 0)Weekly keeps the newest generation of each of that many ISO weeks.
storage.retention.yearlyinteger (int32) (>= 0)Yearly keeps the newest generation of each of that many years.
storage.sizeobject (pattern ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$)✓Size of each new claim. Raising it grows existing claims up to the new size (needs allowVolumeExpansion on the StorageClass); lowering it changes only claims created afterwards and never shrinks one. A claim larger than this, grown by hand or by an autoresizer, is left alone.
storage.storageClassNamestringStorageClassName is immutable once set.
substitutionobjectSubstitution fills placeholders in the files the entrypoint copies from extraPlugins and extraFiles. See Substitution.
substitution.prefixstring (pattern ^[A-Z][A-Z0-9_]*$)✓
terminationGracePeriodSecondsinteger (int64) (>= 1)60TerminationGracePeriodSeconds is the time the pod gets to save its world.
typestring (one of: Ephemeral, Persistent, OnDemand)✓Type selects ephemeral, persistent or on-demand operation. Immutable.
updateobjectUpdate configures the rolling update. Ephemeral only.
update.maxStaleSecondsinteger (int32) (>= 0)0MaxStaleSeconds forces an active drain of stale servers after this many seconds. 0 means stale servers are never actively emptied.
update.maxUnavailableinteger (int32) (>= 1)1MaxUnavailable is how many servers may be draining or terminating at the same time because of a generation change.
update.minAvailableinteger (int32) (>= 1)MinAvailable is how many servers must stay joinable while the group changes over: Ready, registered, door open, and not on their way out, of either generation. The group builds one extra server at a time to keep it. Unset keeps no floor beyond one Ready server of the current generation.
update.strategystring (one of: RollingUpdate, WhenEmpty)RollingUpdateStrategy is how stale servers are replaced.

status

Read by observers; the operator writes it and reads nothing back from it.

FieldTypeRequiredDefaultDescription
boostedReplicasinteger (int32)BoostedReplicas is how much of this group's current floor comes from ScaleBoost objects rather than from spec.scaling.minReplicas.
changeoverstring (one of: , Waiting, Begun, Deferred)Changeover is this group's changeover as the network's budget sees it; written by its own reconcile and read by its siblings'.
conditionsarrayConditions follow the standard Kubernetes condition contract.
conditions[].lastTransitionTimestring (date-time)✓lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
conditions[].messagestring (length 0-32768)✓message is a human readable message indicating details about the transition. This may be an empty string.
conditions[].observedGenerationinteger (int64) (>= 0)observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
conditions[].reasonstring (length 1-1024; pattern ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$)✓reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
conditions[].statusstring (one of: True, False, Unknown)✓status of the condition, one of True, False, Unknown.
conditions[].typestring (length 0-316; pattern ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$)✓type of condition in CamelCase or in foo.example.com/CamelCase.
consecutiveFailuresinteger (int32)ConsecutiveFailures counts *rounds* in which at least one server failed to start, with no success since. One pass adds one however many servers it saw fail.
failureStreakKeystringFailureStreakKey identifies what the servers counted in consecutiveFailures started with: the desired pod hash, the configOverlay ConfigMap's resourceVersion and the spawnery.cloud/retry annotation. The streak is reset when any of them moves, and by nothing else. Opaque; compare it, never parse it.
freeSlotsinteger (int32)FreeSlots is the number of seats a proxy can send a player to right now: the free playable seats of servers that are Ready, in the proxies' routing tables, accepting joins, and rendered under the group's current spec. It is the scaler's input.
lastFailureAtstring (date-time)LastFailureAt is the newest status.failedAt this group has counted. It is what makes the count idempotent across resyncs, and the instant the backoff window runs from.
observedGenerationinteger (int64)ObservedGeneration is the spec generation this status was computed from.
onlinePlayersinteger (int32)OnlinePlayers is the sum of players across all ready servers.
phasestringPhase says whether players can join: Ready means the group's *floor* is met -- spec.replicas for a persistent group, spec.scaling.minReplicas for an ephemeral one -- not that every server the scaler decided to run is up. Compare readyReplicas against replicas for that, or see the Progressing condition.
pinnedReplicasinteger (int32)PinnedReplicas is the size an Exact ScaleBoost holds this group at, which can be 0. Absent while no pin holds.
pinnedUntilstring (date-time)PinnedUntil is when that pin ends. Absent without a pin, and for a pin without an end.
readyReplicasinteger (int32)ReadyReplicas is the number of servers in phase Ready.
replicasinteger (int32)Replicas is the number of Server objects owned by this group.

ProxyGroup

Scope: Namespaced    Plural: proxygroups    Short names: mcproxy

ProxyGroup is the Velocity layer of a network.

What kubectl get shows

ColumnTypeJSONPath
Phasestring.status.phase
Readyinteger.status.readyReplicas
Addressstring.status.address
Voicestring.status.voiceAddress
Playersinteger.status.connectedPlayers
Agedate.metadata.creationTimestamp

spec

ProxyGroupSpec describes the Velocity layer of a network.

FieldTypeRequiredDefaultDescription
attributesmap[string]stringAttributes is what plugins are told about this group. See GroupAttributes. Editing it replaces and restarts nothing.
changeoverStageinteger (int32)ChangeoverStage orders this group's changeover against the network's other groups: a group waits while any group of a lower stage is still changing over. Groups of one stage change over together, within Network.spec.update.maxConcurrentChangeovers.
configobjectConfig are the rendered Velocity settings.
config.motdstringMotd is shown in the server list. Editing it rolls the group's proxies.
config.onlineModebooleantrueOnlineMode is whether the proxy authenticates players with Mojang.
moreTurning it off lets any client connect under any name; the backends run online-mode=false and do not catch it. It is not the backends' proxies.velocity.online-mode, which stays true either way.

Editing it rolls the group's proxies.
config.playerLimitinteger (int32) (>= 1)PlayerLimit is the network-wide player limit of one proxy.
configOverlayobjectConfigOverlay names a ConfigMap whose only accepted key is "velocity.toml", a TOML fragment to merge over the rendered defaults. Any other key is refused.
moreIt outranks the rendered defaults and is outranked by the operationally critical fields.

A key Velocity does not declare is refused, since Velocity silently ignores it. The declared keys come from its captured default configuration, so a Velocity bump can refuse a new key until that capture is updated.
configOverlay.namestring (length >= 1)✓Name of the referenced object.
displayNamestring (length 0-64)DisplayName is what this group is called where a person reads it: a scoreboard, a chat message, a playtime key.
moreThe operator carries it and reads none of it. Empty, a plugin shows the group's own name.
drainobject{timeoutSeconds: 300}Drain bounds how long existing sessions may run out on proxy replacement.
moreIt reaches the pod as terminationGracePeriodSeconds, so editing it replaces every proxy of the group. A drain already in flight picks up the new deadline.
drain.timeoutSecondsinteger (int32) (>= 1)✓TimeoutSeconds is the upper bound for the drain.
envarrayEnv are extra environment variables for the proxy container, appended to the ones the operator sets. A name may not begin with SPAWNERY_. JVM options go in JAVA_TOOL_OPTIONS.
moreEditing it replaces every proxy of the group. A valueFrom reference is digested, not its value: rotating the Secret or ConfigMap reaches only new pods.
env[].namestring✓Name of the environment variable. May consist of any printable ASCII characters except '='.
env[].valuestringVariable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".
env[].valueFromEnvVarSource (Kubernetes, not expanded here)Source for the environment variable's value. Cannot be used if value is not empty.
exposeobject✓Expose makes the proxies reachable from outside the cluster.
expose.clusterIPobjectClusterIP configures type ClusterIP.
expose.clusterIP.addressstring (length >= 1)✓Address is what a player types. Required, because the operator cannot learn it from whatever routes to the Service.
moreGive "host:port" only when the entry point is not on 25565, the client's default.

Nothing checks that it resolves, that anything listens, or that it leads to this group's Service.
expose.hostPortobjectHostPort configures type HostPort.
morePod Security baseline and restricted both refuse a hostPort, so the group needs a namespace of its own with a relaxed label; the refusal is reported on Degraded. Whether the port is reachable from outside is still up to the host firewall or cluster network policy.
expose.hostPort.portinteger (int32) (1-65535)✓Port is bound on every node running a proxy pod. The kube-scheduler keeps at most one such pod per node, so replicas are capped by nodes.
expose.loadBalancerobjectLoadBalancer configures type LoadBalancer.
expose.loadBalancer.annotationsmap[string]stringAnnotations are copied onto the Service, e.g. for MetalLB pool selection.
expose.loadBalancer.externalTrafficPolicystringLocalExternalTrafficPolicy defaults to Local so the client IP survives — bans and rate limits depend on it.
expose.nodePortobjectNodePort configures type NodePort.
expose.nodePort.portinteger (int32) (1-65535)✓Port must lie inside the API server's service-node-port-range.
expose.typestring (one of: LoadBalancer, NodePort, HostPort, ClusterIP)✓Type selects the strategy.
extraFilesobjectExtraFiles names a volume whose tree is copied into this group's servers on every start, replacing what the server wrote there. See ExtraFiles.
extraFiles.claimNamestring (length >= 1)ClaimName is a PersistentVolumeClaim in this object's own namespace.
moreIt must be ReadWriteMany: every pod of a group mounts it.
extraFiles.imagestring (length >= 1)Image names an OCI image whose filesystem root is what the claim's root would be. It is mounted read-only as an image volume and copied as a claim is, pulled with the pod's imagePullSecrets. A digest reference rolls the group whenever it changes; a tag does not.
extraFiles.pullPolicystring (one of: Always, IfNotPresent, Never)PullPolicy for Image. Empty means IfNotPresent.
extraPluginsobjectExtraPlugins names a volume whose plugins and their configuration are copied into this group's servers on every start. See ExtraPlugins.
extraPlugins.claimNamestring (length >= 1)ClaimName is a PersistentVolumeClaim in this object's own namespace.
moreIt must be ReadWriteMany, even for a single-replica group: a ReadWriteOnce claim would leave the second server Pending on a volume affinity error.
extraPlugins.imagestring (length >= 1)Image names an OCI image whose filesystem root is what the claim's root would be. It is mounted read-only as an image volume and copied as a claim is, pulled with the pod's imagePullSecrets. A digest reference rolls the group whenever it changes; a tag does not.
extraPlugins.pullPolicystring (one of: Always, IfNotPresent, Never)PullPolicy for Image. Empty means IfNotPresent.
imagestring (length >= 1)✓Image is the Velocity base image. A digest reference is recommended.
mountsarrayMounts are extra ConfigMap, Secret and PersistentVolumeClaim mounts.
mounts[].configMapConfigMapVolumeSource (Kubernetes, not expanded here)ConfigMap source.
mounts[].mountPathstring (pattern ^/.*)✓MountPath is the absolute path inside the container.
mounts[].namestring (length >= 1)✓Name of the volume inside the pod.
mounts[].persistentVolumeClaimobjectPersistentVolumeClaim source. See MountClaim.
mounts[].persistentVolumeClaim.claimNamestring (length >= 1)✓ClaimName is a PersistentVolumeClaim in this object's own namespace.
mounts[].persistentVolumeClaim.writablebooleanWritable mounts the claim read-write. It defaults to false.
moreNothing coordinates writers: two groups that both write the same claim get what two processes writing one filesystem get.

Flipping it replaces the group's servers.
mounts[].secretSecretVolumeSource (Kubernetes, not expanded here)Secret source.
mounts[].subPathstringSubPath mounts one file or one subdirectory of the source instead of the whole of it, so that MountPath can be a file.
moreWithout it, a mount whose path names a file gets a *directory* there holding the source's keys as separate files.

**A ConfigMap or Secret mounted through subPath does not update** in a running pod; without subPath it does, eventually. No edit to a ConfigMap's contents rolls anything.
networkRefobject✓NetworkRef names the Network this group belongs to. Immutable.
networkRef.namestring (length >= 1)✓Name of the referenced object.
replicasinteger (int32) (>= 1)✓Replicas is the number of proxy pods.
resourcesResourceRequirements (Kubernetes, not expanded here)Resources overrides Network.spec.defaults.resources.
routingobject✓Routing configures the fallback groups.
routing.fallbackGroupsarray of string (at least 1 item(s))✓FallbackGroups is the ordered try-list on join and on drain.
schedulingobjectScheduling overrides Network.spec.defaults.scheduling.
scheduling.affinityAffinity (Kubernetes, not expanded here)Affinity expresses scheduling preferences and constraints.
scheduling.nodeSelectormap[string]stringNodeSelector restricts pods to nodes carrying all these labels.
scheduling.tolerations[]Toleration (Kubernetes, not expanded here)Tolerations allow pods onto tainted nodes.
substitutionobjectSubstitution fills placeholders in the files the entrypoint copies from extraPlugins and extraFiles. See Substitution.
substitution.prefixstring (pattern ^[A-Z][A-Z0-9_]*$)✓
updateobjectUpdate bounds how long a draining proxy may wait for its players.
update.maxStaleSecondsinteger (int32) (>= 0)MaxStaleSeconds disconnects the players left on a draining proxy after this many seconds. 0, the default, means a drain waits for its players: the proxy takes no new connections and stops once empty.
update.transferobjectTransfer moves players to another proxy instead of waiting for them.
update.transfer.forceAfterSecondsinteger (int32) (>= 0)ForceAfterSeconds is how long a leaving proxy waits before it transfers players who have not changed server, counted from when its agent first sees it leaving. Default 120; keep it below drain.timeoutSeconds and any maxStaleSeconds.
update.transfer.forceGroupsarray of stringForceGroups limits the forced transfer to players on servers of these server groups; everyone else moves only when they change server. Empty forces in every group.
voiceobjectVoice changes how voice chat leaves this group. Without it, voice takes the game's way out with the same port number, as UDP. Only read while the Network has spec.voice.
voice.exposeobjectExpose gives voice a way out of its own, for an entry point that carries no UDP. HostPort binds a UDP host port; the other types get a Service named <group>-voice with only the UDP port.
voice.expose.clusterIPobjectClusterIP configures type ClusterIP.
voice.expose.clusterIP.addressstring (length >= 1)✓Address is what a player types. Required, because the operator cannot learn it from whatever routes to the Service.
moreGive "host:port" only when the entry point is not on 25565, the client's default.

Nothing checks that it resolves, that anything listens, or that it leads to this group's Service.
voice.expose.hostPortobjectHostPort configures type HostPort.
morePod Security baseline and restricted both refuse a hostPort, so the group needs a namespace of its own with a relaxed label; the refusal is reported on Degraded. Whether the port is reachable from outside is still up to the host firewall or cluster network policy.
voice.expose.hostPort.portinteger (int32) (1-65535)✓Port is bound on every node running a proxy pod. The kube-scheduler keeps at most one such pod per node, so replicas are capped by nodes.
voice.expose.loadBalancerobjectLoadBalancer configures type LoadBalancer.
voice.expose.loadBalancer.annotationsmap[string]stringAnnotations are copied onto the Service, e.g. for MetalLB pool selection.
voice.expose.loadBalancer.externalTrafficPolicystringLocalExternalTrafficPolicy defaults to Local so the client IP survives — bans and rate limits depend on it.
voice.expose.nodePortobjectNodePort configures type NodePort.
voice.expose.nodePort.portinteger (int32) (1-65535)✓Port must lie inside the API server's service-node-port-range.
voice.expose.typestring (one of: LoadBalancer, NodePort, HostPort, ClusterIP)✓Type selects the strategy.

status

Read by observers; the operator writes it and reads nothing back from it.

FieldTypeRequiredDefaultDescription
addressstringAddress is where players connect.
changeoverstring (one of: , Waiting, Begun, Deferred)Changeover is this group's changeover as the network's budget sees it; written by its own reconcile and read by its siblings'.
conditionsarrayConditions follow the standard Kubernetes condition contract.
conditions[].lastTransitionTimestring (date-time)✓lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
conditions[].messagestring (length 0-32768)✓message is a human readable message indicating details about the transition. This may be an empty string.
conditions[].observedGenerationinteger (int64) (>= 0)observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
conditions[].reasonstring (length 1-1024; pattern ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$)✓reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
conditions[].statusstring (one of: True, False, Unknown)✓status of the condition, one of True, False, Unknown.
conditions[].typestring (length 0-316; pattern ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$)✓type of condition in CamelCase or in foo.example.com/CamelCase.
connectedPlayersinteger (int32)ConnectedPlayers is the sum of players across all proxies.
observedGenerationinteger (int64)ObservedGeneration is the spec generation this status was computed from. It advances on a refused pass too; check Degraded beside it.
phasestringPhase is derived from the proxy pods and conditions.
readyReplicasinteger (int32)ReadyReplicas is the number of proxies that passed the ready gate.
voiceAddressstringVoiceAddress is where voice chat reaches this group, empty while voice is off or no proxy is ready. When voice takes the game's way out it is the game address's host with the UDP port; clients are told the port only and keep the host they typed.

Server

Scope: Namespaced    Plural: servers    Short names: mcsrv

Server is a single running Minecraft server instance.

What kubectl get shows

ColumnTypeJSONPath
Groupstring.spec.groupRef.name
Phasestring.status.phase
Playersinteger.status.players
Slotsinteger.status.slots
Registeredboolean.status.registered
Agedate.metadata.creationTimestamp

spec

ServerSpec describes one running Minecraft server instance. It is created and owned by a ServerGroup and is not meant to be edited by hand.

FieldTypeRequiredDefaultDescription
forceStopbooleanForceStop kills this server's pod at once, with no drain and a grace period of one second: its players lose their connection and the world loses whatever it had not saved. Set by the agent endpoint's force-stop request, which only a proxy may send.
groupGenerationinteger (int64)GroupGeneration is the metadata.generation of the group at creation time. A server whose value is behind the group's is stale.
groupRefobject✓GroupRef names the owning ServerGroup.
groupRef.namestring (length >= 1)✓Name of the referenced object.
heapobject (pattern ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$)Heap is the JVM heap resolved for this member. Set at creation, never changed. Unset: the pod keeps the group's heap behaviour.
holdbooleanHold keeps this server against every automatic removal: a rolling update does not retire it, and neither the demand rule nor a lowered maxReplicas deletes it. It stays until it ends by itself. Set by the agent endpoint's unretire request; a node drain still moves it, and a retire request still retires it.
keystringKey is the caller's name for the world this member carries, and it is set for a member of an OnDemand group and for no other server. Like Ordinal, it lets a Server say what kind of member it is once its group is gone.
numberinteger (int32) (>= 0)Number is which of its group's servers this is, counted the way a person counts: the second hub is 2. The group assigns it once, at creation, and it is given out again only after this server is gone.
moreA persistent server's Number equals its Ordinal. Zero means unnumbered: a server older than this field, or ordinal zero of a persistent group. A reader then falls back to the server's name.
ordinalinteger (int32) (>= 0)Ordinal is the stable index of a persistent server. Unset for ephemeral servers, whose names carry a random suffix instead.
podHashstringPodHash is podspec.DesiredServerHash at the moment this server was created: a digest of everything the operator would render for it. The group compares it against a freshly computed one to decide whether this ordinal is running the current spec.
moreEmpty means adopt, never stale: the group stamps the current hash onto such a server and orders no takedown.
resourcesobjectResources is what a startServer call resolved for this member, with the group's values filled in where the call left them out. Set at creation, never changed. Unset: the pod takes the group's resources.
resources.claimsarrayClaims lists the names of resources, defined in spec.resourceClaims, that are used by this container.
moreThis field depends on the DynamicResourceAllocation feature gate.

This field is immutable. It can only be set for containers.
resources.claims[].namestring✓Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container.
resources.claims[].requeststringRequest is the name chosen for a request in the referenced claim. If empty, everything from the claim is made available, otherwise only the result of this request.
resources.limitsmap[string]objectLimits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
resources.requestsmap[string]objectRequests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
retirebooleanRetire asks this server to stop taking joins and empty out, without its players being moved. The ServerGroup controller sets it during a rolling update, and an administrator through the agent endpoint's retire request. A server counts against spec.update.maxUnavailable while this is true.

status

Read by observers; the operator writes it and reads nothing back from it.

FieldTypeRequiredDefaultDescription
addressstringAddress is the pod IP and port the proxies connect to.
conditionsarrayConditions follow the standard Kubernetes condition contract. While the scheduler cannot place the server's pod, Ready is False with reason PodUnschedulable and the scheduler's message; the group's Progressing condition names the server.
conditions[].lastTransitionTimestring (date-time)✓lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
conditions[].messagestring (length 0-32768)✓message is a human readable message indicating details about the transition. This may be an empty string.
conditions[].observedGenerationinteger (int64) (>= 0)observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
conditions[].reasonstring (length 1-1024; pattern ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$)✓reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
conditions[].statusstring (one of: True, False, Unknown)✓status of the condition, one of True, False, Unknown.
conditions[].typestring (length 0-316; pattern ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$)✓type of condition in CamelCase or in foo.example.com/CamelCase.
drainStartedAtstring (date-time)DrainStartedAt is when the server entered phase Draining. Drives the drain deadline.
failedAtstring (date-time)FailedAt is when the server entered phase Failed. Drives the retention.
phasestringPhase is the state machine position: Pending, Starting, Ready, Draining, Terminating or Failed.
playableSlotsinteger (int32)PlayableSlots is how many of Slots count as capacity: the plugin's figure, else the group's spec.playableSlots, else Slots.
playersinteger (int32)Players is the last reported player count, throttled to protect etcd.
playersUpdatedAtstring (date-time)PlayersUpdatedAt is when Players was last reported by the agent. Counts older than twice the report interval are treated as occupied.
podNamestringPodName is the pod backing this server. Set once the pod was created and never reused for a different pod.
podUIDstringPodUID identifies the pod behind PodName, and so one run of this server apart from the next one under the same name, which a persistent server keeps across restarts. Not cleared when the pod goes.
readinessLossesinteger (int32)ReadinessLosses counts how often this server fell out of Ready. Past the threshold the server is considered broken rather than flapping.
readySincestring (date-time)ReadySince is when the server last entered phase Ready. Drives the reset of the readiness-loss counter.
registeredbooleanRegistered reports whether the proxies currently know this server.
retiringSincestring (date-time)RetiringSince is when the server entered phase Retiring. It drives spec.update.maxStaleSeconds and nothing else — what marks a server as one this update made unavailable is spec.retire.
roundEndedAtstring (date-time)RoundEndedAt is when the server said its round was over. Nil for one that never did. Stamped while the server still runs, so it survives an operator restart.
slotsinteger (int32)Slots is the player capacity reported by the agent.
startedAtstring (date-time)StartedAt is when this server last began trying to become playable: the first pass that accepted it, then the pod creation, and then every entry into phase Starting. It drives the startup deadline, which therefore bounds the current attempt rather than the age of the pod, and also fails a Server whose pod is never created.
storageResizeErrorstringStorageResizeError names why the last resize of this server's claim failed, or is empty when none did: this operator's patch refused by the API server (usually allowVolumeExpansion: false), or a driver failure from the claim's ControllerResizeError or NodeResizeError condition.
storageResizePendingbooleanStorageResizePending is true while this server's claim carries the FileSystemResizePending condition: the CSI driver has grown the volume and needs the pod restarted before the filesystem follows. Most drivers expand online and never set it.
wasRegisteredbooleanWasRegistered is true once this server has been registered with the proxies during the life of its current pod. A server that fell back to Starting may still have players connected.

ScaleBoost

Scope: Namespaced    Plural: scaleboosts    Short names: boost

ScaleBoost is extra capacity for a group, for a while.

moreIt is its own object rather than a field on the group because the operator cannot write a ServerGroup's spec, and under GitOps that spec belongs to a file anyway.

It adds to the group's floor and never to its ceiling: spec.scaling. maxReplicas still binds. Its effect shows on the group as status.boostedReplicas.

What kubectl get shows

ColumnTypeJSONPath
Groupstring.spec.groupRef.name
Modestring.spec.mode
Replicasinteger.spec.replicas
Expiresdate.spec.expiresAt
Agedate.metadata.creationTimestamp

spec

ScaleBoostSpec is extra capacity for one group, for a while.

FieldTypeRequiredDefaultDescription
expiresAtstring (date-time)ExpiresAt is when this boost stops counting. A boost without one never expires; the tools that create boosts supply a default.
groupRefobject✓GroupRef names the ServerGroup this adds capacity to.
groupRef.namestring (length >= 1)✓Name of the referenced object.
modestring (one of: Add, Exact)AddMode Add raises the group's floor by Replicas; boosts on one group add up. Mode Exact holds the group at exactly Replicas servers, 0 included: the group's floor and ceiling both become that number, Add boosts stop counting, and of several Exact boosts the newest wins. Neither lifts spec.scaling.maxReplicas.
replicasinteger (int32) (>= 0)✓Replicas is how many servers to add (Add, at least 1) or to hold the group at (Exact, at least 0).