Skip to main content

Overview

Agones provides four port allocation policies that control how ports are assigned to game servers: Dynamic, Static, Passthrough, and None. Each policy serves different use cases and has distinct performance and operational characteristics.

Port Policies

From the Agones source code (pkg/apis/agones/v1/gameserver.go:78-92):
// PortPolicy is the port policy for the GameServer
type PortPolicy string

const (
    // Static PortPolicy means that the user defines the hostPort to be used
    // in the configuration.
    Static PortPolicy = "Static"
    // Dynamic PortPolicy means that the system will choose an open
    // port for the GameServer in question
    Dynamic PortPolicy = "Dynamic"
    // Passthrough dynamically sets the `containerPort` to the same value as the dynamically selected hostPort.
    // This will mean that users will need to lookup what port has been opened through the server side SDK.
    Passthrough PortPolicy = "Passthrough"
    // None means the `hostPort` is ignored and if defined, the `containerPort` (optional) is used to set the port on the GameServer instance.
    None PortPolicy = "None"
)

Dynamic Port Policy

How It Works

The default policy where Agones automatically allocates an available host port from the configured range:
  1. Port allocator finds an unused port in the range (default: 7000-8000)
  2. Assigns the port as hostPort
  3. You define a fixed containerPort in your GameServer spec
  4. Traffic to hostPort forwards to containerPort

Configuration

apiVersion: agones.dev/v1
kind: GameServer
metadata:
  name: dynamic-gameserver
spec:
  ports:
  - name: default
    portPolicy: Dynamic  # System allocates hostPort
    containerPort: 7654  # Your game server listens here
    protocol: UDP
  template:
    spec:
      containers:
      - name: game-server
        image: my-game:latest

After Allocation

status:
  ports:
  - name: default
    port: 7234  # Dynamically allocated hostPort
  state: Ready
  address: 203.0.113.42
  nodeName: game-node-1
Clients connect to 203.0.113.42:7234, which forwards to container port 7654.

Implementation

From pkg/portallocator/portallocator.go:178-283, the allocator manages dynamic ports:
// Allocate assigns a port to the GameServer and returns it.
func (pa *portRangeAllocator) Allocate(gs *agonesv1.GameServer) *agonesv1.GameServer {
    pa.mutex.Lock()
    defer pa.mutex.Unlock()

    findOpenPorts := func(amount int) []pn {
        var ports []pn
        if amount <= 0 {
            return ports
        }
        for _, n := range pa.portAllocations {
            for p, taken := range n {
                if !taken {
                    ports = append(ports, pn{pa: n, port: p})
                    if len(ports) == amount {
                        return ports
                    }
                }
            }
        }
        return ports
    }
    // ... allocates and marks ports as taken
}

Use Cases

  • Most common use case
  • Standard multiplayer game servers
  • Multiple game server instances per node
  • When you control the client connection logic

Advantages

  • No port conflicts
  • Maximum density per node
  • Simple configuration
  • Automatic port management

Disadvantages

  • Requires port range configuration
  • Clients must query allocated port
  • Small allocation overhead

Static Port Policy

How It Works

You manually specify both hostPort and containerPort:
  1. You choose the exact hostPort to use
  2. Agones does not manage port allocation
  3. You are responsible for avoiding conflicts
  4. Best for predictable, well-known ports

Configuration

apiVersion: agones.dev/v1
kind: GameServer
metadata:
  name: static-gameserver
spec:
  ports:
  - name: default
    portPolicy: Static
    hostPort: 7777      # Fixed, must be available
    containerPort: 7654
    protocol: UDP
  template:
    spec:
      containers:
      - name: game-server
        image: my-game:latest

After Creation

status:
  ports:
  - name: default
    port: 7777  # Exactly as specified
  state: Ready
  address: 203.0.113.42
Clients always connect to 203.0.113.42:7777.

Use Cases

  • Legacy games expecting specific ports
  • Single game server per node
  • Development/testing with predictable ports
  • Integration with external systems requiring fixed ports

Advantages

  • Predictable port numbers
  • No dynamic allocation overhead
  • Simpler client configuration
  • No port range limits

Disadvantages

  • Manual port management required
  • Risk of port conflicts
  • Limited scalability (one port per node)
  • Not suitable for high-density deployments
Static ports limit you to one GameServer per port per node. Ensure you have enough nodes for your desired scale.

Passthrough Port Policy

How It Works

Combines dynamic allocation with container port binding:
  1. Agones allocates a dynamic hostPort
  2. Automatically sets containerPort to match hostPort
  3. Your game server must query the SDK to discover its port
  4. Game server binds to the assigned port

Configuration

apiVersion: agones.dev/v1
kind: GameServer
metadata:
  name: passthrough-gameserver
spec:
  ports:
  - name: default
    portPolicy: Passthrough  # containerPort = hostPort (dynamic)
    # No containerPort specified - will be set dynamically
    protocol: UDP
  template:
    spec:
      containers:
      - name: game-server
        image: my-game:latest

After Allocation

status:
  ports:
  - name: default
    port: 7234  # Both hostPort and containerPort are 7234
  state: Ready

Game Server Implementation

import "agones.dev/agones/sdks/go"

sdk, err := sdk.NewSDK()
if err != nil {
    log.Fatal(err)
}

// Get the allocated port
gs, err := sdk.GameServer()
if err != nil {
    log.Fatal(err)
}

port := gs.Status.Ports[0].Port  // The assigned port

// Bind to the assigned port
addr := fmt.Sprintf("0.0.0.0:%d", port)
listener, err := net.Listen("tcp", addr)

Implementation Details

From pkg/portallocator/portallocator.go:243-245:
if p.PortPolicy == agonesv1.Passthrough {
    gs.Spec.Ports[i].ContainerPort = a.port
}
The allocator sets the containerPort to match the dynamically allocated hostPort.

Use Cases

  • Game servers that need to know their external port
  • When game server advertises its port to matchmaking
  • Peer-to-peer game architectures
  • WebRTC or similar protocols

Advantages

  • No port conflicts
  • Game server knows its external port
  • No port forwarding translation
  • Useful for NAT traversal scenarios

Disadvantages

  • Game server must query SDK
  • Cannot bind to port before SDK initialization
  • Slightly more complex game server code

None Port Policy

How It Works

Beta feature (FeaturePortPolicyNone) that bypasses host port mapping entirely:
  1. No hostPort is assigned
  2. Only containerPort is used (optional)
  3. Clients connect directly to pod IP
  4. Bypasses kube-proxy and iptables

Configuration

apiVersion: agones.dev/v1
kind: GameServer
metadata:
  name: none-gameserver
spec:
  ports:
  - name: default
    portPolicy: None
    containerPort: 7654  # Optional, for documentation
    protocol: UDP
  template:
    spec:
      containers:
      - name: game-server
        image: my-game:latest

After Creation

status:
  ports:
  - name: default
    port: 7654  # containerPort, not a hostPort
  state: Ready
  address: 10.244.1.42  # Pod IP, not node IP
  nodeName: game-node-1
Clients connect directly to pod IP: 10.244.1.42:7654.

Use Cases

  • Ultra-low latency requirements
  • Private clusters with direct pod network access
  • Service mesh integration
  • Development environments

Advantages

  • Lowest possible latency (no kube-proxy)
  • No port allocation overhead
  • Direct pod-to-pod communication
  • No host port limits

Disadvantages

  • Requires pod network accessibility
  • Not suitable for public internet
  • Limited to certain network plugins
  • Beta feature, may change
PortPolicy None requires clients to access the pod network directly. This typically only works within the same cluster or with specialized networking.

Port Policy Comparison

PolicyHost PortContainer PortPort RangeClient Connects ToBest For
DynamicAuto-assignedFixedRequiredNode IP:HostPortStandard use
StaticUser-definedFixedNot neededNode IP:HostPortLegacy/single server
PassthroughAuto-assignedSame as HostPortRequiredNode IP:HostPortNAT traversal
NoneNot usedOptionalNot neededPod IP:ContainerPortLow latency

Multi-Port Configuration

You can mix policies in a single GameServer:
apiVersion: agones.dev/v1
kind: GameServer
spec:
  ports:
  - name: game
    portPolicy: Dynamic
    containerPort: 7654
    protocol: UDP
  - name: query
    portPolicy: Static
    hostPort: 27015
    containerPort: 27015
    protocol: TCP
  - name: rcon
    portPolicy: None
    containerPort: 27016
    protocol: TCP

Port Ranges

Default Range

helm install agones agones/agones \
  --set agones.controller.portRange=7000-8000
This provides 1000 ports for Dynamic and Passthrough policies.

Multiple Named Ranges

With the FeaturePortRanges feature gate:
ports:
- name: game
  portPolicy: Dynamic
  range: default  # Uses 7000-8000
  containerPort: 7654
- name: voice
  portPolicy: Dynamic
  range: voice    # Uses custom range (configured separately)
  containerPort: 8654

Calculating Capacity

Max GameServers per Node = (MaxPort - MinPort + 1) / Ports per GameServer

Example:
- Range: 7000-8000 (1001 ports)
- Ports per GameServer: 1
- Max per Node: 1001 GameServers

Example with 2 ports:
- Range: 7000-8000 (1001 ports)  
- Ports per GameServer: 2
- Max per Node: 500 GameServers

Protocol Support

TCPUDP Protocol

Agones supports dual TCP+UDP on the same port:
ports:
- name: dual
  portPolicy: Dynamic
  containerPort: 7654
  protocol: TCPUDP  # Allocates for both TCP and UDP
From pkg/portallocator/portallocator.go:247-267:
// create a port for TCP when using TCPUDP protocol
if p.Protocol == agonesv1.ProtocolTCPUDP {
    var duplicate = p
    duplicate.HostPort = a.port

    if duplicate.PortPolicy == agonesv1.Passthrough {
        duplicate.ContainerPort = a.port
    }

    extraPorts = append(extraPorts, duplicate)

    gs.Spec.Ports[i].Name = p.Name + "-tcp"
    gs.Spec.Ports[i].Protocol = corev1.ProtocolTCP
}

// create the UDP port when using TCPUDP protocol
for _, p := range extraPorts {
    p.Name += "-udp"
    p.Protocol = corev1.ProtocolUDP
    gs.Spec.Ports = append(gs.Spec.Ports, p)
}
This creates two port entries: dual-tcp and dual-udp.

Best Practices

1

Use Dynamic by Default

Start with Dynamic policy for most game servers - it’s the most flexible
2

Reserve Static for Special Cases

Only use Static when required by legacy clients or external integrations
3

Consider Passthrough for P2P

Use Passthrough when your game server needs to advertise its external port
4

Test None for Performance

Benchmark None policy if you have direct pod network access and need minimal latency
5

Size Port Ranges Appropriately

Ensure port range can accommodate: (desired GameServers per node) × (ports per GameServer)
6

Monitor Port Exhaustion

Alert when port utilization exceeds 80%

Troubleshooting

Port Allocation Failures

# Check port allocator logs
kubectl logs -n agones-system deploy/agones-controller \
  | grep portallocator

# Check GameServer events
kubectl describe gameserver <name>
Common issues:
  • Port range exhausted
  • Node has no available ports
  • Port already in use (Static policy)

Query Allocated Ports

# Get allocated port
kubectl get gameserver my-gs -o jsonpath='{.status.ports[0].port}'

# Get full status
kubectl get gameserver my-gs -o yaml

Verify Port Accessibility

# Test UDP port
nc -u <node-ip> <port>

# Test TCP port
telnet <node-ip> <port>

Performance Considerations

Allocation Speed by Policy

  1. None: Fastest (no allocation)
  2. Static: Fast (no allocation)
  3. Dynamic: Moderate (lookup + allocation)
  4. Passthrough: Moderate (same as Dynamic)

Network Performance by Policy

  1. None: Lowest latency (direct)
  2. Static/Dynamic/Passthrough: Slightly higher (NodePort)

Build docs developers (and LLMs) love