Skip to main content
The Agones SDK provides methods to manage your game server’s lifecycle, allowing you to signal readiness, handle allocations, and perform graceful shutdowns. Understanding these lifecycle methods is crucial for proper integration with Agones.

GameServer States

A GameServer progresses through several states during its lifetime:
1

Scheduled

The GameServer pod has been scheduled to a node but is not yet running. This is a brief transitional state.
2

RequestReady

The GameServer pod is running and waiting for your game server to call Ready(). This is where initialization happens.
3

Ready

Your game server called Ready() and is available for allocation. The server will remain in this state until allocated or deleted.

Reserved

The server is temporarily reserved via Reserve() and cannot be allocated. It will return to Ready after the reservation expires.

Allocated

The server has been allocated to a game session via GameServerAllocation. This is where players connect and play.

Shutdown

Your game server called Shutdown() or the pod is being terminated. The server will be deleted shortly.

Unhealthy

Health checks failed repeatedly. Agones will delete the server and create a replacement.
GameServer Lifecycle Diagram

Core Lifecycle Methods

Ready()

Marks your game server as ready to accept player connections. This transitions the state from RequestReady to Ready.
When to call Ready(): After your server has finished initialization, loaded maps/assets, and is ready to accept player connections.
func main() {
    s, err := sdk.NewSDK()
    if err != nil {
        log.Fatalf("Could not connect to sdk: %v", err)
    }
    
    // Initialize your game server
    log.Print("Starting game server initialization...")
    initializeGameLogic()
    loadMaps()
    startNetworkListener()
    
    // Mark as ready after initialization
    log.Print("Marking server as ready")
    err = s.Ready()
    if err != nil {
        log.Fatalf("Could not send ready message: %v", err)
    }
    
    log.Print("Server is now ready for allocation")
}
If Ready() is not called within the health check grace period (default: 5 seconds + initial delay), the GameServer will be marked as Unhealthy and deleted.

Shutdown()

Signals that your game server is shutting down gracefully. This transitions the state to Shutdown and marks the server for deletion.
When to call Shutdown(): When a game session ends, when handling SIGTERM, or when you want to terminate the server gracefully.
func handleGameEnd(s *sdk.SDK) {
    log.Print("Game session ended, shutting down")
    
    // Clean up resources
    disconnectAllPlayers()
    saveGameState()
    closeConnections()
    
    // Signal shutdown to Agones
    err := s.Shutdown()
    if err != nil {
        log.Printf("Could not shutdown: %v", err)
    }
    
    // Kubernetes will terminate the pod shortly
    // You can perform final cleanup here
    time.Sleep(5 * time.Second)
    os.Exit(0)
}

// Handle SIGTERM for graceful shutdown
func main() {
    s, _ := sdk.NewSDK()
    
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGTERM)
    
    go func() {
        <-sigChan
        log.Print("Received SIGTERM, shutting down")
        s.Shutdown()
        os.Exit(0)
    }()
    
    // Your game logic...
}
Configure spec.template.spec.terminationGracePeriodSeconds in your GameServer manifest to allow enough time for graceful shutdown (default: 30 seconds).

Allocate()

Marks the GameServer as Allocated without going through GameServerAllocation. This is useful for self-allocation scenarios.
func selfAllocate(s *sdk.SDK) {
    log.Print("Self-allocating server")
    
    err := s.Allocate()
    if err != nil {
        log.Printf("Could not allocate: %v", err)
        return
    }
    
    log.Print("Server is now allocated")
    // Start accepting player connections
}
Most deployments use GameServerAllocation instead of self-allocation. Use Allocate() only when implementing custom allocation logic.

Reserve()

Marks the GameServer as Reserved for a specified duration. Reserved servers cannot be allocated but will return to Ready after the duration expires.
func reserveServer(s *sdk.SDK) {
    // Reserve for 60 seconds
    duration := 60 * time.Second
    
    log.Printf("Reserving server for %v", duration)
    err := s.Reserve(duration)
    if err != nil {
        log.Printf("Could not reserve: %v", err)
        return
    }
    
    log.Print("Server is now reserved")
    // Perform pre-allocation setup
    // Server will return to Ready automatically after 60 seconds
}
Use Reserve() when you need to prevent allocation while preparing the server or when implementing custom matchmaking logic.

Advanced Lifecycle Patterns

Server Reuse Pattern

Return servers to the Ready state after game sessions end to reuse them for multiple matches:
func serverReusePattern(s *sdk.SDK) {
    // Watch for allocation
    s.WatchGameServer(func(gs *sdk.GameServer) {
        if gs.Status.State == "Allocated" {
            log.Print("Server allocated, starting game session")
            
            // Run game session asynchronously
            go func() {
                runGameSession()
                
                // Return to Ready after session ends
                log.Print("Session ended, returning to Ready")
                err := s.Ready()
                if err != nil {
                    log.Printf("Could not return to ready: %v", err)
                    s.Shutdown()
                }
            }()
        }
    })
}
See the Server Reuse Pattern guide for best practices and complete examples.

Delayed Shutdown Pattern

Automatically shut down after N allocations or after a timeout:
func delayedShutdown(s *sdk.SDK, maxAllocations int, shutdownDelay time.Duration) {
    allocationCount := 0
    var lastAllocation time.Time
    
    s.WatchGameServer(func(gs *sdk.GameServer) {
        currentAllocation := gs.ObjectMeta.Annotations["agones.dev/last-allocated"]
        
        if currentAllocation != "" && currentAllocation != lastAllocation {
            allocationCount++
            log.Printf("Allocation #%d", allocationCount)
            
            if allocationCount >= maxAllocations {
                log.Printf("Max allocations reached, shutting down in %v", shutdownDelay)
                time.AfterFunc(shutdownDelay, func() {
                    s.Shutdown()
                })
            }
        }
    })
}

Graceful Degradation Pattern

Return to Ready state on errors, or Shutdown if critical:
func handleGameError(s *sdk.SDK, err error, critical bool) {
    log.Printf("Game error occurred: %v", err)
    
    if critical {
        log.Print("Critical error, shutting down")
        s.Shutdown()
    } else {
        log.Print("Non-critical error, returning to Ready")
        cleanupSession()
        s.Ready()
    }
}

Monitoring State Changes

WatchGameServer()

Receive real-time updates when your GameServer configuration changes:
func watchExample(s *sdk.SDK) {
    err := s.WatchGameServer(func(gs *sdk.GameServer) {
        log.Printf("GameServer Update:")
        log.Printf("  Name: %s", gs.ObjectMeta.Name)
        log.Printf("  State: %s", gs.Status.State)
        log.Printf("  Address: %s", gs.Status.Address)
        log.Printf("  Labels: %v", gs.ObjectMeta.Labels)
        
        // React to state changes
        switch gs.Status.State {
        case "Allocated":
            handleAllocation(gs)
        case "Ready":
            handleReady(gs)
        case "Shutdown":
            handleShutdown(gs)
        }
    })
    
    if err != nil {
        log.Fatalf("Could not watch GameServer: %v", err)
    }
}

GetGameServer()

Retrieve current GameServer information on-demand:
func getServerInfo(s *sdk.SDK) {
    gs, err := s.GameServer()
    if err != nil {
        log.Printf("Could not get GameServer: %v", err)
        return
    }
    
    log.Printf("Server Name: %s", gs.ObjectMeta.Name)
    log.Printf("State: %s", gs.Status.State)
    log.Printf("Address: %s", gs.Status.Address)
    
    // Access ports
    for _, port := range gs.Status.Ports {
        log.Printf("Port %s: %d", port.Name, port.Port)
    }
    
    // Access labels and annotations
    for k, v := range gs.ObjectMeta.Labels {
        log.Printf("Label %s: %s", k, v)
    }
}

Best Practices

Call Ready() Once

Only call Ready() after your server is fully initialized. Don’t call it multiple times unless returning from an Allocated state.

Handle SIGTERM

Always listen for SIGTERM signals and call Shutdown() for graceful termination.

Use WatchGameServer

Monitor state changes with WatchGameServer() instead of polling GetGameServer().

Clean Up Before Shutdown

Disconnect players, save state, and close connections before calling Shutdown().
State Transition Errors: You cannot transition from Shutdown back to Ready. Once shutdown is called, the server will be terminated.
Initial Delay: If your server takes time to initialize, configure spec.health.initialDelaySeconds to give your server time before health checks start.

Troubleshooting

Cause: Your game server never called Ready().Solution: Ensure Ready() is called after initialization. Check logs for errors preventing the call.
Cause: Health checks failed or Ready() took too long.Solution:
  • Call Ready() within the grace period
  • Increase spec.health.initialDelaySeconds
  • Ensure health pings are sent regularly
Cause: The shutdown call failed or the pod terminated before cleanup.Solution:
  • Check SDK connection is alive
  • Increase terminationGracePeriodSeconds
  • Log errors from Shutdown() call
Cause: Error calling Ready() after game session or server in wrong state.Solution:
  • Check the current state before calling Ready()
  • Cannot return to Ready from Shutdown state
  • Check for SDK connection errors

Next Steps

Health Checking

Configure health monitoring for your servers

SDK Overview

Explore SDK features for your language

Server Reuse Pattern

Implement efficient server reuse

Allocation

Learn how to allocate game servers

Build docs developers (and LLMs) love