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.
How it works
Section titled “How it works”- Build the Linux dedicated server target of your project.
- Package the staged build as a Linux container and push it to Docker Hub or GitHub Container Registry.
- Create a Gameye application for the image, with the game port registered as UDP (Unreal defaults to
7777). - When your matchmaker forms a match, your backend calls
POST /sessionand sends the players the session’s IP and port. - The server exits when the match ends. That ends the session.
Prerequisites
Section titled “Prerequisites”- 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.
1. Build the Linux server target
Section titled “1. Build the Linux server target”Add a server target next to your game target in Source/:
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:
./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:
./MyGame/Binaries/Linux/MyGameServer /Game/Maps/Gameplay -port=7777 -logIt should start without a window and listen on UDP 7777.
2. Choose a network mode
Section titled “2. Choose a network mode”You pick the network mode on the Gameye application. See Choose your network mode for the full comparison.
- Bridge (recommended). The server listens on
7777inside 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:
#!/bin/bashPORT=7777if [ "$GAMEYE_NETWORK" = "host" ]; then PORT="$GAMEYE_PORT_UDP_7777"fiexec 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.
3. The container
Section titled “3. The container”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 gameserverUSER gameserverWORKDIR /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.
docker build --platform linux/amd64 -t mygame/server:build-001 .docker run --rm -p 7777:7777/udp mygame/server:build-001 /Game/Maps/Gameplay -logdocker push mygame/server:build-001Use 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.
4. Start a session
Section titled “4. Start a session”Clients ping the regions that can run your image, and your matchmaker picks one:
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:
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.
5. End the match
Section titled “5. End the match”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.
With a coding agent
Section titled “With a coding agent”gameye-skills covers Unreal server builds, and the gameye-mcp server can check your Dockerfile and start sessions for you. See AI agents.
Related
Section titled “Related”- Unreal dedicated server sample: a minimal UE 5.8 project that follows this guide, with a build script for Epic’s container image and smoke and join tests.
- Getting started: Docker Hub or GHCR, the Admin Panel and your first API call.
- Container environment variables: every variable Gameye sets in the container.
- Unreal Engine dedicated server hosting: what Gameye offers Unreal studios.