Skip to content
UCIK Docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Deploy an Edgegap server

Package a server, configure an application, and make it ready for players.

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

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

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

Request and monitor an Edgegap server
Request Crossplay Server · Get Crossplay Server Status · Terminate Crossplay Server

Blueprint example

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.

Connect to the assigned server
Preparing your graph…

Loading the interactive viewer.

Game module dependencies: UCIKCore, BetideCore, Engine.

// 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

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.

Stop this deployment
Preparing your graph…

Loading the interactive viewer.

Game Instance variable: StopRequested (Boolean, false).

Server module dependencies: EdgegapIntegrationKit, BetideCore, Engine.

// 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.

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.

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 for the shared hosting API.

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

Retrieve deployment logs
Preparing your graph…

Loading the interactive viewer.

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

Server module dependencies: EdgegapIntegrationKit, BetideCore, Engine.

// 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

Inspect deployment metrics
Get Deployment Metrics

Blueprint example

Was this page helpful?