Godot 4’s high-level multiplayer makes it genuinely easy to prototype a networked game — ENetMultiplayerPeer, RPCs, and MultiplayerSpawner get you to “two players moving on a map” in an afternoon. The hard part comes later: running an authoritative dedicated server build for real players, in the regions they live in, that scales up for launch and back down when the lobby empties.
This guide covers the full path — from configuring high-level multiplayer for a headless server, to exporting a dedicated Linux build, to running live sessions on Gameye. Godot ships no hosting platform of its own and no orchestration SDK, which is exactly why this works cleanly: Gameye orchestrates your server at the container level, so there is nothing engine-specific to integrate. If your server exports to Linux and runs in Docker, it runs on Gameye.
The steps are current for Godot 4.2 through 4.4. Earlier 4.x releases work the same way — the export-preset UI moved slightly between versions, so check your editor if a menu path differs.
What you need before starting
- A Godot 4 project using the high-level multiplayer API (
SceneMultiplayer/ENetMultiplayerPeer) - The export templates for your exact Godot version installed (Editor → Manage Export Templates)
- Docker installed locally for testing
- Access to an OCI-compatible container registry (Docker Hub, GitLab Registry, AWS ECR, GCP Artifact Registry — any works)
- A Gameye sandbox account with an API token
API URLs: sandbox is api.sandbox-gameye.gameye.net, production is api.gameye.io. Examples below use the production URL — swap in the sandbox URL during integration testing.
Gameye does not require an engine-side plugin or any changes to your game logic. Your Godot server runs as a standard Linux process inside a container, so there is no SDK to import and no lifecycle calls to add inside your project.
Step 1: Set up the server side of your multiplayer
A dedicated server is just your game running in server mode, with no window and no rendering. In Godot, you start a server peer and let the scene tree drive the simulation. Your server always binds the same fixed internal port — Gameye runs it in bridge networking and maps an external port to it automatically, so you never manage port assignment yourself. Players connect to the external host and port the Session API returns (Step 6); your binary just listens on its fixed internal port.
# autoload: ServerBootstrap.gd
extends Node
const SERVER_PORT := 7777 # fixed internal port; Gameye maps an external port to it
const MAX_CLIENTS := 64
func _ready() -> void:
# Only run server logic in headless / dedicated-server builds.
if not OS.has_feature("dedicated_server") and not DisplayServer.get_name() == "headless":
return
var peer := ENetMultiplayerPeer.new()
var err := peer.create_server(SERVER_PORT, MAX_CLIENTS)
if err != OK:
push_error("Failed to start server on port %d (err %d)" % [SERVER_PORT, err])
get_tree().quit(1)
return
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
print("Godot dedicated server listening on UDP %d" % SERVER_PORT)
func _on_peer_connected(id: int) -> void:
print("Player connected: %d" % id)
func _on_peer_disconnected(id: int) -> void:
print("Player disconnected: %d" % id)
A note on tick rate. Godot’s default physics tick rate is 60 ticks per second (Engine.physics_ticks_per_second, set in Project Settings → Physics → Common → Physics Ticks Per Second). On an authoritative server that value is your simulation rate — every player’s state is resolved against it. 60 is a sensible default for most action games; raise it for fast-paced shooters, lower it for turn-based or slower simulations to save CPU per session. Your network send rate is separate — control how often you replicate state with a timer rather than blasting on every physics tick.
Step 2: Export a headless Linux dedicated server build
Godot 4 has a first-class dedicated server export mode that strips rendering, audio, and editor resources so the build is lean and runs without a GPU.
- Open Project → Export and add a Linux preset (or duplicate your existing one).
- Set the export mode to “Dedicated Server” (Resources tab → Export Mode). This drops textures and audio you don’t need server-side.
- Make sure Embed PCK is enabled so you ship a single self-contained binary.
Then export from the command line so this slots into CI:
# Godot 4.x — headless editor exporting a dedicated-server Linux build
godot --headless --export-release "Linux" ./build/game_server.x86_64
Test it locally before containerizing — it should start, print your “listening” log line, and stay running:
PORT=7777 ./build/game_server.x86_64 --headless
The --headless flag runs with no display server, which is what you want on a server host. Combined with the dedicated-server export mode, Godot never initializes rendering.
Step 3: Write the Dockerfile
The container only needs the exported binary and a minimal Linux base. Headless Godot has very few runtime dependencies.
FROM debian:bookworm-slim
# ca-certificates covers TLS if your server calls out (auth, webhooks, etc.)
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY build/game_server.x86_64 /app/game_server.x86_64
RUN chmod +x /app/game_server.x86_64
# Server binds its fixed internal port (7777); Gameye maps an external port to it.
EXPOSE 7777/udp
ENTRYPOINT ["/app/game_server.x86_64", "--headless"]
Build and run it locally to confirm the container boots and listens:
docker build -t my-godot-server:latest .
docker run --rm -p 7777:7777/udp my-godot-server:latest
If the binary complains about a missing shared library on first boot, add it to the
apt-get installline. A clean dedicated-server export onbookworm-slimtypically needs nothing beyondca-certificates.
Step 4: Build and push to your registry
Tag the image for your registry and push it. Gameye pulls from any OCI-compatible registry.
docker tag my-godot-server:latest registry.example.com/yourteam/godot-server:1.0.0
docker push registry.example.com/yourteam/godot-server:1.0.0
Use immutable version tags (1.0.0, a git SHA) rather than latest so a session always runs the exact build you intend.
Step 5: Register the image with Gameye
Provide Gameye with your registry credentials and image name (e.g. yourteam/godot-server) during onboarding. Gameye pre-pulls your image onto infrastructure in every region you deploy to, so by the time your matchmaker calls the API there’s no image transfer happening — just a container start. When you push a new version tag, Gameye pulls it in the background so it’s warm before you route sessions to it.
Step 6: Start sessions from your matchmaker
When your matchmaker decides a match is ready, it calls the Gameye Session API. First, get the available locations for your image and have clients ping the latencyIp addresses so you can pick the closest region:
GET https://api.gameye.io/available-location/yourteam%2Fgodot-server
Authorization: Bearer <your-api-token>
# Response
{
"locations": [
{ "location": "europe", "latencyIp": "185.x.x.x" },
{ "location": "us-east", "latencyIp": "104.x.x.x" },
{ "location": "asia-east", "latencyIp": "43.x.x.x" }
]
}
Then start the session in the chosen location:
POST https://api.gameye.io/session
Authorization: Bearer <your-api-token>
Content-Type: application/json
{
"location": "europe",
"image": "yourteam/godot-server",
"version": "1.0.0",
"args": ["--", "--map=arena", "--max-players=16"],
"labels": { "match_id": "abc123", "game_mode": "ranked" },
"ttl": 3600
}
Anything after -- is passed to your game; read it with OS.get_cmdline_user_args() in your bootstrap. The response comes back in ~0.5 seconds with the external connection details:
{
"id": "session-abc123",
"host": "185.x.x.x",
"ports": [
{ "type": "udp", "container": 7777, "host": 34521 }
]
}
Players connect to host and the external host port from the array — not 7777. Your binary always binds 7777 internally; Gameye maps an ephemeral external port to it. Gameye is not in the network path during gameplay, so there’s no relay hop or added latency. Hand those values to your clients:
# client side — host and external port returned by the Session API
var peer := ENetMultiplayerPeer.new()
peer.create_client(host, external_port)
multiplayer.multiplayer_peer = peer
Step 7: Shut down cleanly when the match ends
When a match finishes, your server process should exit cleanly. Gameye monitors the container — when the process exits with code 0, Gameye reclaims the compute and stops billing for that session. The simplest pattern is a self-terminating server that quits once the last player leaves:
func _on_peer_disconnected(id: int) -> void:
print("Player disconnected: %d" % id)
if multiplayer.get_peers().is_empty():
print("Lobby empty — shutting down")
get_tree().quit(0)
If your backend needs to force-end a session before the server self-terminates — say a player reports a crash mid-match — call the API directly:
DELETE https://api.gameye.io/session/session-abc123
Authorization: Bearer <your-api-token>
Self-termination on empty lobby is the cheapest pattern: you only pay for a server while players are actually in it.
Taking it to production
A working session is the start. For a live Godot game, the things that matter next are:
- Regions. Use
"location": "auto"and let Gameye place each session near its players, or pin specific regions for your matchmaker to choose from. Latency is the difference between “feels responsive” and “rubber-banding.” - Scaling. Sessions are created on demand and torn down on exit — no fixed fleet to size, no idle servers burning budget between matches. This is the core of game server orchestration.
- Tick budget. Profile a populated session at your chosen
physics_ticks_per_second. That number, times your peak concurrent sessions, is your real CPU footprint — it drives cost far more than player count alone. - ENet tuning. For high player counts, enable ENet range coding and set channel limits deliberately; for browser clients, swap in
WebSocketMultiplayerPeer(the server side of the integration above is identical from Gameye’s perspective). - No vendor lock-in. Because there’s no SDK in your binary, the same container runs anywhere. You’re never rewriting server code to change hosts.
Gameye runs dedicated servers for any engine with no SDK and no plugin — see engine support for the full picture, or how Gameye compares to other hosts if you’re evaluating options.
Frequently asked questions
Does Godot support dedicated servers?
Yes. Godot 4 has a built-in dedicated server export mode that strips rendering and audio, plus a --headless runtime flag so the server runs without a display. Combined with the high-level multiplayer API (ENetMultiplayerPeer), you get an authoritative server build with no third-party netcode required.
What is Godot’s default physics tick rate?
60 ticks per second. It’s set in Project Settings → Physics → Common → Physics Ticks Per Second, and read at runtime via Engine.physics_ticks_per_second. On a dedicated server this is your authoritative simulation rate — keep 60 for most games, raise it for fast-paced action, and lower it for slower or turn-based games to reduce CPU cost per session.
How do I run a Godot server headless?
Export a Linux build with the export mode set to Dedicated Server, then run the binary with the --headless flag (e.g. ./game_server.x86_64 --headless). No display server or GPU is needed. Inside a container, set that as the ENTRYPOINT.
Do I need an SDK or plugin to host a Godot server on Gameye?
No. Gameye orchestrates at the container level, not the engine level. Your Godot server starts, listens on its fixed internal port, and accepts connections — Gameye maps an external port to it and handles session allocation, region placement, scaling, and shutdown externally via its REST API. Nothing runs inside your game binary.
ENet, WebSocket, or WebRTC for a Godot dedicated server?
For native desktop and mobile clients, ENet (ENetMultiplayerPeer) over UDP is the default and the right choice — low latency, reliable/unreliable channels. For browser-based clients use WebSocketMultiplayerPeer. Both expose the same high-level multiplayer API, and both run identically on Gameye — only the listen protocol in your image registration changes.
How much does it cost to host a Godot multiplayer game?
You pay for sessions while they’re running, not for an idle fleet. Cost is driven by concurrent active sessions and the CPU each one uses (your tick rate and game logic), not by registered players. See pricing for current rates, and use server sizing to estimate your footprint.