plugin-agones when you run the Grounds proxy in front of
Agones-managed gameservers. The plugin combines Kubernetes-driven discovery with optional static
backends and routes joining players to a lobby.
Requirements
For Agones discovery, the plugin expects the following environment:GROUNDS_AGONES_NAMESPACEorPOD_NAMESPACEidentifies the namespace to query; the fallback isgamesGROUNDS_AGONES_ADDRESS_TYPEmust exactly match a type reported inGameServer.status.addresses; common values arePodIP,ExternalIP,InternalIP, andHostname, withPodIPas the defaultGROUNDS_AGONES_PORTselects the backend TCP port; the default is25565- the pod can list Agones
gameservers.agones.devresources in that namespace - the pod can read gameserver pods when a
GameServerstatus does not contain the configured address type
GameServer.
Without the sidecar, backend discovery and routing still work; only proxy self-state management is
disabled.
What the Plugin Provides
Once the plugin starts, it runs these responsibilities on the proxy:DiscoveryServiceregisters configured static backendsDiscoveryServicepolls Kubernetes every two seconds and keeps Velocity’s server registry in sync with the running gameserversGameServerStateManagersyncs the proxy’s own Agones state (ReadyorAllocated) with the connected player count when an SDK sidecar is present
grounds/server-type label and cached for routing and the /agones command.
Gameserver Requirements
For a gameserver pod to appear in Velocity, it must satisfy the discovery contract:1
Deploy into the configured discovery namespace
The discovery query is scoped to
GROUNDS_AGONES_NAMESPACE or POD_NAMESPACE; if neither is set,
it defaults to games. Gameservers outside that namespace are ignored.2
Label the GameServer with a role
Set
grounds/server-type to lobby, game, or match on the GameServer resource metadata.
Unlabeled gameservers are skipped. This example uses the fallback namespace games; set
metadata.namespace to the configured discovery namespace when it differs.3
Expose Minecraft on the configured port
The proxy reads the address whose type matches
GROUNDS_AGONES_ADDRESS_TYPE and connects to
GROUNDS_AGONES_PORT. These default to PodIP and 25565. If the selected address is missing from
GameServer.status.addresses, the plugin falls back to the gameserver pod IP when Kubernetes
exposes one; otherwise the gameserver is skipped. The plugin does not honor Agones allocated ports,
so the gameserver must listen on the configured fixed port inside the pod.4
Reach a running Agones state
Only gameservers in
Ready, Allocated, or Reserved state are registered. Transitions out of
these states unregister the server on the next poll.If your gameserver follows all four steps, it appears in
/agones on the proxy within two seconds
of reaching a running state.Static Servers
Use a static server when a backend runs as a regular KubernetesDeployment or outside Agones.
In production, a dedicated Helm chart owns one immutable, versioned ConfigMap for every Java
Velocity proxy. The ConfigMap is retained with helm.sh/resource-policy: keep, allowing the old
and new versions to overlap during a rollout:
velocity-static-servers-v2), update every proxy reference, and roll out all proxies.
Only garbage-collect the retained old version after the rollout completes. Do not set independent
literal values on production proxies.
For local development, set GROUNDS_STATIC_SERVERS directly. The value is a comma-separated list
of name=host:port entries:
1 and 65535. Server names are
case-insensitive; duplicates such as BuildServer and buildserver fail configuration.
Static servers have the role static. They appear in Velocity’s /server command and in
/agones, but they are never selected as login lobbies. If a static server and an Agones
GameServer use the same name, the static server takes precedence.
After all proxies restart, use
/server buildserver to connect to the example backend. The player
still needs Velocity’s velocity.command.server permission.Drain Routing
During an automatic drain, the proxy defers a move only for players on a backend whose current role isgame or match. Players on static, lobby, an unknown role, or a backend with no role
are moved immediately.
For an automatically drained Java player who is currently on a static backend, the source proxy
signs a short-lived, namespaced Minecraft transfer cookie with HMAC using the existing shared
Velocity forwarding secret. It requests and receives an exact client echo of that cookie before it
initiates the transfer. The target proxy validates the cookie signature and TTL, then uses the named
backend directly only if it is still registered with the static role. If the echo does not arrive
within one second, or the cookie is missing, expired, invalid, or names an unknown backend, the
transfer falls back to normal routing and therefore to a lobby when one is available. This requires
Java 1.20.5 or later, matching the existing host-transfer minimum. Bedrock drain remains disabled.
The preservation applies only to automatic drain. Generic /region moves and other manual proxy
moves do not preserve the current backend.
Lobby Routing
The plugin treatsgrounds/server-type=lobby as the entry point for new connections.
If another plugin already sets an initial server (for example through a rejoin feature), the
discovery plugin does not override that choice.
“First registered lobby” is a deterministic but unordered pick from Velocity’s internal map. If you
need priority routing, plan to pair this with party-aware routing later.
The /agones Command
The plugin registers an operator command for inspecting the current proxy state.
The command prints every registered server with its role, address, and connected player count.
Roles include
lobby, game, match, and static; registrations without a known role appear as
unknown.
Example output:
State Sync on the Proxy
The proxy itself runs as an Agones gameserver, so the plugin also manages its own state:- the plugin calls
Allocatewhen the first player logs in - the plugin calls
Readywhen the last player disconnects - a 10 second fallback loop reconciles the state in case events are missed
Failure Modes
Kubernetes client fails to initialize
Kubernetes client fails to initialize
If the plugin cannot build a Kubernetes client at startup, Agones discovery stays disabled for the
process lifetime. The plugin still removes the exact image placeholders, registers
GROUNDS_STATIC_SERVERS, and installs the routing listeners.Agones sidecar unreachable
Agones sidecar unreachable
The proxy-side state sync logs the failure and keeps trying on its fallback loop. Discovery and
routing continue to work because they depend on the Kubernetes API, not on the sidecar.
Gameserver missing a PodIP
Gameserver missing a PodIP
If the configured address type is missing from
GameServer.status.addresses, the plugin reads the
same-named pod and uses its pod IP. If neither source provides an address, the server is skipped and
retried during the next poll.Static server configuration is invalid
Static server configuration is invalid
The plugin rejects malformed entries and duplicate names during initialization. Correct
GROUNDS_STATIC_SERVERS and restart the proxy. An absent or blank value disables static
registration.Gameserver missing the server-type label
Gameserver missing the server-type label
Unlabeled gameservers are silently skipped. Check the
GameServer resource if you expect it to
appear and it does not.Next Steps
- Read Paper or Minestom to set up gameservers that the Velocity proxy will discover.
- Review Agones Integration for the shared discovery contract.
