Installing the operator¶
The Helm chart is the only way the operator installs.
Installing¶
helm install spawnery oci://ghcr.io/spawnery/charts/spawnery \
--version 0.34.0 --namespace spawnery-system --create-namespace
The chart is an OCI artefact, so there is no helm repo add: an OCI
reference is the whole address.
--version is the chart's own number and not the release tag; the two move
apart. Each GitHub Release body prints the install line with the right one
already in it.
From a checkout, which is what hack/e2e.sh and every local install do:
helm install spawnery charts/spawnery --namespace spawnery-system --create-namespace
--create-namespace is not optional in practice. The chart templates no
Namespace object of its own, so without the flag helm install refuses
immediately with namespaces "spawnery-system" not found.
Choosing a game namespace¶
A game namespace is one trust domain. Anyone who may create a pod in it has
access to that network, by two routes that do not depend on each other: they
may mount the Network's forwarding secret, because any pod may mount any
Secret in its own namespace, and they may wear the labels the operator's
NetworkPolicy admits, because a pod's labels are chosen by whoever creates
it. Both were measured on 2026-08-21 against Cilium: a pod labelled as a
proxy reached a backend on 25565, and an unlabelled pod read the secret.
No policy the operator could write would close that, and Network boundaries carries why, along with what the policy does defend.
Do not share a game namespace with workloads you would not trust with that
network. Give each Network a namespace of its own, and treat the right
to create pods in it as the right that it is.
The one manual step this chart cannot make¶
config/rbac/forwarding-secret-reader.yaml grants the operator's
ServiceAccount secrets: get in a game namespace: the namespace holding a
Network, not the namespace the operator runs in. Apply it once per game
namespace, once the chart is installed and the namespace exists:
kubectl apply -n <game-namespace> -f config/rbac/forwarding-secret-reader.yaml
It sits outside the chart because a chart installed once cannot know the game namespaces an operator will create later.
For the namespaces you already know, networkNamespaces does it for you, and
does two things this file cannot: it restricts the grant by
resourceNames to that network's own secrets, and it puts the RoleBinding's
subject in the release's own namespace. It renders nothing by default, so a
namespace nobody listed still needs the manual step. helm install prints
that reminder from NOTES.txt; nothing checks the grant was ever applied.
The RoleBinding in config/rbac/forwarding-secret-reader.yaml hard-codes
this chart's documented default namespace, spawnery-system, as its subject:
subjects:
- kind: ServiceAccount
name: spawnery-operator
namespace: spawnery-system
Installed into any namespace other than spawnery-system, that namespace
has to be changed to the real one before the file is applied. Get it wrong and the
failure will not say "namespace": the Network reports that it could not read
its forwarding secret and names the secret and the kubectl apply line
above, never the RoleBinding subject that is wrong. The operator logs the API
server's refusal once, naming the ServiceAccount it really runs as, and that
line is where the mismatch is visible instead of deducible. Until it is
fixed, that namespace's Network can never detect a rotation of its
forwarding secret, and its groups keep scheduling normally with nothing on
their status to show for it.
The values¶
Every key is in Chart values, generated from
charts/spawnery/values.schema.json by make manifests, so it cannot drift.
One thing that page does not carry: replicas and imagePullSecrets are
deliberately not values. readyz hangs off the leader lock, so a second
replica never becomes ready. The Deployment hard-codes 1 and strategy:
Recreate, and a knob whose only valid setting is 1 is a trap. There is no
imagePullSecrets because all three images are public.
The /cloud permissions¶
The agents register a /cloud command on every Paper server and Velocity
proxy, and nobody holds any of its permissions by default, so right after
installing it answers "unknown command" to every player. That is the safe
state, not a broken install; The /cloud
command covers the seven nodes and what each
opens.
Uninstalling¶
helm uninstall spawnery --namespace <namespace>
removes the Deployment, Service, NetworkPolicy and RBAC objects this chart
manages, but leaves the four CRDs standing (each carries
helm.sh/resource-policy: keep) and therefore every Network,
ServerGroup, ProxyGroup and Server object with them. An uninstall
removes the operator that reconciles them, not the objects it managed.
A full removal needs a second, manual step:
kubectl delete crd networks.spawnery.cloud proxygroups.spawnery.cloud \
servergroups.spawnery.cloud servers.spawnery.cloud
Deleting a CRD deletes every object of that kind immediately, with no
finalizer and no drain, and owner-reference garbage collection cascades into
every Pod, Service and ConfigMap it created. Persistent worlds are the
deliberate exception: every PersistentVolumeClaim carries no owner reference at all, precisely so
a world outlives its Server, its group, and an operator who deletes the
wrong object. Reclaiming an orphaned claim afterwards is a manual kubectl
act; see Claims, and why they outlive their
servers.