Hosting Unity dedicated servers
Gameye runs Unity 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 project needs no Gameye package or SDK. Any Unity netcode stack with a dedicated server mode works, including Netcode for GameObjects, FishNet, Mirror and Photon Fusion.
How it works
Section titled “How it works”- Build your project with the Linux Dedicated Server target.
- Package the 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 (
7777in this guide). - 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.
- Unity 2021.2 or later with the Linux Dedicated Server Build Support module installed for your Editor version. Install the IL2CPP module too if you build with IL2CPP.
- Docker.
1. Start the server
Section titled “1. Start the server”The server listens on all interfaces on a fixed port. In bridge mode, the default, Gameye maps that port to a free port on the node. With Netcode for GameObjects and Unity Transport:
// ServerBootstrap.cs: put it on an object in your first sceneusing Unity.Netcode;using Unity.Netcode.Transports.UTP;using UnityEngine;
public class ServerBootstrap : MonoBehaviour{ const ushort Port = 7777;
#if UNITY_SERVER void Start() { Application.targetFrameRate = 60; // your tick rate QualitySettings.vSyncCount = 0;
var transport = NetworkManager.Singleton.GetComponent<UnityTransport>(); transport.SetConnectionData("0.0.0.0", Port, "0.0.0.0"); NetworkManager.Singleton.OnClientConnectedCallback += id => Debug.Log($"JOIN {id}"); NetworkManager.Singleton.OnClientDisconnectCallback += id => Debug.Log($"LEAVE {id}");
if (!NetworkManager.Singleton.StartServer()) { Debug.LogError("server failed to start"); Application.Quit(1); return; } Debug.Log($"READY port={Port}"); }#endif}- Set
Application.targetFrameRateto your tick rate. A dedicated server build has no vsync, so without it the main loop spins a CPU core flat out. - The Dedicated Server target defines
UNITY_SERVER. Use#if UNITY_SERVERand#if !UNITY_SERVERso one project builds both the client and the server. - With FishNet, Mirror or another stack, the shape is the same: start the server on a fixed UDP port and bind to all interfaces. For Photon Fusion, see Using Photon Fusion.
Debug.Loggoes to stdout once you start the player with-logFile -(see the container section). Stdout is what the logs endpoint returns.- Treat every message from a client as untrusted input, and never let a client set an outcome directly.
2. Build the server
Section titled “2. Build the server”- In Unity 6, open File → Build Profiles and choose Linux Server. In Unity 2021.2 to 2023, open File → Build Settings, choose Dedicated Server and set the target platform to Linux. This is the Dedicated Server target. It strips rendering and audio from the build. The older “Server Build” or headless checkbox on the Linux player is not the same thing.
- Prefer the IL2CPP scripting backend for production servers. Mono is fine while you iterate.
- Build from the command line, so the same step runs in CI.
BuildScript.BuildServeris your own editor script that callsBuildPipeline.BuildPlayerwithStandaloneBuildSubtarget.Server:
unity -batchmode -quit -projectPath . \ -buildTarget Linux64 -standaloneBuildSubtarget Server \ -executeMethod BuildScript.BuildServerThe output is a binary such as GameServer.x86_64, a GameServer_Data folder and UnityPlayer.so. Run it once before you containerize it:
./Build/LinuxServer/GameServer.x86_64 -batchmode -nographics -logFile -It should print READY port=7777 and keep running.
3. Shutdown
Section titled “3. Shutdown”When Gameye stops a session, for example after a DELETE /session/{id} or when its ttl runs out, the container gets SIGTERM. The Unity player handles it as a normal quit, so you don’t need a wrapper script. In our test, a Unity 6000.3 IL2CPP dedicated server running as the container’s main process exited with code 0 about 2.4 seconds after SIGTERM, with its logs flushed. If Unity is still running about 50 seconds later, the container is killed.
Put your end-of-match work in one handler and use it for both paths, the stop signal and the natural match end:
void Awake(){ Application.quitting += ReportResultsAndFlush;}
// When the match ends:// Application.Quit(0);
void ReportResultsAndFlush(){ // Tell your backend how the match ended. Keep it to a few seconds.}When the match ends, quit. The process exits with code 0, the session ends and billing stops. Don’t go back to a lobby scene: start a new session for the next match. Signal handling can change between Unity versions, so after an engine upgrade, run docker stop on a local container and check that your handler still runs.
4. The container
Section titled “4. The container”Start the player directly as the container’s main process, so SIGTERM reaches Unity. Build the image for linux/amd64.
FROM debian:bookworm-slimRUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates libstdc++6 libgcc-s1 libatomic1 zlib1g \ && rm -rf /var/lib/apt/lists/*RUN groupadd --system --gid 10001 game \ && useradd --system --uid 10001 --gid game --no-create-home --shell /usr/sbin/nologin gameWORKDIR /serverCOPY --chown=game:game Build/LinuxServer/ /server/RUN chmod +x /server/GameServer.x86_64USER gameEXPOSE 7777/udpENTRYPOINT ["/server/GameServer.x86_64", "-batchmode", "-nographics", "-logFile", "-"]-logFile -sends the Unity player log to stdout. Without it, the log goes to a file inside the container and the logs endpoint shows nothing.- Keep
ENTRYPOINTin this exec form. If you need a shell script to turn environment variables into Unity arguments, end it withexecso Unity replaces the shell and still getsSIGTERM. - Add a
.dockerignorethat keepsLibrary/,Temp/and the other Unity folders out of the build context.
docker build --platform linux/amd64 -t mygame/unity-server:build-001 .docker run --rm -p 7777:7777/udp mygame/unity-server:build-001docker push mygame/unity-server:build-001Use a new tag for every build. Nodes that already have a tag don’t pull it again. See Tagging rules.
5. Start a session
Section titled “5. 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/unity-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": "unity-server", "location": "eu-west-1", "version": "build-001", "args": ["-map", "arena"], "env": { "MATCH_ID": "abc-123" }, "ttl": "1h" }'Self-serve accounts use api.sandbox-gameye.gameye.net instead of the production host. Gameye appends args to your ENTRYPOINT, so read them with System.Environment.GetCommandLineArgs(). Read env with System.Environment.GetEnvironmentVariable(). Don’t put secrets in env, because anyone in your organization who can read sessions can see it.
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, not to 7777:
var transport = NetworkManager.Singleton.GetComponent<UnityTransport>();transport.SetConnectionData(host, port); // e.g. "203.0.113.42", 49152NetworkManager.Singleton.StartClient();Gameye isn’t in the network path during the match. See Integrating Unity Matchmaker to start sessions from Unity Matchmaker, or Custom matchmaker for region selection and player tracking.
With a coding agent
Section titled “With a coding agent”gameye-skills has a Unity server skill with these templates, and the gameye-mcp server can check your Dockerfile and start sessions for you. See AI agents.
Related
Section titled “Related”- Getting started: Docker Hub or GHCR, the Admin Panel and your first API call.
- Container environment variables: every variable Gameye sets in the container.
- Dedicated Server in the Unity Manual.