Skip to content

Hosting Unreal Engine dedicated servers

Gameye runs Unreal Engine 5 dedicated servers as Linux containers. Each match is one server process in its own Gameye session, on a dedicated server in the region you choose. Your server needs no Gameye plugin or SDK, and no changes to replication or session code.

  1. Build the Linux dedicated server target of your project.
  2. Package the staged build as a Linux container and push it to Docker Hub or GitHub Container Registry.
  3. Create a Gameye application for the image, with the game port registered as UDP (Unreal defaults to 7777).
  4. When your matchmaker forms a match, your backend calls POST /session and sends the players the session’s IP and port.
  5. The server exits when the match ends. That ends the session.
  • A Gameye account. Sign up at trial.gameye.com.
  • An API token for your backend, created in the Admin Panel under Users → API tokens.
  • Unreal Engine 5 built from source. The Epic Games Launcher install can’t build dedicated server targets.
  • A Linux build machine (Ubuntu, WSL2 or a CI runner), or Unreal’s Linux cross-compile toolchain on Windows.
  • Docker.

Add a server target next to your game target in Source/:

Source/MyGameServer.Target.cs
using UnrealBuildTool;
public class MyGameServerTarget : TargetRules
{
public MyGameServerTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Server;
DefaultBuildSettings = BuildSettingsVersion.Latest;
IncludeOrderVersion = EngineIncludeOrderVersion.Latest;
ExtraModuleNames.Add("MyGame");
}
}

Latest follows the engine you build with. If your game target pins a version instead, such as EngineIncludeOrderVersion.Unreal5_5, use the same values here.

Build, cook and stage the server for Linux:

Terminal window
./Engine/Build/BatchFiles/RunUAT.sh BuildCookRun \
-project="/path/to/MyGame.uproject" \
-platform=Linux -server -noclient \
-serverconfig=Development \
-build -cook -stage -pak -archive \
-archivedirectory="/output/LinuxServer"

The server binary ends up at a path like LinuxServer/MyGame/Binaries/Linux/MyGameServer. Some engine versions add another LinuxServer/ folder level, so check the archive directory. Run it once before you containerize it:

Terminal window
./MyGame/Binaries/Linux/MyGameServer /Game/Maps/Gameplay -port=7777 -log

It should start without a window and listen on UDP 7777.

You pick the network mode on the Gameye application. See Choose your network mode for the full comparison.

  • Bridge (recommended). The server listens on 7777 inside the container. Gameye maps it to a free port on the node and returns that port when the session starts. The server needs no changes.
  • Host. The container shares the node’s network, with no port-mapping overhead. The server must listen on the port Gameye assigns, which it reads from GAMEYE_PORT_UDP_7777.

Gameye sets GAMEYE_PORT_UDP_7777 in both modes, and in both it holds the port players connect to. Only host mode uses it as the listen port. In bridge mode the server must keep listening on 7777, because that is the container port Gameye maps.

The entrypoint script below starts the server in either mode. Set GAMEYE_NETWORK=host in the session env when the application uses host mode:

start.sh
#!/bin/bash
PORT=7777
if [ "$GAMEYE_NETWORK" = "host" ]; then
PORT="$GAMEYE_PORT_UDP_7777"
fi
exec stdbuf -oL -eL ./MyGame/Binaries/Linux/MyGameServer -port="$PORT" "$@"

exec makes the server the container’s main process, so it receives the stop signal directly. stdbuf -oL makes Unreal write each log line to stdout as it happens. Gameye runs containers with a terminal, so the logs endpoint shows lines live either way, but when you test with plain docker run or in CI, Unreal holds stdout in a buffer and docker logs falls behind. stdbuf replaces itself with the server, so the stop signal still reaches Unreal.

Unreal servers refuse to run as root, so add a user. The server is a 64-bit Linux binary, so build the image for linux/amd64.

FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates libssl3 \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m gameserver
USER gameserver
WORKDIR /home/gameserver
COPY --chown=gameserver:gameserver LinuxServer/ ./
COPY --chown=gameserver:gameserver --chmod=755 start.sh ./
ENTRYPOINT ["./start.sh"]

Leave the map, game mode and player count out of the image. Pass them per session in args, so one image serves every mode.

Terminal window
docker build --platform linux/amd64 -t mygame/server:build-001 .
docker run --rm -p 7777:7777/udp mygame/server:build-001 /Game/Maps/Gameplay -log
docker push mygame/server:build-001

Use a new tag for every build. Nodes that already have a tag don’t pull it again, so re-pushing a tag such as latest doesn’t deliver your new build. See Tagging rules.

Clients ping the regions that can run your image, and your matchmaker picks one:

Terminal window
curl https://api.production-gameye.gameye.net/available-location/mygame-server \
-H "Authorization: Bearer $GAMEYE_API_TOKEN"

When the match is ready, your backend starts a session:

Terminal window
curl -X POST https://api.production-gameye.gameye.net/session \
-H "Authorization: Bearer $GAMEYE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"image": "mygame-server",
"location": "eu-west-1",
"version": "build-001",
"args": ["/Game/Maps/Gameplay?game=Ranked", "-MaxPlayers=10"],
"labels": { "match_id": "abc-123" },
"ttl": "1h"
}'

Self-serve accounts use api.sandbox-gameye.gameye.net instead of the production host. The response holds the node IP and the port mapping:

{
"id": "a3b2c1d0-...",
"host": "203.0.113.42",
"ports": [{ "type": "udp", "container": 7777, "host": 49152 }]
}

Players connect to host on ports[].host, here 203.0.113.42:49152, with open 203.0.113.42:49152 or your own travel URL. Gameye isn’t in the network path during the match.

ttl caps how long the session can run, as a duration such as "30m" or "2h". Set one as a backstop in case the server never exits. See Custom matchmaker for region selection and player tracking.

When the match is over, report the result to your backend and quit the server, for example with FGenericPlatformMisc::RequestExit(false). The process exits, the session ends and billing stops.

To stop a session from your backend, call DELETE /session/{id}. Gameye sends the container SIGTERM, which Unreal on Linux treats as a request to quit. Test the match-end path in sandbox before launch: a server that never exits keeps running until its ttl.

gameye-skills covers Unreal server builds, and the gameye-mcp server can check your Dockerfile and start sessions for you. See AI agents.