---
title: "Deploy an Edgegap server"
description: "Package a server, configure an application, and make it ready for players."
seo:
  noindex: true
---

{/* Copyright (c) 2026 Betide Studio. All Rights Reserved. */}

Publish a Linux dedicated-server container, then request deployments of that version. A deployment request does not build or upload your game.

## Build and publish

Configure Edgegap under **Betide → Crossplay** and its build tools under **Betide → Tools**.

| Setting | What to use |
| --- | --- |
| Application Name | Your Edgegap application |
| Docker Path | The local Docker executable; Docker must be running |
| Target Name Override | Your dedicated-server target, usually `<Project>Server` |
| Server Launch Target Override | Only when the container must launch a different binary |
| Additional Ports | Include `gameport`, matching the server's internal listening port and protocol |
| Required CPU / Required Memory | Resources needed by one server process |

Choose **Local Build** with a working Linux server toolchain, or **Docker Build** with access to Epic's Unreal container image. For Docker Build, set **UE Docker Image Tag** to your project's engine version; do not leave the older default tag. Provide a linked GitHub account and `GHCR_TOKEN` with package-read access.

Verify your **Deployer Key**, then use **Build and Push**. Check the resulting image and application version in Edgegap. Keep a distinct version for each release and test a client against the container before deploying it to players. [Edgegap container workflow](https://docs.edgegap.com/unreal-engine/more-build-methods)

## Credentials

| Credential | Where it belongs |
| --- | --- |
| Authorization Key / `EDGEGAP_API_KEY` | Trusted backend or dedicated-server environment; required by the direct hosting adapter |
| Deployer Key / `EDGEGAP_DEPLOYER_KEY` | Editor or build pipeline |
| Registry token / `EDGEGAP_REGISTRY_TOKEN` | Build pipeline when using a custom registry |
| Matchmaker Auth Token | Client matchmaking only; see [matchmaking](/integrations/edgegap/matchmaking) |

The main API key is not a client credential. Keep direct deployment requests on your backend. For clients, use the configured matchmaker or your own authenticated backend endpoint.

## Request a deployment

**Direct hosting example blocked:** the current adapter sends `app_name`, `version_name`, and `users[].ip`, while Edgegap's v2 deployment schema requires `application`, `version`, and typed user data. Its status parser also reads `status` and nested `assignment`; the v1 status response supplies `current_status` and connection fields at the top level. The adapter needs a fix before this request-and-poll flow can be used. [Edgegap deployment API](https://docs.edgegap.com/docs/api/dedicated-servers)

<BlueprintViewer title="Request and monitor an Edgegap server" nodeNames={["Request Crossplay Server", "Get Crossplay Server Status", "Terminate Crossplay Server"]} />

## Connect players

Pass the assigned **FQDN or public IP** and **external** `gameport` from your trusted backend or matchmaker. **Host** must not include a scheme, port, or URL options. The container's internal port may differ from the external port players use.

**Blueprint**

<BlueprintViewer title="Connect to the assigned server" src="/blueprints/crossplay-edgegap-connect.txt" />

**C++**

Game module dependencies: `UCIKCore`, `BetideCore`, `Engine`.

```cpp
// Copyright (c) 2026 Betide Studio. All Rights Reserved.
// EdgegapConnectGameInstance.h
#pragma once
#include "Engine/GameInstance.h"
#include "Engine/EngineBaseTypes.h"
#include "EdgegapConnectGameInstance.generated.h"

UCLASS()
class UEdgegapConnectGameInstance : public UGameInstance
{
    GENERATED_BODY()
  public:
    virtual void Init() override;
    virtual void Shutdown() override;
    UFUNCTION(BlueprintCallable)
    void ConnectToEdgegapServer(const FString &Host, int32 ExternalGamePort);

  private:
    void OnNetworkFailure(UWorld *World, UNetDriver *Driver, ENetworkFailure::Type Failure,
                          const FString &Reason);
    void OnTravelFailure(UWorld *World, ETravelFailure::Type Failure, const FString &Reason);
};

// EdgegapConnectGameInstance.cpp
#include "EdgegapConnectGameInstance.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Travel/UCIKTravelSubsystem.h"
#include "UObject/Class.h"
DEFINE_LOG_CATEGORY_STATIC(LogGameEdgegapConnect, Log, All);

void UEdgegapConnectGameInstance::Init()
{
    Super::Init();
    if (GEngine)
    {
        GEngine->OnNetworkFailure().AddUObject(this, &ThisClass::OnNetworkFailure);
        GEngine->OnTravelFailure().AddUObject(this, &ThisClass::OnTravelFailure);
    }
}

void UEdgegapConnectGameInstance::Shutdown()
{
    if (GEngine)
    {
        GEngine->OnNetworkFailure().RemoveAll(this);
        GEngine->OnTravelFailure().RemoveAll(this);
    }
    Super::Shutdown();
}

void UEdgegapConnectGameInstance::ConnectToEdgegapServer(const FString &Host,
                                                         int32 ExternalGamePort)
{
    if (UKismetSystemLibrary::IsDedicatedServer(this))
    {
        UE_LOG(LogGameEdgegapConnect, Warning, TEXT("Connect from the player client"));
        return;
    }
    if (Host.IsEmpty() || ExternalGamePort <= 0 || ExternalGamePort > 65535)
    {
        UE_LOG(LogGameEdgegapConnect, Warning,
               TEXT("Provide the assigned host and external game port (1-65535)"));
        return;
    }
    UUCIKTravelSubsystem *Travel = GetSubsystem<UUCIKTravelSubsystem>();
    if (!Travel)
    {
        UE_LOG(LogGameEdgegapConnect, Warning, TEXT("Crossplay travel is unavailable"));
        return;
    }
    FUCIK_ConnectInfo Destination;
    Destination.Scheme = TEXT("unreal");
    Destination.Host = Host;
    Destination.Port = ExternalGamePort;
    Destination.Provider = FBetideProviderId(FName(TEXT("Betide.Edgegap")));
    Travel->TravelToConnectInfo(
        Destination,
        FBetideCallback<bool>::CreateWeakLambda(this, [](const TBetideResult<bool> &Result) {
            if (Result.bOk)
                UE_LOG(LogGameEdgegapConnect, Log,
                       TEXT("Travel requested; waiting for the server connection"));
            else
                UE_LOG(LogGameEdgegapConnect, Warning, TEXT("%s"), *Result.Error.Message);
        }));
}

void UEdgegapConnectGameInstance::OnNetworkFailure(UWorld *World, UNetDriver *,
                                                   ENetworkFailure::Type Failure, const FString &)
{
    if (World && World->GetGameInstance() != this)
        return;
    UE_LOG(LogGameEdgegapConnect, Warning, TEXT("%s"), *UEnum::GetValueAsString(Failure));
}

void UEdgegapConnectGameInstance::OnTravelFailure(UWorld *World, ETravelFailure::Type Failure,
                                                  const FString &)
{
    if (World && World->GetGameInstance() != this)
        return;
    UE_LOG(LogGameEdgegapConnect, Warning, TEXT("%s"), *UEnum::GetValueAsString(Failure));
}
```

Use an IP-compatible **GameNetDriver** and call from the client after its local Player Controller exists. This example accepts an endpoint supplied outside the blocked direct-hosting adapter above.

**On Success** means Unreal received the travel request; it does not confirm connection. **NetworkError** and **TravelError** report later failures. Deployment readiness can precede game readiness, so retry only after failure and cap retries in your connection UI. Do not retry from **On Success**. [Edgegap connection timing](https://docs.edgegap.com/learn/matchmaking)

## Stop unused servers

Stop accepting players and finish saving before calling **Stop Edgegap After Saving**. Run it inside the deployed dedicated server; Edgegap supplies `ARBITRIUM_DELETE_URL` and `ARBITRIUM_DELETE_TOKEN`. No main API key is needed for this request.

**Blueprint**

<BlueprintViewer title="Stop this deployment" src="/blueprints/crossplay-edgegap-self-stop.txt" />

Game Instance variable: `StopRequested` (**Boolean**, false).

**C++**

Server module dependencies: `EdgegapIntegrationKit`, `BetideCore`, `Engine`.

```cpp
// Copyright (c) 2026 Betide Studio. All Rights Reserved.
// EdgegapStopGameInstance.h
#pragma once
#include "Engine/GameInstance.h"
#include "Nodes/Deployments/EGIK_SelfStopDeployment.h"
#include "EdgegapStopGameInstance.generated.h"

UCLASS()
class UEdgegapStopGameInstance : public UGameInstance
{
    GENERATED_BODY()
  public:
    UFUNCTION(BlueprintCallable)
    void StopEdgegapAfterSaving();

  private:
    bool StopRequested = false;
    UPROPERTY()
    TObjectPtr<UEGIK_SelfStopDeployment> StopAction;
    UFUNCTION()
    void OnStopAccepted(bool bSuccess, const FBetideError &Error);
    UFUNCTION()
    void OnStopFailed(bool bSuccess, const FBetideError &Error);
    void ReleaseStopAction();
};

// EdgegapStopGameInstance.cpp
#include "EdgegapStopGameInstance.h"
#include "Kismet/KismetSystemLibrary.h"
DEFINE_LOG_CATEGORY_STATIC(LogGameEdgegapStop, Log, All);

void UEdgegapStopGameInstance::StopEdgegapAfterSaving()
{
    if (!UKismetSystemLibrary::IsDedicatedServer(this))
    {
        UE_LOG(LogGameEdgegapStop, Warning,
               TEXT("Self-stop runs on the deployed dedicated server"));
        return;
    }
    if (StopRequested)
    {
        UE_LOG(LogGameEdgegapStop, Warning, TEXT("A stop request is already pending or accepted"));
        return;
    }
    StopRequested = true;
    StopAction = UEGIK_SelfStopDeployment::SelfStopDeployment();
    StopAction->RegisterWithGameInstance(this);
    StopAction->OnSuccess.AddDynamic(this, &ThisClass::OnStopAccepted);
    StopAction->OnFailure.AddDynamic(this, &ThisClass::OnStopFailed);
    StopAction->Activate();
}

void UEdgegapStopGameInstance::OnStopAccepted(bool, const FBetideError &)
{
    ReleaseStopAction();
    UE_LOG(LogGameEdgegapStop, Log, TEXT("Edgegap accepted the stop request"));
}

void UEdgegapStopGameInstance::OnStopFailed(bool, const FBetideError &Error)
{
    StopRequested = false;
    ReleaseStopAction();
    UE_LOG(LogGameEdgegapStop, Warning, TEXT("%s"), *Error.Message);
}

void UEdgegapStopGameInstance::ReleaseStopAction()
{
    if (StopAction)
    {
        StopAction->OnSuccess.RemoveDynamic(this, &ThisClass::OnStopAccepted);
        StopAction->OnFailure.RemoveDynamic(this, &ThisClass::OnStopFailed);
        StopAction = nullptr;
    }
}
```

Success means Edgegap accepted the stop request, not that the container has already exited. Failure reports the missing environment or HTTP error and permits an explicit retry. Do not exit before the request completes or rely on an exit-time HTTP request as your only cleanup path.

<Accordion>
  <AccordionItem title="Ports, environment, and health">

Set port protocol and **Check Port** to match a service that actually listens. A UDP game port does not become an HTTP health endpoint by enabling a check. **Max Duration** bounds deployment lifetime; **Estimated Maximum Deploy Time** allows for startup. Keep the matchmaking **Server Allocation Timeout** above that allowance.

**Default Environment Variables** are saved in project configuration. **Is Hidden** masks a value in Edgegap's dashboard; it does not remove that value from your project files. Supply production secrets through your deployment environment.

Use **Entrypoint Override** and **Command Arguments** only when the image needs them. A wrong binary, missing executable permission, uncooked map, or mismatched internal port can leave a running container unreachable.

  </AccordionItem>
  <AccordionItem title="Server browser and player slots">

The Server Browser has its own URL and tokens. **Client Token** is intended for browsing/reserving seats and ships in client configuration. **Server Token** is for registration, heartbeats, and confirmations; packaged servers read `EDGEGAP_SERVER_BROWSER_SERVER_TOKEN`.

The generic hosting adapter also requires the main API key for availability and deployment status. For a client-only browser, use the dedicated Server Browser nodes with its client token, rather than putting the main key in the game. See [dedicated servers](/crossplay/dedicated-server) for the shared hosting API.

  </AccordionItem>
  <AccordionItem title="Deployment diagnostics">

Run log retrieval on a trusted dedicated server with the main API key. Configure **Endpoint Storage before stopping the deployment**, then pass its request ID. [Edgegap logs API](https://docs.edgegap.com/docs/api/dedicated-servers)

**Blueprint**

<BlueprintViewer title="Retrieve deployment logs" src="/blueprints/crossplay-edgegap-logs.txt" />

Game Instance variables: `LogsBusy` (**Boolean**, false), `LastLogs` (**EGIK Container Logs Response**).

**C++**

Server module dependencies: `EdgegapIntegrationKit`, `BetideCore`, `Engine`.

```cpp
// Copyright (c) 2026 Betide Studio. All Rights Reserved.
// EdgegapLogsGameInstance.h
#pragma once
#include "Engine/GameInstance.h"
#include "Nodes/Deployments/EGIK_GetContainerLogs.h"
#include "EdgegapLogsGameInstance.generated.h"

UCLASS()
class UEdgegapLogsGameInstance : public UGameInstance
{
    GENERATED_BODY()
  public:
    UFUNCTION(BlueprintCallable)
    void LoadDeploymentLogs(const FString &RequestId);
    UPROPERTY(BlueprintReadOnly)
    FEGIK_ContainerLogsResponse LastLogs;

  private:
    bool LogsBusy = false;
    UPROPERTY()
    TObjectPtr<UEGIK_GetContainerLogs> LogsAction;
    UFUNCTION()
    void OnLogsLoaded(const FEGIK_ContainerLogsResponse &Response, const FBetideError &Error);
    UFUNCTION()
    void OnLogsFailed(const FEGIK_ContainerLogsResponse &Response, const FBetideError &Error);
    void ReleaseLogsAction();
};

// EdgegapLogsGameInstance.cpp
#include "EdgegapLogsGameInstance.h"
#include "Kismet/KismetSystemLibrary.h"
DEFINE_LOG_CATEGORY_STATIC(LogGameEdgegapLogs, Log, All);

void UEdgegapLogsGameInstance::LoadDeploymentLogs(const FString &RequestId)
{
    if (!UKismetSystemLibrary::IsDedicatedServer(this))
    {
        UE_LOG(LogGameEdgegapLogs, Warning, TEXT("Run diagnostics on a trusted dedicated server"));
        return;
    }
    if (LogsBusy)
    {
        UE_LOG(LogGameEdgegapLogs, Warning, TEXT("A logs request is already running"));
        return;
    }
    if (RequestId.IsEmpty())
    {
        UE_LOG(LogGameEdgegapLogs, Warning, TEXT("Provide a deployment request ID"));
        return;
    }
    LastLogs = FEGIK_ContainerLogsResponse();
    LogsBusy = true;
    LogsAction = UEGIK_GetContainerLogs::GetContainerLogs(RequestId);
    LogsAction->RegisterWithGameInstance(this);
    LogsAction->OnSuccess.AddDynamic(this, &ThisClass::OnLogsLoaded);
    LogsAction->OnFailure.AddDynamic(this, &ThisClass::OnLogsFailed);
    LogsAction->Activate();
}

void UEdgegapLogsGameInstance::OnLogsLoaded(const FEGIK_ContainerLogsResponse &Response,
                                            const FBetideError &)
{
    LastLogs = Response;
    LogsBusy = false;
    ReleaseLogsAction();
    if (LastLogs.RawLogs.IsEmpty())
        UE_LOG(
            LogGameEdgegapLogs, Warning,
            TEXT("No inline logs returned; check Endpoint Storage and the deployment dashboard"));
    else
        UE_LOG(LogGameEdgegapLogs, Log,
               TEXT("Logs loaded: read LastLogs.RawLogs and LastLogs.Encoding"));
}

void UEdgegapLogsGameInstance::OnLogsFailed(const FEGIK_ContainerLogsResponse &,
                                            const FBetideError &Error)
{
    LogsBusy = false;
    ReleaseLogsAction();
    UE_LOG(LogGameEdgegapLogs, Warning, TEXT("%s"), *Error.Message);
}

void UEdgegapLogsGameInstance::ReleaseLogsAction()
{
    if (LogsAction)
    {
        LogsAction->OnSuccess.RemoveDynamic(this, &ThisClass::OnLogsLoaded);
        LogsAction->OnFailure.RemoveDynamic(this, &ThisClass::OnLogsFailed);
        LogsAction = nullptr;
    }
}
```

`LastLogs` retains the request ID, raw payload, and encoding for your admin viewer. An empty payload is not proof that the deployment produced no logs. This node does not expose crash logs or the storage link; use Edgegap's dashboard for those. Keep log contents out of player-facing responses.

**Metrics example blocked:** the API returns time series (`labels`, `datasets`, `timestamps`) under `cpu`, `mem`, and network receive/transmit. The node expects scalar CPU, memory, and network fields and can report success with incorrect zero values. Its response types and parser need updating. [Edgegap metrics API](https://docs.edgegap.com/docs/api/dedicated-servers)

<BlueprintViewer title="Inspect deployment metrics" nodeNames={["Get Deployment Metrics"]} />

  </AccordionItem>
</Accordion>
