Overview

Integrate Nakama matchmaking with i3D.net to spin up dedicated servers on demand. This guide shows how to set up the Arcus SDK on your headless server, configure the Nakama i3D fleet manager, wire matchmaker callbacks so players get connection details automatically, and manage the session lifecycle.

Prerequisites

Nakama version
The Nakama–i3D plugin has been tested with Nakama 3.36+.

Before you begin, make sure you have:

  • An i3D.net account with registered hosts and an uploaded game server binary.
  • A running Nakama server (self-hosted or Heroic Cloud).
  • The Arcus SDK for your platform (Unity, Unreal, or C++).
  • Go 1.23.5+ for server development.

Architecture overview #

The Nakama–i3D integration uses i3D.net’s One API to allocate servers. When players are matched, the plugin:

  • Requests a server allocation from i3D.net.
  • Waits for the server to report ready via Arcus.
  • Notifies players with connection details.
  • Manages the server lifecycle through status updates.

Install the Nakama–i3D plugin #

This guide assumes you already have a Nakama Go runtime project and know how to host a Nakama instance (for example, on Heroic Cloud). The plugin implements Nakama’s Fleet Manager interface for i3D.net and is available as an open‑source module.

Install the plugin #

Add the i3D fleet manager to your Nakama Go module:

1
go get github.com/i3d/nakama-fleetmanager

Register the fleet manager and matchmaker callback #

Create and register the fleet manager in InitModule:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import (
    "github.com/i3d/nakama-fleetmanager"
    "github.com/heroiclabs/nakama/runtime"
)

func InitModule(ctx context.Context, nk runtime.NakamaModule, initializer runtime.Initializer, logger runtime.Logger) error {
    // Load config
    cfg, err := fleetmanager_config.NewConfigFromRuntime(ctx)
    if err != nil {
        cfg, err = fleetmanager_config.NewConfig()
        if err != nil {
            return fmt.Errorf("config error: %w", err)
        }
    }

    // Create and register the fleet manager
    fm, err := fleetmanager.NewI3dFleetManager(ctx, logger, initializer, nk, cfg)
    if err != nil {
        return err
    }
    if err := initializer.RegisterFleetManager(fm); err != nil {
        return err
    }

    // Register the matchmaker handler
    return initializer.RegisterMatchmakerMatched(MatchmakerMatched)
}

Create a headless game server #

Your dedicated server must implement the Arcus protocol so i3D.net and Nakama can track status and readiness. This section covers what the integration needs. Use your networking library’s docs for gameplay sync (Nakama authoritative matches, NGO, Mirror, etc.).

Unity #

  1. Download and import the Unity Arcus SDK.
  2. Initialize Arcus in your server bootstrap scene or script.

Unreal Engine #

  1. Install the Unreal Arcus plugin.
  2. Initialize the Arcus subsystem in your server GameMode/module.

C++ #

  1. Follow the C++ integration guide and link the Arcus library.
  2. Initialize the Arcus client during server startup.

Minimal initialization (Unity) #

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void Start()
{
    arcusClient.Initialize();

    // Read metadata passed from Nakama (map, mode, etc.)
    var meta = arcusClient.GetMetadata();
    var mapName = meta.TryGetValue("map", out var m) ? m : "default";
    LoadMap(mapName);

    // Signal that the server is online and ready for allocation
    arcusClient.SetStatus(ArcusStatus.Online); // status 4
}

Maintain state synchronization #

Arcus keeps state synchronized between your servers and Nakama:

  • Servers report readiness before players connect.
  • Metadata (map, mode, etc.) from Nakama is passed to the server.
  • Lifecycle is coordinated through status updates.
Important
Your game server must implement the Arcus protocol. It handles critical lifecycle events and ensures players connect only to ready servers.

Arcus status lifecycle #

Your server drives state transitions so allocation and player joins are coordinated.

StatusNameMeaning
0InactiveNot participating.
1SetupPreparing environment.
2StartingInitializing the game server.
3ReadyInternal setup; switch to Online (4) when initialization completes.
4OnlineAvailable for allocation or reallocation.
5AllocatedReserved for a session; accept players.
6AllocatingAllocation in progress; failed allocs trigger server restart.
Tip
When a session ends, set Online (4) to reuse the process, or exit for a clean restart and fresh ports.

Configure i3D services #

Configure via environment variables (recommended) or a local.yml file mounted in your Nakama data directory.

Environment variables #

Set these variables in your Nakama deployment:

VariableRequiredDescriptionDefault
I3D_APPLICATION_IDtruei3D application (game) ID
I3D_ACCESS_TOKENtrueOne API token
I3D_USE_BEARER_AUTHfalseUse OAuth2 M2M bearer auth (true/false)false
I3D_CLIENT_IDCond.OAuth2 client ID (when bearer auth enabled)
I3D_CLIENT_SECRETCond.OAuth2 client secret
I3D_AUDIENCECond.OAuth2 audience
I3D_AUTHENTICATION_URLCond.OAuth2 token URL
I3D_API_URLfalseOne API base URLhttps://api.i3d.net
I3D_RETRY_ATTEMPTSfalseRetry count for allocation requests3
I3D_RETRY_DELAYfalseInitial retry backoff (e.g., 1500ms)1500ms
I3D_RETRY_MAX_DELAYfalseMax backoff delay (e.g., 7500ms)7500ms
Security note
Prefer OAuth2 M2M in production. Keep tokens out of source control and use your platform’s secret manager.

Authentication Setup #

Basic Authentication #

For basic authentication, you only need:

  • I3D_APPLICATION_ID: Your application ID from i3D.net
  • I3D_ACCESS_TOKEN: Your API access token

M2M OAuth2 Authentication #

For enhanced security using OAuth2:

  • Set I3D_USE_BEARER_AUTH=true
  • Provide your OAuth2 credentials:
    • I3D_CLIENT_ID
    • I3D_CLIENT_SECRET
    • I3D_AUDIENCE
    • I3D_AUTHENTICATION_URL

Implement matchmaking #

Handle matchmaker events #

Allocate a server when players are matched:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
func MatchmakerMatched(
    ctx context.Context,
    logger runtime.Logger,
    db *sql.DB,
    nk runtime.NakamaModule,
    entries []runtime.MatchmakerEntry,
) (string, error) {
    // Extract user IDs from matched players
    userIDs := make([]string, len(entries))
    for i, entry := range entries {
        userIDs[i] = entry.Presence.UserId
    }

    // Define callback for when server is ready
    callback := func(
        status runtime.FmCreateStatus,
        inst *runtime.InstanceInfo,
        sessions []*runtime.SessionInfo,
        metadata map[string]any,
        err error,
    ) {
        if err != nil {
            logger.Error("Failed to create game session", err)
            return
        }

        // Notify players with connection details
        for _, session := range sessions {
            notification := map[string]any{
                "IpAddress": inst.IpAddress,
                "Port":      inst.Port,
                "SessionId": session.SessionId,
            }

            content, _ := json.Marshal(notification)
            nk.NotificationSend(ctx, session.UserId, "Game Session Created", content, 9000, "", false)
        }
    }

    // Metadata for the game server
    metadata := map[string]any{
        "map":        "desert_arena",
        "gameMode":   "team_deathmatch",
        "maxPlayers": len(entries),
    }

    // Create the game session
    fm := nk.GetFleetManager()
    return "", fm.Create(ctx, len(entries), userIDs, nil, metadata, callback)
}

Use allocation filters #

Filter allocations by fleet, region, build, and more:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Create a filter builder
fb := fleetmanager.NewFilterBuilder()

// Add filters as needed
fb.Add(fleetmanager.FleetId, "your-fleet-id")
fb.Add(fleetmanager.RegionName, "eu-west") // region name as defined in i3D.net
fb.Add(fleetmanager.ApplicationBuildId, "your-build-id")

// Apply filters to metadata
metadata := fb.AddFiltersToMetaData(map[string]any{
    "map":      "desert_arena",
    "gameMode": "ranked",
})

// Create session with filters
fm.Create(ctx, maxPlayers, userIDs, nil, metadata, callback)

Available filters #

  • deploymentEnvironmentId / deploymentEnvironmentName
  • fleetId / fleetName
  • hostId
  • applicationBuildId / applicationBuildName
  • dcLocationId / dcLocationName
  • regionId / regionName

Advanced features #

Health check RPC #

Register a basic health check endpoint:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func InitModule(ctx context.Context, ...) error {
    // ... other initialization code ...

    // Register health check RPC
    if err := initializer.RegisterRpc("healthcheck", fleetmanager.RpcHealthCheck); err != nil {
        return err
    }

    return nil
}

List active sessions #

Query active game sessions (occupied servers only):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func ListActiveSessions(ctx context.Context, nk runtime.NakamaModule) {
    fm := nk.GetFleetManager()

    query := "+value.playerCount:2" // Sessions with 2+ players
    limit := 10
    cursor := ""

    for {
        instances, nextCursor, err := fm.List(ctx, query, limit, cursor)
        if err != nil {
            logger.Error("Failed to list sessions", err)
            break
        }

        // Process instances
        for _, instance := range instances {
            logger.Info("Active session",
                "id", instance.Id,
                "players", instance.PlayerCount,
                "ip", instance.IpAddress,
                "port", instance.Port)
        }

        if nextCursor == "" {
            break
        }
        cursor = nextCursor
    }
}

Best practices #

Server lifecycle management #

  • Exit after game completion. After players leave, exit the server process. i3D.net restarts it with fresh ports for a clean state.
  • Validate connections. Use Arcus to ensure players connect only when the server is ready.

Performance optimization #

  • Tune retries. Set retry attempts and delays to meet your latency targets.
  • Allocate by region. Use region filters to place servers near players.
  • Monitor allocation times. Use the health check RPC and logs to track fleet manager performance.

Limitations #

  • List returns only occupied servers. i3D.net focuses on allocation, not session persistence.
  • Latency-based allocation isn’t automatic. Implement your own selection logic using region filters.
  • Join only validates capacity against maxPlayers. Player management is handled between clients and game servers.

FAQ #

My game server isn’t receiving metadata from Nakama
Verify your server initializes the Arcus protocol correctly. Check the Arcus SDK docs for your platform.

Players can’t connect to the allocated server
Verify that:

  • The server sets status to Allocated (5) before accepting connections.
  • The correct ports are configured in your i3D.net deployment.
  • Network rules allow player connections.

How do I handle server crashes?
Exit with a non‑zero code other than 134 to trigger an automatic restart. If the server fails to start three times, i3D.net removes the instance. Check the portal for crash logs.

Can I reuse servers for multiple sessions?
Yes. Set status to Online (4) after a game completes. For a clean state, prefer exiting the process.

How do I implement region‑based matchmaking?
Use region filters when creating sessions to place players in their preferred regions. Combine with Nakama matchmaker properties for best results.