# Getting Started

Edgegap helps take your multiplayer game online and operate game servers cost-efficiently:

<table data-view="cards"><thead><tr><th align="center"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td align="center"><strong>1. Build your Server</strong></td><td><a href="/files/Hk85MEqaOwJC59GIb7l2">/files/Hk85MEqaOwJC59GIb7l2</a></td></tr><tr><td align="center"><strong>2. Upload Build</strong></td><td><a href="/files/RWPWVGE2XojZmzUC3wau">/files/RWPWVGE2XojZmzUC3wau</a></td></tr><tr><td align="center"><strong>3. Deploy and Connect</strong></td><td><a href="/files/7X3gXtLZ8N65HcJPIy4R">/files/7X3gXtLZ8N65HcJPIy4R</a></td></tr></tbody></table>

{% hint style="success" %}
[Register your Edgegap account for free and experience our platform today](https://app.edgegap.com/auth/register), no credit card required.
{% endhint %}

## 🚀 Getting Started

Try one of our quick start guides supported with game engine plugins:

* [Unreal Engine](/unreal-engine),
* [Unity](/unity),
* Custom Runtime (coming soon).

{% hint style="success" %}
Start with a solid foundation, using one of [our free sample projects](/docs/sample-projects), easily adapted to suit your project.
{% endhint %}

## ❓ What is Game Server Hosting & Orchestration?

{% embed url="<https://youtu.be/2_vTh9b-mYw>" %}

For players to connect and play online, you will need a game server in charge of authority, game logic, and gameplay mechanics. [This can be a dedicated server (remote, cloud or bare metal) or a player host (listen server with relay, peer to peer).](https://edgegap.com/blog/explainer-series-authoritative-servers-relays-peer-to-peer-understanding-networking-types-and-their-benefits-for-each-game-types)

Edgegap orchestrate game servers worldwide across 615+ physical locations and 17+ data center providers to ensure deployment to the ideal location. Our patented strategy is the best-in-class turnkey solution to reduce latency and minimize operating (hosting) cost for game studios, thanks to our pay-per-use pricing.

## 🟢 After your first deployment

{% hint style="success" %}
See [Matchmaking](/learn/matchmaking) and [Server Browser](/learn/server-browser) to get started with game client integration.
{% endhint %}

To get the most out of Edgegap, see additional **Best Practices and Insights**:

* [prepare for launch - pre-launch checklist, including load testing](https://edgegap.com/blog/multiplayer-game-pre-launch-checklist),
* [scale to 14 million concurrent users in 60 minutes](https://edgegap.com/resources/performance-benchmark),
* [release updates with zero downtime and simplify DevOps](https://docs.edgegap.com/docs/gen2-matchmaker#rolling-updates-ab-tests).

## 📣 Community Discord

{% hint style="success" %}
[Join our Community Discord](https://discord.gg/MmJf8fWjnt) to network with studios and get support from our staff!
{% endhint %}


# Unreal Engine - Getting Started

Learn by doing and deploy your first Dedicated Server on Edgegap. By the end of this guide, you will have deployed a dedicated server with Edgegap at no cost.

Building with Docker Desktop is the fastest, easiest, and most reliable method to get started.

{% embed url="<https://youtu.be/q7ljcr9rAWE>" %}

## ✔️ Preparation

Before you get started, make sure to [create a free account with Edgegap](https://app.edgegap.com/auth/register) (no credit card required).

**Configure a few essentials on your development machine:**

<details>

<summary><a href="https://www.docker.com/products/docker-desktop/">Install Docker Desktop and Docker Edgegap Extension</a></summary>

* [Install Docker Desktop from the official source](https://www.docker.com/products/docker-desktop/) (no account required).
* Restart your computer after completing the installation.
* Navigate to Settings ⚙️ > Extensions > Enable Docker Extensions.
* [Install extension from the official docker marketplace (click this link).](https://www.docker.com/products/docker-desktop/)

</details>

<details>

<summary><a href="https://open.docker.com/extensions/marketplace?extensionId=edgegap/docker-extension">Install Edgegap Quickstart Docker Extension</a></summary>

* Install from Docker Desktop / Extensions / Browse or [using the link](https://open.docker.com/extensions/marketplace?extensionId=edgegap/docker-extension).

<figure><img src="/files/lVhxCyRW5xlTXHgHoU4n" alt=""><figcaption></figcaption></figure>

</details>

<details>

<summary><a href="https://www.unrealengine.com/en-US/ue-on-github">Gain Access to Unreal Engine resources on GitHub</a></summary>

* [Sign up for a GitHub account.](https://github.com/)
* Navigate to your [Unreal Engine linked accounts dashboard page](https://www.epicgames.com/account/connections).
* Link GitHub account with your Epic account.
  * Authorize Epic Games to access your GitHub account.
  * Accept email invitation to join Epic Games organization on GitHub.

</details>

<details>

<summary><a href="https://github.com/settings/tokens/new">Generate GitHub Personal Access Token (classic)</a></summary>

* enable only permission `[read:packages]` ,
* generate token - **store this value safely, you won't see it again**.

</details>

{% hint style="info" %}
**Confident in your server builds?** Skip to [#customize-server-image](#customize-server-image "mention") and [Advanced Features](/learn/advanced-features).
{% endhint %}

## ⚙️ 1. Configure Project <a href="#id-1-configure-project" id="id-1-configure-project"></a>

Whether you’re using a Windows, Mac, or a Linux machine, you will **need to build your server for Linux runtime**, as most cloud providers nowadays (including Edgegap) run on Linux. Don’t worry, no Linux knowledge is required.

{% hint style="info" %}
This method doesn't require downloading Unreal Engine source code, or building it from source!
{% endhint %}

☑️ Start by **verifying your Unreal Engine version** - pre-filled with value from your project files.

☑️ **Input GitHub username and** [**PAT**](#user-content-fn-1)[^1] from [#preparation](#preparation "mention"), to download dependencies from GitHub.

<details>

<summary><a href="https://github.com/settings/tokens/new">Generate GitHub Personal Access Token (classic)</a></summary>

* enable only permission `[read:packages]` ,
* generate token - **store this value safely, you won't see it again**.

</details>

☑️ **Disable Unreal Engine version compatibility check** for dedicated servers and **set `IpNetDriver` as the default driver or the fallback driver** for replication networking:

{% hint style="warning" %}
Make sure to **input your `DefaultServerTarget`  at the end of this snippet**!
{% endhint %}

<pre data-title="Config/DefaultEngine.ini"><code><a data-footnote-ref href="#user-content-fn-2">[ConsoleVariables]</a>
net.IgnoreNetworkChecksumMismatch=1
net.CurrentHandshakeVersion=2
net.MinHandshakeVersion=2
net.VerifyNetSessionID=0
net.VerifyNetClientID=0

<a data-footnote-ref href="#user-content-fn-3">[/Script/Engine.GameEngine]</a>
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemUtils.IpNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")

<a data-footnote-ref href="#user-content-fn-4">[/Script/OnlineSubsystemUtils.IpNetDriver]</a>
MaxClientRate=1000000000
MaxInternetClientRate=1000000000
InitialConnectTimeout=120.0
MaxNetTickRate=60
NetServerMaxTickRate=60

<a data-footnote-ref href="#user-content-fn-5">[/Script/BuildSettings.BuildSettings]</a>
DefaultServerTarget=LyraServer
</code></pre>

☑️ **Restart Unreal Engine** to reload latest changes.

☑️ **Create a dedicated server target script** by copying your `<PROJECT>Editor.Target.cs` file in project root folder and renaming the copy to `<PROJECT>Server.Target.cs`.

☑️ **Replace any references to** word **`Editor`** **with** **`Server`** in your server target script.

☑️ **Modify server build defaults** by editing your server target script:

<pre data-title="Source/<PROJECT>Server.Target.cs"><code>bOverrideBuildEnvironment = true;
<a data-footnote-ref href="#user-content-fn-6">bUseLoggingInShipping = true;</a>
<a data-footnote-ref href="#user-content-fn-7">bUseChecksInShipping = false;</a>
<a data-footnote-ref href="#user-content-fn-8">bBuildWithEditorOnlyData = false;</a>
<a data-footnote-ref href="#user-content-fn-9">bUsesSlate = false;</a>
<a data-footnote-ref href="#user-content-fn-10">bCompileCEF3 = false;</a>
</code></pre>

✅ You may now proceed to the next step.

<details>

<summary>Optional: Steam Integration</summary>

To **integrate Steam**, use `IpNetDriver` as your default Net Driver and verify that the [64 bit steamclient.so for Linux](https://developer.valvesoftware.com/wiki/SteamCMD#Ubuntu) is copied to image on path `/home/ubuntu/.steam/sdk64/` .

{% hint style="success" %}
Add `steamclient.so`  linux library with a single click using our [docker extension](/unreal-engine).
{% endhint %}

<a href="https://github.com/edgegap/edgegap-unreal-buildutils/raw/refs/heads/main/steamclient.so" class="button secondary" data-icon="square-down">Download steamclient.so</a>

<pre class="language-docker" data-title="Dockerfile"><code class="lang-docker">...
<a data-footnote-ref href="#user-content-fn-11">RUN mkdir -p /home/ubuntu/.steam/sdk64</a>
<a data-footnote-ref href="#user-content-fn-12">COPY ./steamclient.so /home/ubuntu/.steam/sdk64/steamclient.so</a>
<a data-footnote-ref href="#user-content-fn-13">RUN chmod 755 /home/ubuntu/.steam/sdk64/steamclient.so</a>
...
</code></pre>

{% hint style="warning" %}
**Make sure to disable steam networking, which sends packets through Steam Relay and may cause connection issues or lag spikes.** This doesn't prevent you from using other Steamworks features like Leaderboards, Achievements, Voice, or publishing your game on Steam Store.
{% endhint %}

<pre data-title="Config/DefaultEngine.ini"><code>[OnlineSubsystemSteam]
bUseSteamNetworking=false
bAllowP2PPacketRelay=false
<a data-footnote-ref href="#user-content-fn-14">SteamDevAppId=480</a>
<a data-footnote-ref href="#user-content-fn-14">SteamAppId=480</a>
</code></pre>

</details>

## 🔧 2. Build Game Server <a href="#id-2-build-game-server" id="id-2-build-game-server"></a>

Now we'll build and cook your project, and package it in an easily reusable docker image.

Working in a team of developers means sharing your code. When things go wrong, the last thing you want to hear is “it works on my machine”. Game servers have to run reliably on any machine, since a successful games’ servers will run on thousands of server machines across the world.

To help make your server reliable, we use Docker - virtualization software to ensuring that all of your server code dependencies down to the operating system level are going to be always exactly the same, no matter how or where the server is launched.

{% hint style="info" %}
We recommend watching ["Never install locally" (video)](https://www.youtube.com/watch?v=J0NuOlA2xDc\&ab_channel=Coderized). **You DON'T need to use Dockerhub with Docker**.  Docker ≠ Dockerhub. Think of Docker as a programming engine and Dockerhub as it’s App Store.
{% endhint %}

☑️ You may configure the following options (or keep defaults):

* **Image name** is a unique identifier of your choice, labeling your server build before shipping.
  * Usually, this will include the name of your game - for example “my-game-server”.
* **Image tag** is an identifier pointing to a specific version of your image.
  * The term “build artifact” is sometimes used to refer to a specific version of your image.
  * Timestamps are a great option for tagging, e.g. `2024.01.30-16.23.00-UTC`  (default).

☑️ **Build Project** once you're satisfied with your configuration. Completing this step will add a new image with your linux game server executable in your local Docker client.

✅ You may now proceed to the next step.

## 🧪 3. Test Server Locally <a href="#id-3-test-server-locally" id="id-3-test-server-locally"></a>

Let’s try deploying locally (on your machine) and connecting a game client, to make sure the server image is functioning properly before we upload and deploy (which may take a bit of time).

☑️ **Select the image tag you wish to run locally** (remote images will be downloaded). Optionally, more [docker run arguments](https://docs.docker.com/reference/cli/docker/image/build/#options) can be supplied to customize your local test:

* `-p 7777:7777/udp` - this is your local container [port mapping](/learn/orchestration/application-and-versions#port-mapping),
* `-e ARBITRIUM_PORT_GAMEPORT_INTERNAL=7777`  is an [environment variable](#environment-variables) mocking a real Edgegap deployment, telling your game server the internal port to listen on for player connections.

☑️ Once you’re happy with your configuration hit **Start Local Server**. Completing this step will result in a **new container being started** on your development machine.

☑️ Now it's time to connect your Unreal Engine Editor (PIE) game client to your local server container. Open Unreal PIE console with `~`  (tilde) and connect with `open <ip>:<port>`:

* `ip`  = `localhost`  or `127.0.0.1`  (equivalent in most cases),
* `port`  = randomized external port value of the container in Docker GUI.

<figure><img src="/files/hxCzzJZwAV68VDKInc91" alt=""><figcaption></figcaption></figure>

☑️ Once you’ve verified you’re able to connect to your local server container and play without issues, you may delete the container 🗑️ to free up resources on your machine for other programs.

✅ You may now proceed to the next step.

<details>

<summary>Troubleshooting and FAQ</summary>

Can’t connect clients to server - `Request timed out.` , `请求超时` , `ConnectionFailed` , or `Port verification failed`

* First, make sure that the container is up, and there are no runtime errors in your logs.
* Please verify that your port values in the `docker run` command match.
* Please ensure that your game client is connecting to the **external port** shown on your container details page, this value will be always randomized due to security reasons.
* Please ensure you’ve renamed your target file and configured game builds as described in step [#id-1-configure-project](#id-1-configure-project "mention").

***

My container is up but I’m not able to connect for several minutes afterwards.

* Once a container is up, your game engine initialization begins. This process may take anywhere from seconds to minutes, and the server doesn’t accept to player connections during this period.
* Consider optimizing your server initialization to decrease this time period.
* Game clients should retry connection in 1 second intervals for a limited amount of time (depending on your initialization duration), after which they should return to matchmaking.
* Consider adding a loading scene so the server can perform initialization (and travel in case of Unreal Engine) at the same time as clients, while synchronizing state of both.

***

`Warning: Could not create socket for bind address`

* Please install Epic’s Steam Subsystem plugin through Fab asset store.
* When using Edgegap Integration Kit (EGIK) with SteamCore source version downloaded from github, Epic’s Steam Subsystem plugin is not included due to Epic Games plugin distribution policies.

***

I connected but my screen is completely dark.

* Verify you have the correct **Game Default Map** set under **Edit / Project Settings / Maps & Modes**.

</details>

## ☁️ 4. Publish to Edgegap <a href="#id-4-publish-to-edgegap" id="id-4-publish-to-edgegap"></a>

It’s time to ship your server online! Now that your image can successfully host players, we can upload it to Edgegap and start running it anywhere in the world. In this guide, we’ll be using [**Edgegap’s Container Registry**](/learn/advanced-features/edgegap-container-registry) (storage for images).

☑️ **Choose an application name** to label and group similar images on Edgegap.

☑️ **Select the image tag you wish to publish** and **Upload Image**. Completing this step will result in uploading your server image to Edgeap Registry and a new [Application version being created](/learn/orchestration/application-and-versions) in your web browser. **Make sure to create your** [**port mapping**](/learn/orchestration/application-and-versions#port-mapping) **when prompted,** with the default value&#x73;**.**

{% hint style="success" %}
Found a bug and need to rebuild/publish again? Use **Rebuild from Source** to [#id-2.-build-game-server](#id-2.-build-game-server "mention") and [#id-4.-publish-to-edgegap](#id-4.-publish-to-edgegap "mention") **with the current extension input values quickly.**
{% endhint %}

✅ You may now proceed to the next step.

<details>

<summary>Troubleshooting and FAQ</summary>

`denied: adding 756.6 MiB of storage resource, which when updated to current usage of 4.3 GiB will exceed the configured upper limit of 4.7 GiB` , `failed commit on ref "layer-sha256:--------": unexpected status from PUT request to https://registry.edgegap.com/`

* Seems like you’ve run out of image storage space on [Container Registry](https://app.edgegap.com/registry-management/repositories/list). Consider removing unused build artifacts (if you have any) or optimize server build size. If using a custom Dockerfile or .dockerignore, you might be copying some unneeded files into your image.

***

`You have reached you Application limit of 2` , `Unable to update docker tag/version: You have reached you Application Version limit of 2`

* You’ve reached the limits of our Free tier, please consider upgrading your account. Alternatively, you may remove your existing resources through our [Dashboard](https://app.edgegap.com/).

***

My new application version is not listed in the plugin/extension.

* Please ensure you’ve completed app version create form in the last step.

</details>

## 🚀 5. Deploy to Cloud <a href="#id-5-deploy-to-cloud" id="id-5-deploy-to-cloud"></a>

This is the final step in this guide, after which you will have a server deployed on Edgegap cloud, to which players from anywhere in the world can connect.

☑️ **Choose an application and version** from previous step to deploy.

☑️ Once you’re ready, hit **Deploy to Cloud**, wait to reach [/pages/0UXQAhtFuL0FkdoUmYTh#id-3.-deployment-ready](https://docs.edgegap.com/pages/0UXQAhtFuL0FkdoUmYTh#id-3.-deployment-ready "mention"). Completing this step will result in a [new Deployment being started](https://app.edgegap.com/deployment-management/deployments/list) on your Edgegap account.

☑️ Verify there are no new errors in your console output. Ensure also that your [Deployments](/learn/orchestration/deployments#container-logs) don’t show any errors and your [Deployments](/learn/orchestration/deployments#container-metrics) don’t indicate 100% resource utilization (vCPU or memory), otherwise new player connections may be rejected, or your server stuck in a restart loop. See troubleshooting steps below to address any issues.

☑️ Now we’ll perform the final test and **connect your Unreal Engine Editor to your cloud deployment**. Grab your **Deployment’s Host** in place of server IP and the Deployment’s **external port**, open Unreal console in game client (tilde `~`) and type `open {host}:{port}` .

<figure><img src="/files/BwcOdlJqXkoYOKEKZU4u" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
The external port of your Deployment on Edgegap cloud will be chosen at random, so that a potential attacker (hacker) is slowed down and detected before they can cause damage.
{% endhint %}

{% hint style="warning" %}
**Disable VPN when testing** for more realistic conditions and receive a [low-latency deployment](/learn/orchestration/deployments#server-placement).
{% endhint %}

☑️ Once you verify you’re able to connect to your Deployment without issues and are done testing, **Stop your Deployment** to free up capacity in your account for the next build.

* In case you encounter issues, [inspect Dashboard logs of your deployment](https://app.edgegap.com/deployment-management/deployments/list).
* If you can’t figure out the issue, we’re hanging out in our [Community Discord](https://discord.gg/NgCnkHbsGp) and happy to help.

🙌 Congratulations on your first Deployment on Edgegap! If you’d like to learn more, keep reading.

<details>

<summary>Troubleshooting and FAQ</summary>

Can’t connect clients to server - `Request timed out.` , `请求超时` , `ConnectionFailed` , or `Port verification failed`

* First, make sure that the deployment is Ready, and there are no runtime exceptions or errors in your deployment log. If your deployment stopped, inspect logs in our [Dashboard](https://app.edgegap.com/deployment-management/deployments/list).
* Please verify that your port setting in your server build’s netcode settings matches the internal port in your [App version](https://app.edgegap.com/application-management/applications/list). For plugin builds, the port is set for you automatically. You can change the port mapping by editing the [App version](https://app.edgegap.com/application-management/applications/list) without rebuilding. Find your protocol in your netcode integration.
* Please ensure that your game client is connecting to the **external port** shown on your Deployment details page, this value will be always randomized due to security reasons.
* Please ensure you’ve renamed your target file and configured game builds as described in step [#id-1.-configure-project](#id-1.-configure-project "mention").
* Are you located in China and are using [Smart Fleets](https://docs.edgegap.com/docs/deployment/session/fleet-manager/fleet)? Your connection may be blocked by the Great Firewall. Consider adding a server located in China to your fleet, or using a VPN to connect.

***

My deployment is ready but I’m not able to connect for several minutes afterwards.

* Once a deployment is Ready, your game engine initialization begins. This process may take anywhere from seconds to minutes, and the server doesn’t accept to player connections during this period.
* Consider optimizing your server initialization to decrease this time period.
* Game clients should retry connection in 1 second intervals for a limited amount of time (depending on your initialization duration), after which they should return to matchmaking.
* Consider adding a loading scene so the server can perform initialization (and travel in case of Unreal Engine) at the same time as clients, while synchronizing state of both.

***

`Warning: Could not create socket for bind address`

* Please install Epic’s Steam Subsystem plugin through Fab asset store.
* When using Edgegap Integration Kit (EGIK) with SteamCore Integration Kit (SIK) source version downloaded from github, Epic’s Steam Subsystem plugin is not included due to Epic Games plugin distribution policies.

***

I connected but my screen is completely dark.

* Verify you have the correct **Game Default Map** set under **Edit / Project Settings / Maps & Modes**.
* Verify that the [Unreal Engine version compatibility check has been disabled](#id-2.-configure-game-server-builds) in `DefaultEngine.ini`.

***

My deployment stopped/restarted and I can’t access it’s logs anymore.

* In case the server process crashes due to an exception, our system will attempt restarting the server automatically. Consider testing your server locally to uncover the root cause.
* We only keep logs for the duration of the deployment, if you wish to inspect logs after deployment stops, please [integrate a third party log storage](https://docs.edgegap.com/docs/deployment/endpoint-storage).
* See [/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped](https://docs.edgegap.com/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped "mention") to discover all causes for stopping your deployment.

***

My deployment stopped automatically after X minutes.

* Free Tier deployments have a 60 minute limit, please consider upgrading your account.
* All deployments will be terminated after 24 hours of runtime following our server sanitization policy, for infrastructure maintenance, and to prevent racking up unexpected costs when deployment wasn’t shut down properly. For long-running servers, consider using [Private Fleets](/learn/orchestration/private-fleets) with [Persistence](/learn/orchestration/persistence).
* See [/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped](https://docs.edgegap.com/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped "mention") to discover all causes for stopping your deployment.

***

What will happen if a player leaves my deployment?

* By default, servers don’t reject player connections. Authenticating players is up to your devs, since many different methods and player authentication providers can be used.
* Game clients may store connection information locally to attempt reconnecting in case of unexpected client crashes.
* To allow players to join games in progress, consider using [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#backfill) or [Sessions](https://docs.edgegap.com/docs/deployment/session).

***

My server shows 100% CPU utilization after becoming ready.

* This may not be an issue, as game engines tend to perform CPU-heavy operations during server initializations. If the CPU usage doesn’t drop after 2-3 minutes from deployment start, you may need to optimize your server or increase app version resources.
* Reducing tick rate can help control CPU usage due to processing less messages.
* You’re limited to 1.5 vCPU and 3GB of memory (RAM) in Free Tier.
* You may increase allocated resources when creating a new app version. You can duplicate your App version in our Dashboard and adjust these values as needed, without rebuilding your server or image.

***

My deployment is restarting repeatedly and shows error `OOM kill`

* This is caused by exceeding allocated memory amount. Consider optimizing memory usage with object pooling, compression, or removing unneeded objects in your scene.
* You’re limited to 1.5 vCPU and 3GB of memory (RAM) in Free Tier.
* You may increase allocated resources when creating a new app version. You can duplicate your App version in our Dashboard and adjust these values as needed, without rebuilding your server or image.

***

Sometimes, my server’s memory (RAM) usage spikes to a high value, is that a problem?

* As long as you stay within the allocated app version memory amount, this is not an issue.
* Exceeding the allocated app version memory amount will cause `OOM kill` (see above).

***

Will my server performance be impacted by other servers running on the same machine?

* No, our platform ensures that allocated resources will not be used by other studios, or other servers on shared infrastructure. With Edgegap, there are no noisy neighbors.

</details>

## 👉 Next Steps

Once you have a working client/server setup, make sure to **save a copy of your project** (using version control software like git) so you can always trace back your steps in case you run into issues.

Continue reading to learn more about topics related to server lifecycle and discoverability.

### Stop Deployments

Once the match ends (or players leave), your deployment can be stopped to save cost. [Running empty or only partially filled can increase your cost unnecessarily!](https://edgegap.com/blog/how-session-fill-rate-affects-your-multiplayer-hosting-costs)

If you followed this guide and built with our Docker Extension, you can simply call method `FGenericPlatformMisc::RequestExit` . We've added a script managing your server process in the packaged image, which will automatically perform a graceful deployment shutdown.

To customize server lifecycle management, modify our [example `StartServer.sh`](https://github.com/edgegap/edgegap-unreal-buildutils/blob/main/StartServer.sh)  script.

{% hint style="info" %}
Prefer managing lifecycle from Unreal? See [Developer Tools](/unreal-engine/developer-tools#integration-kit) for self-stop API blueprint.
{% endhint %}

{% hint style="warning" %}
Connect your [Endpoint Storage](/docs/endpoint-storage) to get your deployment logs, otherwise they will be deleted!
{% endhint %}

### Injected Variables

Read useful information like deployment ID, server IP address, server location, and more; by accessing injected environment variables. Each deployment automatically includes:

* [Deployment Variables](/learn/orchestration/deployments#injected-environment-variables) - automatically supplied by Edgegap,
* [Matchmaking Variables](/learn/matchmaking/matchmaker-in-depth#injected-environment-variables) - automatically supplied by Edgegap when using [Matchmaking](/learn/matchmaking),
* [App Version Variables](/learn/orchestration/application-and-versions#injected-variables) - custom key-value pairs configurable by you.

{% hint style="success" %}
Import our [Developer Tools](/unreal-engine/developer-tools#integration-kit) to **easily read typed variables using Blueprints**.
{% endhint %}

### Profiling Servers

To understand and optimize server performance issues on Edgegap, explore [Deployments](/learn/orchestration/deployments#container-logs), [Deployments](/learn/orchestration/deployments#container-metrics), and more [Deployments](/learn/orchestration/deployments#dashboard-monitoring) tools at your disposal.

You can also use existing Unreal Engine profiling tools with Edgegap:

* [Configure tracing in your Unreal Engine server](https://dev.epicgames.com/documentation/en-us/unreal-engine/developer-guide-to-tracing-in-unreal-engine) (built-in and custom events):
  * save trace on server disk with `-tracefile`, upload to 3rd party storage and analyze offline,
  * or stream trace data with [Deployments](/learn/orchestration/deployments#port-mapping) for internal port `1981` over UDP protocol.
* Analyze [Memory Insights](https://dev.epicgames.com/documentation/en-us/unreal-engine/memory-insights-in-unreal-engine) and [Networking Insights](https://dev.epicgames.com/documentation/en-us/unreal-engine/networking-insights-in-unreal-engine) and [#optimize-server-builds](#optimize-server-builds "mention").

### Session Automation

{% hint style="warning" %}
**Starting your Deployments manually, pasting URL and ports will not cut it for a live game.**
{% endhint %}

Automate popular game flows for managing sessions and scaling on demand with either:

{% columns %}
{% column width="33.33333333333333%" %}
[Matchmaking](/learn/matchmaking):

* Shorter Rounds
* On-Demand Matches
* Skill Rating and/or Custom Rules
  {% endcolumn %}

{% column width="33.33333333333333%" %}
[Server Browser](/learn/server-browser):

* Persistent or Rounds
* Social Regional Hubs
* Auto-Assign and/or\
  Custom Search
  {% endcolumn %}

{% column width="33.33333333333333%" %}
Custom Backend:

* Migrate Live Games
* [Deploy with v2 API](/docs/api/dedicated-servers)
* [Observe Webhooks](/learn/orchestration/deployments#webhooks)
  {% endcolumn %}
  {% endcolumns %}

### Optimize Builds

**Configure asset chunking to isolate client-only assets from server assets.**

* Explore [Asset Chunking techniques and recommendations](https://dev.epicgames.com/documentation/en-us/unreal-engine/preparing-assets-for-chunking-in-unreal-engine) by Epic.

**Exclude assets and plugins which are client-only, and not required for server to run.**

* Learn about [build-time asset and plugin exclusion](https://dev.epicgames.com/community/learning/tutorials/Kp1k/unreal-engine-build-time-asset-and-plugin-exclusion).

**Review your content cooking strategy.**

* Consider [Cooking on the Fly (COTF)](https://dev.epicgames.com/documentation/en-us/unreal-engine/build-operations-cooking-packaging-deploying-and-running-projects-in-unreal-engine#cookonthefly) to delay cooking client assets and speed up server builds.

**Implement Level Streaming to reduce runtime memory load.**

* If your design keeps players mostly together in the same map area, [level streaming may cut down your server's memory usage](https://dev.epicgames.com/documentation/en-us/unreal-engine/level-streaming-in-unreal-engine) by more than 60%, and improve client performance!

**Only include what you absolutely need for your server to run.**

* Copying unused files in your images results in image bloat, longer uploads, slower caching speeds, and slower overall server startup. [Review Docker image optimization suggestions](https://docs.docker.com/build-cloud/optimization/#dockerignore-files).

<details>

<summary>Example <code>.dockerignore</code> file to remove extra files.</summary>

```docker
# Compiled Object files
*.slo
*.lo
*.o
*.obj

# Precompiled Headers
*.gch
*.pch

# Compiled Dynamic libraries
*.so
*.dylib
*.dll

# Fortran module files
*.mod

# Compiled Static libraries
*.lai
*.la
*.a
*.lib

# Executables
*.exe
*.out
*.app
*.ipa

# These project files can be generated by the engine
*.xcodeproj
*.xcworkspace
*.sln
*.suo
*.opensdf
*.sdf
*.VC.db
*.VC.opendb

# Precompiled Assets
**/SourceArt/**/*.png
**/SourceArt/**/*.tga

# Builds
**/Build/*

# Whitelist PakBlacklist-<BuildConfiguration>.txt files
!**/Build/*/
**/Build/*/**
!**/Build/*/PakBlacklist*.txt

# Don't ignore icon files in Build
!**/Build/**/*.ico

# Configuration files generated by the Editor
**/Saved/*
**/Intermediate/*
**/DerivedDataCache/*
**/Binaries/*
**/Build/*
**/Releases/*
**/Packaged/*
```

</details>

**Consider using** [**multi-stage Docker builds (link)**](https://docs.docker.com/build/building/multi-stage/)**.**

* Separate large server dependencies to a separate image to reuse in multi-stage builds. Docker will cache each layer and simply reuse the previous version and skip uploading this part unless specifically instructed to do so, saving you bandwidth and time waiting for the upload to finish.
* If you’re not sure why one of your Dockerfile commands throws an error, try debugging locally. Create a new stage just before the issue happens (add a second `FROM` command), use `--target` to instruct the build process to stop at the problematic stage, and then `docker exec -it {container} /bin/bash` to enter interactive terminal inside your container. Afterwards, you can use shell commands in your base image to investigate further (e.g. `top` on ubuntu).

### Customize Image

We also support adding your own Dockerfile for users who need more control over their images due to build size optimization, extraneous dependencies, or requiring more complex startup process. We’ll now share a few “do it yourself” tips and best practices.

**Always make sure you are working with a functioning server build.**

* Before assuming an issue is related to the custom Dockerfile, ensure your Unity server build can be started, and that the build process in Unity didn’t throw any exceptions or errors.

**Always test locally before uploading.**

* Testing your image locally will save you lots of time while waiting for the upload to finish. It’s also entirely free ✨ as it doesn’t require any Edgegap resources.
* When testing locally, make sure to set your internal port correctly:

  ```bash
  docker run \
    -p 7777/udp \
    -e ARBITRIUM_PORTS_MAPPING='{"ports":{"gameport":{"internal":7777}}}' \
    'registry.edgegap.com/<repository>:<tag>'
  ```

**Make sure you’ve got the basics down. Every Dockerfile needs a few essential commands:**

* `FROM {image}` is your base image, for Unity projects we usually use a long-term supported Linux, but any Linux-based base image will do. These are usually public images stored on dockerhub. Dockerfile reference here. [Dockerfile reference here](https://docs.docker.com/reference/dockerfile/#from).
* `COPY {source} {destination}` to copy your linux server build from your host machine inside the image, so you can start it later on. [Dockerfile reference here](https://docs.docker.com/reference/dockerfile/#copy).
* `USER {user}` should follow after a [useradd (ubuntu) command](https://manpages.ubuntu.com/manpages/bionic/man8/useradd.8.html) or equivalent, it’s best not to run everything as `root` to be on the safer side. [Dockerfile reference here](https://docs.docker.com/reference/dockerfile/#user).
* `CMD {command}` will be the last line, most likely calling a `StartServer.sh` or some kind of startup script to make sure your server initializes correctly once everything is set up. [Dockerfile reference here](https://docs.docker.com/reference/dockerfile/#cmd).
* do NOT use `VOLUME` - you will not be able to mount any local storage this way on Edgegap, consider our Endpoint Storage feature instead and use an S3 bucket, see [Endpoint Storage](https://docs.edgegap.com/docs/deployment/endpoint-storage),
* `EXPOSE 7777/UDP`  is not required! This will not actually make the internal server port available from outside the container, it's only a hint for the developer and the port needs to be
  * published when testing locally with `docker run <image> -p 7777/udp` ,
  * or mapped in [Edgegap Port Mapping](/learn/orchestration/application-and-versions#other-parameters-optional).

**Delay declaration of parameters until latest possible moment. Configurability > composability due to long server build times.** [**Apply this approach to Dockerfile commands to build and upload faster.**](https://medium.com/@esotericmeans/optimizing-your-dockerfile-dc4b7b527756)

* Scenario: you need to define parameters like deployment stage, version, game mode, map, player count per server, backup frequency, or similar.
* Bad solution: creating a separate image for every combination of your parameters. You will spend all of your time rebuilding the images with very little benefits from this approach.
* Better solution - substitute configuration parameters just in time:
  1. deployment parameters - supplied just before the deployment is made - matchmaking selectors passed as environment variables, or your custom session management system passing environment variables at deployment time,
  2. version parameters - shared for all deployments on an app version - deployment stage, artifact tag, third party secrets and endpoints, and similar; then
  3. one single image - contains and loads all configuration options when launched.

**Do NOT run databases on Edgegap deployments.**

* Edgegap deployments are not intended for long-running processes and may be terminated after a long period of runtime without prior notice. A database (even if distributed) running in this manner may be terminated and result in an irreversible loss of data. If you need a database, please consider a third party DBaaS.
* Consider using our [Managed Clusters](https://app.edgegap.com/cluster-management/clusters/list) for hosting databases and long running services.

[^1]: Personal Access Token

[^2]: disable compatibility check

[^3]: use IpNetDriver

[^4]: set client/server tick rates and timeout

[^5]: set server build as default target

[^6]: enable logs

[^7]: don't use checks

[^8]: don't package editor content

[^9]: don't package Slate UI (editor)

[^10]: don't compile CEF3 (editor)

[^11]: create parent directory

[^12]: copy the steamclient.so file

[^13]: allow executing steam client

[^14]: find in your steam publisher portal


# More Build Methods

These approaches are generally slower and require deeper understanding of Unreal Engine.

{% hint style="success" %}
See [Unreal Engine](/unreal-engine) for your first steps with Unreal Engine game server hosting on Edgegap.
{% endhint %}

Explore alternative build methods suited for more advanced Unreal Engine and Edgegap users:

1. [#build-with-scripts](#build-with-scripts "mention") is a fast and automated method suitable for Continuous Integration.
2. [#build-from-plugin](#build-from-plugin "mention") is a legacy build method requiring building Unreal Engine from source.

## ⚡ Build with Scripts

Building with scripts is a fast, fully automated, and easy method suitable for Continuous Integration.

### Preparation <a href="#scripts-before-starting" id="scripts-before-starting"></a>

Before you get started, make sure to [create a free account with Edgegap](https://app.edgegap.com/auth/register) (no credit card required).

**Configure a few essentials on your development machine:**

<details>

<summary><a href="https://www.docker.com/products/docker-desktop/">Install Docker Desktop (or Docker CLI)</a></summary>

* [Install Docker Desktop from the official source](https://www.docker.com/products/docker-desktop/) (no account required).
* Restart your computer after completing the installation.

</details>

<details>

<summary><a href="https://www.unrealengine.com/en-US/ue-on-github">Gain Access to Unreal Engine resources on GitHub</a></summary>

* [Sign up for a GitHub account.](https://github.com/)
* Navigate to your [Unreal Engine linked accounts dashboard page](https://www.epicgames.com/account/connections).
* Link GitHub account with your Epic account.
  * Authorize Epic Games to access your GitHub account.
  * Accept email invitation to join Epic Games organization on GitHub.

</details>

<details>

<summary><a href="https://github.com/settings/tokens/new">Generate GitHub Personal Access Token (classic)</a></summary>

* enable only permission `[read:packages]` ,
* generate token - **store this value safely, you won't see it again**.

</details>

### 1. Configure Project <a href="#scripts-configure-game-builds" id="scripts-configure-game-builds"></a>

Whether you’re using a Windows, Mac, or a Linux machine, you will **need to build your server for Linux runtime**, as most cloud providers nowadays (including Edgegap) run on Linux. Don’t worry, no Linux knowledge is required.

☑️ **Disable Unreal Engine version compatibility check** for dedicated servers and **set `IpNetDriver` as the default driver or the fallback driver** for replication networking:

{% hint style="warning" %}
Make sure to **input your `DefaultServerTarget`  at the end of this snippet**!
{% endhint %}

<pre data-title="Config/DefaultEngine.ini"><code><a data-footnote-ref href="#user-content-fn-1">[ConsoleVariables]</a>
net.IgnoreNetworkChecksumMismatch=1
net.CurrentHandshakeVersion=2
net.MinHandshakeVersion=2
net.VerifyNetSessionID=0
net.VerifyNetClientID=0

<a data-footnote-ref href="#user-content-fn-2">[/Script/Engine.GameEngine]</a>
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemUtils.IpNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")

<a data-footnote-ref href="#user-content-fn-3">[/Script/OnlineSubsystemUtils.IpNetDriver]</a>
MaxClientRate=1000000000
MaxInternetClientRate=1000000000
InitialConnectTimeout=120.0
MaxNetTickRate=60
NetServerMaxTickRate=60

<a data-footnote-ref href="#user-content-fn-4">[/Script/BuildSettings.BuildSettings]</a>
DefaultServerTarget=LyraServer
</code></pre>

☑️ **Restart Unreal Engine** to reload latest changes.

☑️ **Create a dedicated server target script** by copying your `<PROJECT>Editor.Target.cs` file in project root folder and renaming the copy to `<PROJECT>Server.Target.cs`.

☑️ **Replace any references to** word **`Editor`** **with** **`Server`** in your server target script.

☑️ **Modify server build defaults** by editing your server target script:

<pre data-title="Source/<PROJECT>Server.Target.cs"><code>bOverrideBuildEnvironment = true;
<a data-footnote-ref href="#user-content-fn-5">bUseLoggingInShipping = true;</a>
<a data-footnote-ref href="#user-content-fn-6">bUseChecksInShipping = false;</a>
<a data-footnote-ref href="#user-content-fn-7">bBuildWithEditorOnlyData = false;</a>
<a data-footnote-ref href="#user-content-fn-8">bUsesSlate = false;</a>
<a data-footnote-ref href="#user-content-fn-9">bCompileCEF3 = false;</a>
</code></pre>

✅ You may now proceed to the next step.

<details>

<summary>Optional: Steam Integration</summary>

To **integrate Steam**, use `IpNetDriver` as your default Net Driver and verify that the [64 bit steamclient.so for Linux](https://developer.valvesoftware.com/wiki/SteamCMD#Ubuntu) is copied to image on path `/home/ubuntu/.steam/sdk64/` .

{% hint style="success" %}
Add `steamclient.so`  linux library with a single click using our [docker extension](/unreal-engine).
{% endhint %}

<a href="https://github.com/edgegap/edgegap-unreal-buildutils/raw/refs/heads/main/steamclient.so" class="button secondary" data-icon="square-down">Download steamclient.so</a>

<pre class="language-docker" data-title="Dockerfile"><code class="lang-docker">...
<a data-footnote-ref href="#user-content-fn-10">RUN mkdir -p /home/ubuntu/.steam/sdk64</a>
<a data-footnote-ref href="#user-content-fn-11">COPY ./steamclient.so /home/ubuntu/.steam/sdk64/steamclient.so</a>
<a data-footnote-ref href="#user-content-fn-12">RUN chmod 755 /home/ubuntu/.steam/sdk64/steamclient.so</a>
...
</code></pre>

{% hint style="warning" %}
**Make sure to disable steam networking, which sends packets through Steam Relay and may cause connection issues or lag spikes.** This doesn't prevent you from using other Steamworks features like Leaderboards, Achievements, Voice, or publishing your game on Steam Store.
{% endhint %}

<pre data-title="Config/DefaultEngine.ini"><code>[OnlineSubsystemSteam]
bUseSteamNetworking=false
bAllowP2PPacketRelay=false
<a data-footnote-ref href="#user-content-fn-13">SteamDevAppId=480</a>
<a data-footnote-ref href="#user-content-fn-13">SteamAppId=480</a>
</code></pre>

</details>

### 2. Build and Publish <a href="#scripts-build-and-upload-to-edgegap" id="scripts-build-and-upload-to-edgegap"></a>

Working in a team of developers means sharing your code. When things go wrong, the last thing you want to hear is “it works on my machine”. Game servers have to run reliably on any machine, since a successful games’ servers will run on thousands of server machines across the world.

To help make your server reliable, we use Docker - virtualization software to ensuring that all of your server code dependencies down to the operating system level are going to be always exactly the same, no matter how or where the server is launched.

{% hint style="info" %}
We recommend watching ["Never install locally" (video)](https://www.youtube.com/watch?v=J0NuOlA2xDc\&ab_channel=Coderized). **You DON'T need to use Dockerhub with Docker**.  Docker ≠ Dockerhub. Think of Docker as a programming engine and Dockerhub as it’s App Store.
{% endhint %}

☑️ **Verify that Docker is installed and running.**

{% embed url="<https://github.com/edgegap/edgegap-unreal-buildutils>" %}

☑️ **Download our Edgegap Build Utils** including:

* platform-specific build scripts:
  * `BuildAndUpload.ps1`  for Windows,
  * `BuildAndUpload.sh`  for macOS and Linux,
* `Dockerfile`  - recipe for building your docker images,
* `dockerignore`  - list of noncritical files to remove and speed up builds,
* `StartServer.sh`  - utility script managing your Unreal Engine lifecycle on runtime.

☑️ **Move the `edgegap-unreal-buildutils` folder to your project root directory.**

☑️ **Edit the `BuildAndUpload`  script for your platform** to configure:

* github credentials used to pull pre-built Unreal Engine images for Linux Servers,
* project details - engine version, server configuration, .uproject file name,
* [Edgegap Registry](https://app.edgegap.com/registry-management/repositories/list) credentials used to upload your completed builds.

☑️ **Execute the edited script** to start build and upload process. Completing this step will add a new **image in your** [Edgegap Container Registry dashboard page in your Repository](https://app.edgegap.com/registry-management/repositories/list).

☑️ You will be automatically redirected to **create a new** [Apps and Versions](/learn/orchestration/application-and-versions#app-versions) in [dashboard](https://app.edgegap.com/application-management/applications/list)**.**

✅ You may now proceed to the next step, skip to [#test-your-server-locally](#test-your-server-locally "mention").

<details>

<summary>Troubleshooting and FAQ</summary>

`ERROR: failed to run Build function: the Dockerfile cannot be empty`

* Some plugin sources may be missing `Dockerfile` and/or `StartServer.sh` , please find a copy these files from github source version of your plugin to your local plugin.

***

`open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified.`

* Verify that Docker is running on your development machine.

***

`invalid reference format EdgegapLog: Warning: OnContainerizeCallback: Could not generate container, message:Failed`

* Verify that your application name is set and doesn't contain trailing whitespace.
* Make sure to press Enter to confirm the input value when done editing.

***

[**Couldn’t find target rules file for target ‘ProjectName’ when building a project?**](https://forums.unrealengine.com/t/how-may-i-resolve-an-error-couldnt-find-target-rules-file-for-target-projectname-when-building-a-project/411707)

* Change build configuration in your `XYZServerTarget.cs` file:

```
BuildConfig = EProjectPackagingBuildConfigurations::PPBC_Shipping;
```

***

`403 Forbidden, Could not push container, message:Failed`

* Please verify your docker credentials are correctly set.
* If using Edgegap Container Registry, ensure the username doesn’t include `app-version-pull` , which is the profile only allowing pulling images. Your profile should resemble your account email instead.
* Verify that you have enough free space in your account’s [Container Registry](https://app.edgegap.com/registry-management/repositories/list). We recommend trying to [#optimize-server-build-size](#optimize-server-build-size "mention"), and using [our API to delete older images](https://docs.edgegap.com/api/#tag/Container-Registry/operation/image-tag-delete). Images are stored on your local machine until deleted for backups.

</details>

## ⚡ Build from Plugin <a href="#build-from-plugin" id="build-from-plugin"></a>

Our legacy plugin includes advanced utilities, and requires building Unreal Engine from source.

### Preparation <a href="#plugin-before-starting" id="plugin-before-starting"></a>

Before you get started, make sure to [create a free account with Edgegap](https://app.edgegap.com/auth/register) (no credit card required).

**Configure a few essentials on your development machine:**

<details>

<summary><a href="https://www.docker.com/products/docker-desktop/">Install Docker Desktop (or Docker CLI)</a></summary>

* [Install Docker Desktop from the official source](https://www.docker.com/products/docker-desktop/) (no account required).
* Restart your computer after completing the installation.

</details>

<details>

<summary><a href="https://www.unrealengine.com/en-US/ue-on-github">Gain Access to Unreal Engine resources on GitHub</a></summary>

* [Sign up for a GitHub account.](https://github.com/)
* Navigate to your [Unreal Engine linked accounts dashboard page](https://www.epicgames.com/account/connections).
* Link GitHub account with your Epic account.
  * Authorize Epic Games to access your GitHub account.
  * Accept email invitation to join Epic Games organization on GitHub.

</details>

<details>

<summary>Configure Your Development Environment</summary>

* [Download Unreal Engine source code](https://dev.epicgames.com/documentation/en-us/unreal-engine/downloading-unreal-engine-source-code#downloadingthesourcecode).
* [Set up Visual Studio](https://dev.epicgames.com/documentation/en-us/unreal-engine/setting-up-visual-studio-development-environment-for-cplusplus-projects-in-unreal-engine).

</details>

<details>

<summary>Install an Official Edgegap Unreal Engine Plugin</summary>

Choose one of the official plugins:

* download [Developer Tools](/unreal-engine/developer-tools#integration-kit),
* or download [Developer Tools](/unreal-engine/developer-tools#legacy-plugin).

</details>

### 1. Configure Project <a href="#scripts-configure-game-builds" id="scripts-configure-game-builds"></a>

Whether you’re using a Windows, Mac, or a Linux machine, you will **need to build your server for Linux runtime**, as most cloud providers nowadays (including Edgegap) run on Linux. Don’t worry, no Linux knowledge is required.

☑️ [Build your Unreal Engine version from source](https://dev.epicgames.com/documentation/en-us/unreal-engine/building-unreal-engine-from-source) on your development machine,

* install [specific release branch (e.g. ](https://github.com/EpicGames/UnrealEngine/tree/5.5)[`5.5`](https://github.com/EpicGames/UnrealEngine/tree/5.5)[)](https://github.com/EpicGames/UnrealEngine/tree/5.5) to build on a stable foundation,
* **use Solid State Drive (SSD)** to speed up builds (from \~12+ hours to \~2+ hours),
* this is only required the first time and every time you upgrade your Unreal Engine version.

{% hint style="warning" %}
**Download branch with a git client!** Using github UI will always download the unstable `release` branch.
{% endhint %}

☑️ [Install Unreal Cross-Compiling Toolchain](https://dev.epicgames.com/documentation/en-us/unreal-engine/linux-development-requirements-for-unreal-engine) to build game servers for Linux.

☑️ **Restart your development machine**, otherwise you’ll run into errors later on!

☑️ **Disable Unreal Engine version compatibility check** for dedicated servers and **set `IpNetDriver` as the default driver or the fallback driver** for replication networking:

{% hint style="warning" %}
Make sure to **input your `DefaultServerTarget`  at the end of this snippet**!
{% endhint %}

<pre data-title="Config/DefaultEngine.ini"><code><a data-footnote-ref href="#user-content-fn-1">[ConsoleVariables]</a>
net.IgnoreNetworkChecksumMismatch=1
net.CurrentHandshakeVersion=2
net.MinHandshakeVersion=2
net.VerifyNetSessionID=0
net.VerifyNetClientID=0

<a data-footnote-ref href="#user-content-fn-2">[/Script/Engine.GameEngine]</a>
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemUtils.IpNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")

<a data-footnote-ref href="#user-content-fn-3">[/Script/OnlineSubsystemUtils.IpNetDriver]</a>
MaxClientRate=1000000000
MaxInternetClientRate=1000000000
InitialConnectTimeout=120.0
MaxNetTickRate=60
NetServerMaxTickRate=60

<a data-footnote-ref href="#user-content-fn-4">[/Script/BuildSettings.BuildSettings]</a>
DefaultServerTarget=LyraServer
</code></pre>

☑️ **Restart Unreal Engine** to reload latest changes.

☑️ **Create a dedicated server target script** by copying your `<PROJECT>Editor.Target.cs` file in project root folder and renaming the copy to `<PROJECT>Server.Target.cs`.

☑️ **Replace any references to** word **`Editor`** **with** **`Server`** in your server target script.

☑️ **Modify server build defaults** by editing your server target script:

<pre data-title="Source/<PROJECT>Server.Target.cs"><code>bOverrideBuildEnvironment = true;
<a data-footnote-ref href="#user-content-fn-5">bUseLoggingInShipping = true;</a>
<a data-footnote-ref href="#user-content-fn-6">bUseChecksInShipping = false;</a>
<a data-footnote-ref href="#user-content-fn-7">bBuildWithEditorOnlyData = false;</a>
<a data-footnote-ref href="#user-content-fn-8">bUsesSlate = false;</a>
<a data-footnote-ref href="#user-content-fn-9">bCompileCEF3 = false;</a>
</code></pre>

✅ You may now proceed to the next step.

<details>

<summary>Optional: Steam Integration</summary>

To **integrate Steam**, use `IpNetDriver` as your default Net Driver and verify that the [64 bit steamclient.so for Linux](https://developer.valvesoftware.com/wiki/SteamCMD#Ubuntu) is copied to image on path `/home/ubuntu/.steam/sdk64/` .

{% hint style="success" %}
Add `steamclient.so`  linux library with a single click using our [docker extension](/unreal-engine).
{% endhint %}

<a href="https://github.com/edgegap/edgegap-unreal-buildutils/raw/refs/heads/main/steamclient.so" class="button secondary" data-icon="square-down">Download steamclient.so</a>

<pre class="language-docker" data-title="Dockerfile"><code class="lang-docker">...
<a data-footnote-ref href="#user-content-fn-10">RUN mkdir -p /home/ubuntu/.steam/sdk64</a>
<a data-footnote-ref href="#user-content-fn-11">COPY ./steamclient.so /home/ubuntu/.steam/sdk64/steamclient.so</a>
<a data-footnote-ref href="#user-content-fn-12">RUN chmod 755 /home/ubuntu/.steam/sdk64/steamclient.so</a>
...
</code></pre>

{% hint style="warning" %}
**Make sure to disable steam networking, which sends packets through Steam Relay and may cause connection issues or lag spikes.** This doesn't prevent you from using other Steamworks features like Leaderboards, Achievements, Voice, or publishing your game on Steam Store.
{% endhint %}

<pre data-title="Config/DefaultEngine.ini"><code>[OnlineSubsystemSteam]
bUseSteamNetworking=false
bAllowP2PPacketRelay=false
<a data-footnote-ref href="#user-content-fn-13">SteamDevAppId=480</a>
<a data-footnote-ref href="#user-content-fn-13">SteamAppId=480</a>
</code></pre>

</details>

### 2. Build and Publish <a href="#plugin-build-and-upload-to-edgegap" id="plugin-build-and-upload-to-edgegap"></a>

Working in a team of developers means sharing your code. When things go wrong, the last thing you want to hear is “it works on my machine”. Game servers have to run reliably on any machine, since a successful games’ servers will run on thousands of server machines across the world.

To help make your server reliable, we use Docker - virtualization software to ensuring that all of your server code dependencies down to the operating system level are going to be always exactly the same, no matter how or where the server is launched.

{% hint style="info" %}
We recommend watching ["Never install locally" (video)](https://www.youtube.com/watch?v=J0NuOlA2xDc\&ab_channel=Coderized). **You DON'T need to use Dockerhub with Docker**.  Docker ≠ Dockerhub. Think of Docker as a programming engine and Dockerhub as it’s App Store.
{% endhint %}

☑️ **Verify that Docker is installed and running.**

☑️ [Rebuild our plugin](https://dev.epicgames.com/community/learning/tutorials/qz93/unreal-engine-building-plugins) for your custom Unreal Engine version built from source.

☑️ **Copy compiled plugin** to your `Plugins` folder **in the root of your Unreal project** (not engine).

☑️ **Launch your new Unreal Engine** from Visual Studio and **open toolbar item Edit / Plugins**.

☑️ **Enable our plugin** in section **INSTALLED / Other**.

☑️ **Configure our plugin** by opening toolbar item **Edit / Project Settings / Edgegap**:

{% hint style="warning" %}
Always press enter after editing input values to **ensure they are saved correctly**.
{% endhint %}

* **API Token** is needed to upload your server to Edgegap, get one by clicking Get Token.
* **Application name** on Edgegap can match your project name or be customized, make sure to only use lowercase letters, numbers, or characters dash `-` and underscore `_`.
* **Image Path** provides optionally a custom icon for your game server on Edgegap, skip for now.
* **Version name** is useful for tracking client/server compatibility and rolling back in case of issues.
  * Timestamps are a great option for app version names, e.g. `2024.01.30-16.50.20-UTC` .
  * Multiple application versions may point to the same image tag, such as `v1.1.0` and `dev` .
  * Learn more about [Apps and Versions](/learn/orchestration/application-and-versions) later.

{% hint style="warning" %}
**Do not reuse `latest` version** to prevent our system from deploying outdated (cached) image.
{% endhint %}

☑️ Click **Create Application.** Completing this step will result in a **new application appearing** in [Edgegap Dashboard](https://app.edgegap.com/application-management/applications/list).

☑️ Skip custom container registry settings for now, you can use third party registry later if you’d like.

☑️ Once you’re happy with your configuration hit **Build and Push**, wait for the process to finish and verify there are no new errors in your Unreal console. Completing this step will result in a **new folder appearing in your project root** - `Saved/LinuxServer`. In addition, a **new image appears now in your** [Edgegap Container Registry dashboard page underneath your Repository](https://app.edgegap.com/registry-management/repositories/list), and a new [Apps and Versions](/learn/orchestration/application-and-versions#app-versions) **appears in your** [dashboard underneath your Application](https://app.edgegap.com/application-management/applications/list)**.**

☑️ In your new application version, set environment variable `TARGET_FILE_NAME`  to match your `DefaultServerTarget`  value from step [#id-2.-configure-game-server-builds-1](#id-2.-configure-game-server-builds-1 "mention").

✅ You may now proceed to the next step.

<details>

<summary>Troubleshooting and FAQ</summary>

`ERROR: failed to run Build function: the Dockerfile cannot be empty`

* Some plugin sources may be missing `Dockerfile` and/or `StartServer.sh` , please find a copy these files from github source version of your plugin to your local plugin.

***

`open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified.`

* Verify that Docker is running on your development machine.

***

`invalid reference format EdgegapLog: Warning: OnContainerizeCallback: Could not generate container, message:Failed`

* Verify that your application name is set and doesn't contain trailing whitespace.
* Make sure to press Enter to confirm the input value when done editing.

***

[**Couldn’t find target rules file for target ‘ProjectName’ when building a project?**](https://forums.unrealengine.com/t/how-may-i-resolve-an-error-couldnt-find-target-rules-file-for-target-projectname-when-building-a-project/411707)

* Change build configuration in your `XYZServerTarget.cs` file:

```
BuildConfig = EProjectPackagingBuildConfigurations::PPBC_Shipping;
```

***

`403 Forbidden, Could not push container, message:Failed`

* Please verify your docker credentials are correctly set.
* If using Edgegap Container Registry, ensure the username doesn’t include `app-version-pull` , which is the profile only allowing pulling images. Your profile should resemble your account email instead.
* Verify that you have enough free space in your account’s [Container Registry](https://app.edgegap.com/registry-management/repositories/list). We recommend trying to [#optimize-server-build-size](#optimize-server-build-size "mention"), and using [our API to delete older images](https://docs.edgegap.com/api/#tag/Container-Registry/operation/image-tag-delete). Images are stored on your local machine until deleted for backups.

</details>

## 👉 Next Steps

Continue to [/pages/DbR6ihTNvNj5aBknA5wA#id-5.-deploy-to-cloud](https://docs.edgegap.com/unreal-engine/pages/DbR6ihTNvNj5aBknA5wA#id-5.-deploy-to-cloud "mention") with our [Dashboard Deployment](https://app.edgegap.com/deployment-management/deployments/list) feature and learn more about stopping your deployments, injected variables and parametrization, and server discoverability.

[^1]: disable compatibility check

[^2]: use IpNetDriver

[^3]: set client/server tick rates and timeout

[^4]: set server build as default target

[^5]: enable logs

[^6]: don't use checks

[^7]: don't package editor content

[^8]: don't package Slate UI (editor)

[^9]: don't compile CEF3 (editor)

[^10]: create parent directory

[^11]: copy the steamclient.so file

[^12]: allow executing steam client

[^13]: find in your steam publisher portal


# Developer Tools

We build these tools for you with a simple mantra: “the keys to successful teams are fast iteration and frequent testing”. If you see an opportunity for improvement, please let us know in our [Community Discord](https://discord.gg/NgCnkHbsGp). We hope you will enjoy a smooth experience. :rocket:

{% hint style="info" %}
See our [Unreal Engine Samples](/docs/sample-projects/unreal-engine) to use as a project starter or inspiration.
{% endhint %}

## 🐋 Docker Extension

Verify your project settings, build dedicated servers for Linux, test locally or deploy to cloud, all within the comfortable graphical interface of Docker Desktop:

* [install from Docker Extension Marketplace](https://open.docker.com/extensions/marketplace?extensionId=edgegap/docker-extension) (free).

{% hint style="success" %}
**Follow along our easy guide for** [Unreal Engine](/unreal-engine) **developers (includes video tutorial).**
{% endhint %}

## ⚡ Integration Kit

* Unreal Engine [Developer Tools](/unreal-engine/developer-tools#integration-kit):
  * [read the documentation](https://egik.betide.studio/) by Betide Studios,
  * [install from Fab Marketplace](https://www.fab.com/listings/ff17ad88-12a1-49cf-9a41-31695ed11e16) (free for Personal use),
  * [import simple example blueprint](https://blueprintue.com/blueprint/m33u1okj/) (matchmaking) and customize to your needs.

This plugin supports Unreal Engine 4.27, 5.0 and all following minor releases (5.1, 5.2, etc.):

{% hint style="info" %}
This Kit includes many utilities to speed up [Matchmaking](/unity/matchmaking)and [Server Browser](/unity/server-browser) integration.
{% endhint %}

{% hint style="success" %}
This plugin is a Verified Solution maintained by an independent partner - Betide Studio.
{% endhint %}

## ⚡ Legacy Plugin

Build and launch your dedicated game server into the cloud directly from your Unreal Editor!

* [Dedicated Servers for Unreal plugin - turnkey solution, source code, and full release notes.](https://github.com/edgegap/edgegap-unreal-plugin)

This plugin supports Unreal Engine 4.27, 5.0 and all following minor releases (5.1, 5.2, etc.).

{% hint style="success" %}
This plugin is provided 100% free of charge, under Terms and Conditions of Free Tier.
{% endhint %}


# Unity - Getting Started

Learn by doing and deploy your first Dedicated Server on Edgegap. By the end of this guide, you will have deployed a dedicated server with Edgegap at no cost.

{% embed url="<https://youtu.be/4FR04V4YEUk>" %}

## ✔️ Preparation

Before you get started, make sure to [create a free account with Edgegap](https://app.edgegap.com/auth/register) (no credit card required).

**Configure a few essentials on your development machine:**

<details>

<summary>Install Unity Linux Build Support Modules</summary>

* Use Unity Hub to select tab **Installs**, access **Settings** and **Add Modules** for each Unity version you intend to use with Edgegap platform:

<figure><img src="/files/lNnd1bJ42H0dxX6TL6wm" alt=""><figcaption></figcaption></figure>

* Scroll down to select and install the following Unity modules:
  * **Linux Build Support (IL2CPP),**
    * **Linux Build Support (Mono),**
    * **Linux Dedicated Server Build Support**

<figure><img src="/files/7yeESZBpcwHUZJOQzkub" alt=""><figcaption></figcaption></figure>

</details>

<details>

<summary><a href="https://www.docker.com/products/docker-desktop/">Install Docker Desktop (or Docker CLI)</a></summary>

* [Install Docker Desktop from the official source](https://www.docker.com/products/docker-desktop/) (no account required).
* Restart your computer after completing the installation.

</details>

<details>

<summary><a href="https://github.com/edgegap/edgegap-unity-plugin">Install Edgegap's Unity Dedicated Servers Quickstart Plugin</a></summary>

Please refer to [the official plugin repository](https://github.com/edgegap/edgegap-unity-plugin) for detailed instructions on installation.

{% embed url="<https://youtu.be/3a5veDyxpoE>" %}

</details>

{% hint style="info" %}
**Confident in your server builds?** Skip to [#customize-server-image](#customize-server-image "mention") or [Advanced Features](/learn/advanced-features) for more.
{% endhint %}

## ⚙️ 1. Connect Account

☑️ Sign in and verify there are no new errors in your Unity console related to Edgegap's plugin.

✅ You may now proceed to the next step.

<details>

<summary>Troubleshooting and FAQ</summary>

`!Success: 400 BAD REQUEST - POST | https://api.edgegap.com/v1/wizard/init-quick-start - {"message": "The browser (or proxy) sent a request that this server could not understand."}`

* If you’ve installed by copying the ZIP file or used a sample project with a copy of the plugin installed this way, you will need to manually install package dependencies including the Newtonsoft JSON library, see [the official plugin repository](https://github.com/edgegap/edgegap-unity-plugin/tree/main?tab=readme-ov-file#instructions-1).
* Please contact us on [Community Discord](https://discord.gg/NgCnkHbsGp) for help if this is not the case.

</details>

## 🔧 2. Build Game Server

Whether you’re using a Windows, Mac, or a Linux machine, you will **need to build your server for Linux runtime**, as most cloud providers nowadays (including Edgegap) run on Linux. Don’t worry, no Linux knowledge is required to accomplish this with our plugin.

☑️ **Validate you have installed the required Unity Linux build tools.**

<details>

<summary>Install Unity Linux Build Support Modules</summary>

* Use Unity Hub to select tab **Installs**, access **Settings** and **Add Modules** for each Unity version you intend to use with Edgegap platform:

<figure><img src="/files/lNnd1bJ42H0dxX6TL6wm" alt=""><figcaption></figcaption></figure>

* Scroll down to select and install the following Unity modules:
  * **Linux Build Support (IL2CPP),**
    * **Linux Build Support (Mono),**
    * **Linux Dedicated Server Build Support**

<figure><img src="/files/7yeESZBpcwHUZJOQzkub" alt=""><figcaption></figcaption></figure>

</details>

☑️ Edit Build Settings to **ensure that all of your required game scenes are included**.

{% hint style="info" %}
**Advanced Unity users** - optionally change [Unity Build Settings](https://docs.unity3d.com/Manual/BuildSettings.html). Caution! This may break your build.
{% endhint %}

☑️ Optional: add netcode-specific script for port verification and environment bootstrapping to your initial server scene from Edgegap Server Hosting menu (right-click / :heavy\_plus\_sign: in your Hierarchy window).

<figure><img src="/files/u6YBlcLtn4qqBlLdk5aX" alt="" width="360"><figcaption></figcaption></figure>

{% hint style="info" %}
Once you complete step [#id-6.-deploy-to-cloud](#id-6.-deploy-to-cloud "mention"), the port verification script will log a warning if your netcode address or ports don't match your Edgegap [App Version Port Mapping](/learn/orchestration/application-and-versions#other-parameters-optional) configuration.
{% endhint %}

{% hint style="success" %}
Server builds should use address `0.0.0.0`  and port `7777`  in your netcode transport. If you customize your port please specify the same in your [Apps and Versions](/learn/orchestration/application-and-versions#port-mapping) once you [#id-5.-upload-to-edgegap](#id-5.-upload-to-edgegap "mention").
{% endhint %}

☑️ Once you’re happy with your configuration hit **Build server**, wait for the process to finish and verify there are no new errors in your Unity console. Completing this step will result in a **new folder appearing in your project root** - `Builds/EdgegapServer/ServerBuild` .

✅ You may now proceed to the next step.

<details>

<summary>Troubleshooting and FAQ</summary>

Unity: The only standalone targets supported are Windows x64 and OSX with OpenXR.

* Open your Packages and disable OpenXR before building your server.
* The OpenXR plugin is only required for clients and is not compatible with Linux server builds. By excluding it from your server builds you are not losing any functionality.

</details>

## 🐋 3. Containerize Server

Working in a team of developers means sharing your code. When things go wrong, the last thing you want to hear is “it works on my machine”. Game servers have to run reliably on any machine, since a successful games’ servers will run on thousands of server machines across the world.

To help make your server reliable, we use Docker - virtualization software to ensuring that all of your server code dependencies down to the operating system level are going to be always exactly the same, no matter how or where the server is launched.

{% hint style="info" %}
We recommend watching ["Never install locally" (video)](https://www.youtube.com/watch?v=J0NuOlA2xDc\&ab_channel=Coderized). **You DON'T need to use Dockerhub with Docker**.  Docker ≠ Dockerhub. Think of Docker as a programming engine and Dockerhub as it’s App Store.
{% endhint %}

☑️ For now, start by hitting the **Validate** button to ensure you’ve completed [Developer Tools](/unity/developer-tools#usage-requirements).

<details>

<summary><a href="https://www.docker.com/products/docker-desktop/">Install Docker Desktop (or Docker CLI)</a></summary>

* [Install Docker Desktop from the official source](https://www.docker.com/products/docker-desktop/) (no account required).
* Restart your computer after completing the installation.

</details>

☑️ You may configure the following options (or keep defaults):

* **Build path** is the relative path to your server build artifact, let’s keep the default for now.

{% hint style="warning" %}
**Keep builds inside your project folder**, Docker only accepts relative build paths to project root.
{% endhint %}

* **Image name** is a unique identifier of your choice, labeling your server build before shipping.
  * Usually, this will include the name of your game - for example “my-game-server”.
* **Image tag** is an identifier pointing to a specific version of your image.
  * The term “build artifact” is sometimes used to refer to a specific version of your image.
  * Timestamps are a great option for tagging, e.g. `2024.01.30-16.23.00-UTC` .
* **Path to Dockerfile** can be used to customize the recipe for your images.
  * We recommend keeping the default setting for now, you can read more later in section [#customize-server-image](#customize-server-image "mention").
* **Optional docker build parameters** can be used to further instruct Docker on finer nuances.
  * We recommend keeping the default setting for now, you can [read more later in Docker docs](https://docs.docker.com/reference/cli/docker/image/build/#options).

☑️ Once you’re happy with your configuration hit **Containerize with Docker**, wait for the process to finish and verify there are no new errors in your Unity console. Completing this step will result in a **new image appearing in your local machine**. You can verify this either in Docker Desktop, in tab Images underneath Local (default), or in docker CLI by running `docker images` .

✅ You may now proceed to the next step.

<details>

<summary>Troubleshooting and FAQ</summary>

`/bin/bash: docker: command not found` , or `could not find Packages\com.edgegap.unity-servers-plugin\Editor`

* First, ensure you’ve completed [Developer Tools](/unity/developer-tools#usage-requirements).
* Confirm you have verified your Edgegap account, you should’ve received a verification link by email.
* Some settings may have reset after updating your Docker Desktop. Try navigating to your Docker Desktop Settings / Advanced and for “Choose how to configure the installation of Docker’s CLI tools:” select “System (requires password)”.

***

`docker build requires exactly 1 argument`

* Please verify that your image tag doesn’t contain any whitespace characters (spaces, tabs). Re-typing your image tag value will ensure you didn’t accidentally copy such characters.

***

`(HTTP code 400) unexpected - invalid tag format`

* This is a [known issue with macOS Docker version 4.33](https://github.com/docker/for-win/issues/14258), please consider rolling back to 4.32 or upgrading to 4.35.

***

`ERROR: failed to solve: ubuntu:22.04: failed to resolve source metadata for http://docker.io/library/ubuntu:22.04: failed to authorize: failed to fetch oauth token`

* Are you located in China? Your connection may be interrupted by the Great Firewall. Try running `docker pull ubuntu:22.04` in your command line manually (open command line by pressing Win+R, then type `cmd` and Enter).

***

`System.IndexOutOfRangeException: Index was outside the bounds of the array.`

* If you installed our Unity quickstart plugin by downloading ZIP, your Unity Editor cache might be broken. Try deleting your plugin copy and installing using the git URL or from Unity Asset Store. You should no longer need Newtonsoft.JSON package as it’s automatically included with the other sources.

***

My docker image size is massive (over 1GB) / tiny (below 100mb), is this okay?

* This could be okay in some cases, as long as you can run the server and connect successfully (see [#id-4.-test-your-server-locally](#id-4.-test-your-server-locally "mention")). If this is not the case, consider reviewing your build options, resetting them to defaults, and gradually adding options to see how they impact your build size. See also [#optimize-server-build-size](#optimize-server-build-size "mention").

***

I’m experiencing another issue not mentioned anywhere in this documentation.

* First, please try [updating your Edgegap plugin](https://github.com/edgegap/edgegap-unity-plugin?tab=readme-ov-file#update-the-plugin-in-unity) as we may have shipped a fix. If this doesn’t help, please contact us on our [Community Discord](https://discord.gg/NgCnkHbsGp) and we’ll promptly investigate with you.

</details>

## 🧪 4. Test Server Locally

Let’s try deploying locally (on your machine) and connecting a game client, to make sure the server image is functioning properly before we upload and deploy (which may take a bit of time).

☑️ You may configure the following options (or keep defaults):

* **Server image tag** from the previous step.
  * Defaults to the last tag you’ve built with the plugin.
* **Optional docker run parameters** can be supplied for exposing multiple ports, or running your image on macOS machines.
  * You may publish multiple ports for your container if needed, simply add the parameter `-p {internal port}/{protocol}` for each, for example `-p 8080/tcp -p 7777/udp` to publish and map your server port `8080` to a random external port for TCP connection and server port `7777` to a random external port for UDP connection at the same time.
  * **Find server port configuration in your Transport or netcode-specific settings.**
  * If you’re using a machine with ARM architecture (macOS M1, M2, M3, etc..) you should see this optional parameter included in your Optional docker build parameters: `--platform=linux/amd64` .

☑️ Once you’re happy with your configuration hit **Deploy local container**, wait for the process to finish, and verify there are no new errors in your Unity console. Completing this step will result in a **new container being started** on your development machine.

{% hint style="info" %}
For more details see Docker Desktop / Containers, or Docker CLI command `docker ps` .
{% endhint %}

☑️ Now it’s time to **connect your Unity Editor game client to your local docker container** to verify your server image is functioning properly. Find your netcode client settings and input:

* `localhost` or `0.0.0.0` (equivalent in most cases) in place of server IP,
* randomized external port value found in Docker Desktop / Containers / edgegap-server-test.

<figure><img src="/files/hxCzzJZwAV68VDKInc91" alt=""><figcaption></figcaption></figure>

☑️ Once you’ve verified you’re able to connect to your local server container and play without issues, you may delete the container 🗑️ to free up resources on your machine for other programs.

✅ You may now proceed to the next step.

<details>

<summary>Troubleshooting and FAQ</summary>

I’m unable to connect to the local docker container using my Unity Editor game client.

* First, make sure that the container status is Up and is not Restarting or Exited, which would indicate a runtime exception. If your container is not running, inspect it’s logs via Docker Desktop tab Containers (click on your container) or using `docker logs {container_id} --timestamps` via docker CLI.
* Next, please verify that your Network Manager port setting from your server build matches the published port in **Optional docker run parameters**. If it doesn’t, try resetting or manually changing the value for this input field to match `{container}` port to your Network Manager setting. Find your protocol in your netcode settings.
* Lastly, confirm that your Unity Editor game client netcode settings are using the port published in **Optional docker run parameters** (see screenshot above).

***

`(Segmentation fault) - core dumped`

* If you’re using a machine with ARM architecture (macOS M1, M2, M3, etc..) you should see this optional parameter included in your Optional docker build parameters: `--platform=linux/amd64` . If you don’t, try resetting the value for this input field.

***

`SceneId of 9120233082191360994 not found in SceneObjects.`

* This could mean that the scene you’re trying to load was not correctly included in the build, a known issue in older plugin versions. To remedy this, please try updating your netcode integration version or [updating your Edgegap plugin](https://github.com/edgegap/edgegap-unity-plugin?tab=readme-ov-file#update-the-plugin-in-unity).

***

`http2: server: error reading preface from client //./pipe/docker_engine: file has already been closed`

* This is a [known issue with older versions of Docker Desktop for Windows](https://github.com/docker/for-win/issues/13611). Please update your Docker Desktop application and try containerizing again.

***

`Curl error 35: Cert handshake failed. Fatal error. UnityTls error code: 7`

* This error indicates a root SSL certificate validation issue, a known issue in older plugin versions. To remedy this, please try [updating your Edgegap plugin](https://github.com/edgegap/edgegap-unity-plugin?tab=readme-ov-file#update-the-plugin-in-unity).

</details>

## ☁️ 5. Upload to Edgegap

It’s time to ship your server online! Now that your image can successfully host players, we can upload it to Edgegap and start running it anywhere in the world. In this guide, we’ll be using [**Edgegap’s Container Registry**](/learn/advanced-features/edgegap-container-registry) (storage for images).

☑️ You may configure the following options (or keep defaults):

* **Application name** on Edgegap can match your image name or be customized.
  * We’ve chosen to copy your image name for now.
* **Application version** on Edgegap can match your tag or be customized.
  * Timestamps are a great option for app version names, e.g. `2024.01.30-16.50.20-UTC` .
  * Multiple application versions may point to the same image tag, such as `v1.1.0` and `dev` .
  * Learn more about [Apps and Versions](/learn/orchestration/application-and-versions) later.
* **Server image name** from step [#id-3.-containerize-your-game-server](#id-3.-containerize-your-game-server "mention").
* **Server image tag** from step [#id-3.-containerize-your-game-server](#id-3.-containerize-your-game-server "mention").

{% hint style="success" %}
Find any image name and tag stored on your machine in **Docker Desktop / Images**.
{% endhint %}

☑️ Once you’re happy with your configuration hit **Upload image and create App version**, wait for the process to finish, and verify there are no new errors in your Unity console.

☑️ You will be brought to our [Dashboard](https://app.edgegap.com/), where you may configure optional settings. Completing this step will result in a [new Application version being created](https://app.edgegap.com/application-management/applications/list), and your [build artifact being tagged and uploaded to Edgegap’s Container Registry](https://app.edgegap.com/registry-management/repositories/list).

☑️ You will now be prompted to define a Port for your new Application version. Make sure to set the same server port value as in step [#id-4.-test-your-server-locally](#id-4.-test-your-server-locally "mention") from your Transport or netcode-specific settings.

✅ You may now proceed to the next step.

<details>

<summary>Troubleshooting and FAQ</summary>

`denied: adding 756.6 MiB of storage resource, which when updated to current usage of 4.3 GiB will exceed the configured upper limit of 4.7 GiB` , `failed commit on ref "layer-sha256:--------": unexpected status from PUT request to https://registry.edgegap.com/`

* Seems like you’ve run out of image storage space on [Container Registry](https://app.edgegap.com/registry-management/repositories/list). Consider removing unused build artifacts (if you have any) or optimize server build size. If using a custom Dockerfile or .dockerignore, you might be copying some unneeded files into your image.

***

`You have reached you Application limit of 2` , `Unable to update docker tag/version: You have reached you Application Version limit of 2`

* You’ve reached the limits of our Free tier, please consider upgrading your account. Alternatively, you may remove your existing resources through our [Dashboard](https://app.edgegap.com/).

***

My new application version is not listed in the plugin/extension.

* Please ensure you’ve completed app version create form in the last step.

</details>

## 🚀 6. Deploy to Cloud

This is the final step in this guide, after which you will have a server deployed on Edgegap cloud, to which players from anywhere in the world can connect.

☑️ **Choose an application and version** from previous step to deploy.

☑️ Once you’re ready, hit **Deploy to Cloud**, wait to reach [/pages/0UXQAhtFuL0FkdoUmYTh#id-3.-deployment-ready](https://docs.edgegap.com/pages/0UXQAhtFuL0FkdoUmYTh#id-3.-deployment-ready "mention"). Completing this step will result in a [new Deployment being started](https://app.edgegap.com/deployment-management/deployments/list) on your Edgegap account.

☑️ Verify there are no new errors in your console output. Ensure also that your [Deployments](/learn/orchestration/deployments#container-logs) don’t show any errors and your [Deployments](/learn/orchestration/deployments#container-metrics) don’t indicate 100% resource utilization (vCPU or memory), otherwise new player connections may be rejected, or your server stuck in a restart loop. See troubleshooting steps below to address any issues.

☑️ Now we’ll perform the final test and **connect your Unity Editor game client to your cloud deployment**. Input game client connection details from the Deployment's:

* **Host** **URL** pointing to server's IP, usually in `NetworkManager` component.
* **External port** mapping to the [server's internal listen port](/learn/orchestration/application-and-versions#port-mapping), usually in Transport component.

<figure><img src="/files/BwcOdlJqXkoYOKEKZU4u" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
The external port of your Deployment on Edgegap cloud will be chosen at random, so that a potential attacker (hacker) is slowed down and detected before they can cause damage.
{% endhint %}

{% hint style="warning" %}
**Disable VPN when testing** for more realistic conditions and receive a [low-latency deployment](/learn/orchestration/deployments#server-placement).
{% endhint %}

☑️ Once you verify you’re able to connect to your Deployment without issues and are done testing, **Stop your Deployment** to free up capacity in your account for the next build.

* In case you encounter issues, [inspect Dashboard logs of your deployment](https://app.edgegap.com/deployment-management/deployments/list).
* If you can’t figure out the issue, we’re hanging out in our [Community Discord](https://discord.gg/NgCnkHbsGp) and happy to help.

🙌 Congratulations on your first Deployment on Edgegap! If you’d like to learn more, keep reading.

<details>

<summary>Troubleshooting and FAQ</summary>

Can’t connect clients to server - `Request timed out.` , `请求超时` , `ConnectionFailed` , or `Port verification failed`

* First, make sure that the deployment is Ready, and there are no runtime exceptions or errors in your deployment log. If your deployment stopped, inspect logs in our [Dashboard](https://app.edgegap.com/deployment-management/deployments/list).
* If you’re using Mirror netcode you need to have ["Auto Start Server”](https://mirror-networking.gitbook.io/docs/hosting/edgegap-hosting-plugin-guide#build-and-push) selected in your `NetworkManager` , rebuild, push, and redeploy your server.
* If you’re using FishNet netcode you need to enable [“Start on Headless”](https://fish-networking.gitbook.io/docs/manual/components/managers/server-manager#settings-are-general-settings-related-to-the-servermanager) in your `ServerManager`, rebuild, push, and redeploy your server.
* If you’re using Photon Fusion 2 netcode, please ensure that your server is passing the deployment public IP, external port and the `roomCode` on the server, and the same room code in the client in the [“NeworkRunner.StartGame”](https://doc.photonengine.com/fusion/current/manual/network-runner#creating-or-joining-a-room) parameter `StartGameArgs`. Deployment ID (e.g. `b63e6003b19f`) is a great choice as it’s globally unique and easily accessible to client by [Matchmaker](/learn/matchmaking/matchmaker-in-depth) and [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#injected-environment-variables).
* Next, please verify that your port setting in your server build’s netcode settings matches the internal port in your [App version](https://app.edgegap.com/application-management/applications/list). You can change the port mapping by editing the [App version](https://app.edgegap.com/application-management/applications/list) without rebuilding. Find your protocol in your netcode integration.
* Please ensure that your game client is connecting to the **external port** shown on your Deployment details page, this value will be always randomized due to security reasons.
* If you’re using Secure Websocket (WSS) protocol in your netcode integration, please ensure that your [App version](https://app.edgegap.com/application-management/applications/list) port configuration for the WSS port has TLS Upgrade enabled.
* Are you located in China and are using [Smart Fleets](https://docs.edgegap.com/docs/deployment/session/fleet-manager/fleet)? Your connection may be blocked by the Great Firewall. Consider adding a server located in China to your fleet, or using a VPN to connect.

***

My deployment stopped/restarted and I can’t access it’s logs anymore.

* In case the server process crashes due to an exception, our system will attempt restarting the server automatically. Consider [testing your server locally](#id-4.-test-your-server-locally) to uncover the root cause.
* We only keep logs for the duration of the deployment, if you wish to inspect logs after deployment stops, please [integrate a third party log storage](https://docs.edgegap.com/docs/deployment/endpoint-storage).
* See [/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped](https://docs.edgegap.com/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped "mention") to discover all causes for stopping your deployment.

***

My deployment stopped automatically after X minutes.

* Free Tier deployments have 60 minute limit, please consider upgrading your account.
* All deployments will be terminated after 24 hours of runtime following our server sanitization policy, for infrastructure maintenance, and to prevent racking up unexpected costs when deployment wasn’t shut down properly. For long-running servers, consider using [Private Fleets](/learn/orchestration/private-fleets) with [Persistence](/learn/orchestration/persistence).
* See [/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped](https://docs.edgegap.com/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped "mention") to discover all causes for stopping your deployment.

***

My deployment is ready but I’m not able to connect for several minutes afterwards.

* Once a deployment is Ready, your game engine initialization begins. This process may take anywhere from seconds to minutes, and the server doesn’t accept to player connections during this period.
* Consider optimizing your server initialization to decrease this time period.
* Game clients should retry connection in 1 second intervals for a limited amount of time (depending on your initialization duration), after which they should return to matchmaking.
* Consider adding a loading scene so the server can perform initialization (and travel in case of Unreal Engine) at the same time as clients, while synchronizing state of both.

***

My Meta Quest device throws `HTTP 0: Cannot resolve destination host` .

* When building Unity apps for Android target, your Internet Access permission may be removed from the output APK client build artifact automatically.
* Re-add the permissions in (requires rebuilding client afterwards):
  * Project Settings / OpenXR / :gear: Meta Quest Support / Force Remove Internet Permissions (uncheck).
  * Player Settings / Internet Access (set to require).

***

What will happen if a player leaves my deployment?

* By default, servers don’t reject player connections. Authenticating players is up to your devs, since many different methods and player authentication providers can be used.
* Game clients may store connection information locally to attempt reconnecting in case of unexpected client crashes.
* To allow players to join games in progress, consider using [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#backfill) or [Sessions](https://docs.edgegap.com/docs/deployment/session).

***

My server shows 100% CPU utilization after becoming ready.

* This may not be an issue, as game engines tend to perform CPU-heavy operations during server initializations. If the CPU usage doesn’t drop after 2-3 minutes from deployment start, you may need to optimize your server or increase app version resources.
* Reducing tick rate can impact CPU usage as the server performs less messaging operations.
* If you’re using Mirror netcode you need to have ["Auto Start Server”](https://mirror-networking.gitbook.io/docs/hosting/edgegap-hosting-plugin-guide#build-and-push) selected in your `NetworkManager` , rebuild, push, and redeploy your server.
* If you’re using FishNet netcode you need to enable [“Start on Headless”](https://fish-networking.gitbook.io/docs/manual/components/managers/server-manager#settings-are-general-settings-related-to-the-servermanager) in your `ServerManager`, rebuild, push, and redeploy your server.
* You’re limited to 1.5 vCPU and 3GB of memory (RAM) in Free Tier.
* You may increase allocated resources when creating a new app version. You can duplicate your App version in our Dashboard and adjust these values as needed, without rebuilding your server or image.

***

My deployment is restarting repeatedly and shows error `OOM kill`

* This is caused by exceeding allocated memory amount. Consider optimizing memory usage with object pooling, compression, or removing unneeded objects in your scene.
* Ensure your project is loading the default scene containing your `NetworkManager` and the scene is included in Unity’s Build Settings.
* You’re limited to 1.5 vCPU and 3GB of memory (RAM) in Free Tier.
* You may increase allocated resources when creating a new app version. You can duplicate your App version in our Dashboard and adjust these values as needed, without rebuilding your server or image.

***

Sometimes, my server’s memory (RAM) usage spikes to a high value, is that a problem?

* As long as you stay within the allocated app version memory amount, this is not an issue.
* Exceeding the allocated app version memory amount will cause `OOM kill` (see above).

***

Will my server performance be impacted by other servers running on the same machine?

* No, our platform ensures that allocated resources will not be used by other studios, or other servers on shared infrastructure. With Edgegap, there are no noisy neighbors.

</details>

## 👉 Next Steps

Once you have a working client/server setup, make sure to **save a copy of your project** (using version control software like git) so you can always trace back your steps in case you run into issues.

Continue reading to learn more about topics related to server lifecycle and discoverability.

### Stop Deployments

Once the match ends (or players leave), your deployment can be stopped to save cost. [Running empty or only partially filled can increase your cost unnecessarily!](https://edgegap.com/blog/how-session-fill-rate-affects-your-multiplayer-hosting-costs)

{% hint style="success" %}
Import our `DeploymentAgent`  example from Unity SDK to **stop servers easily and reliably**.
{% endhint %}

{% hint style="warning" %}
Connect your [Endpoint Storage](/docs/endpoint-storage) to get your deployment logs, otherwise they will be deleted!
{% endhint %}

### Injected Variables

Read useful information like deployment ID, server IP address, server location, and more; by accessing injected environment variables. Each deployment automatically includes:

* [Deployment Variables](/learn/orchestration/deployments#injected-environment-variables) - automatically supplied by Edgegap,
* [Matchmaking Variables](/learn/matchmaking/matchmaker-in-depth#injected-environment-variables) - automatically supplied by Edgegap when using [Matchmaking](/learn/matchmaking),
* [App Version Variables](/learn/orchestration/application-and-versions#injected-variables) - custom key-value pairs configurable by you.

{% hint style="success" %}
Import our `DeploymentAgent`  example from Unity SDK to **easily read strongly typed variables**.
{% endhint %}

### Session Automation

{% hint style="warning" %}
**Starting your Deployments manually, pasting URL and ports will not cut it for a live game.**
{% endhint %}

Automate popular game flows for managing sessions and scaling on demand with either:

{% columns %}
{% column width="33.33333333333333%" %}
[Matchmaking](/learn/matchmaking):

* Shorter Rounds
* On-Demand Matches
* Skill Rating and/or Custom Rules
  {% endcolumn %}

{% column width="33.33333333333333%" %}
[Server Browser](/learn/server-browser):

* Persistent or Rounds
* Social Regional Hubs
* Auto-Assign and/or\
  Custom Search
  {% endcolumn %}

{% column width="33.33333333333333%" %}
Custom Backend:

* Migrate Live Games
* [Deploy with v2 API](/docs/api/dedicated-servers)
* [Observe Webhooks](/learn/orchestration/deployments#webhooks)
  {% endcolumn %}
  {% endcolumns %}

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

### Optimize Builds

**Rebuild only assets that changed since last build.**

Consider using [Unity’s Incremental Builds](https://docs.unity3d.com/Manual/incremental-build-pipeline.html) to speed up your build time.

* Consider using [Unity’s Incremental Builds](https://docs.unity3d.com/Manual/incremental-build-pipeline.html) to speed up your build time.

**Only include what you absolutely need for your server to run.**

* Copying unused files in your images results in image bloat, longer uploads, slower caching speeds, and slower overall server startup. [Review Docker image optimization suggestions](https://docs.docker.com/build-cloud/optimization/#dockerignore-files).

**Disable static batching of meshes to reduce image size.**

* [Disable static batching for faster builds, uploads, and deployments.](https://docs.unity3d.com/Manual/DrawCallBatching.html)

**Compress meshes to reduce image size.**

* [Set mesh compression to High for faster builds, uploads, and deployments.](https://docs.unity3d.com/6000.0/Documentation/Manual/compressing-mesh-data-optimization.html)
* Vertex compression does not impact image size.

**Implement conditional lazy-loading of resources.**

* Exclude client-only assets by [setting textures and meshes to CPU read/write disabled](https://docs.unity3d.com/6000.0/Documentation/Manual/dedicated-server-optimizations.html).
* Consider using [Unity Addressables](https://docs.unity3d.com/Packages/com.unity.addressables@2.1/manual/index.html) for your client builds to speed up builds and deployments by [loading assets just in time](https://docs.unity3d.com/Packages/com.unity.addressables@1.19/manual/LoadingAddressableAssets.html), or skipping loading some assets in server builds by checking for presence of [Deployments](/learn/orchestration/deployments#injected-environment-variables).

**Consider using** [**multi-stage Docker builds (link)**](https://docs.docker.com/build/building/multi-stage/)**.**

* Separate large server dependencies to a separate image to reuse in multi-stage builds. Docker will cache each layer and simply reuse the previous version and skip uploading this part unless specifically instructed to do so, saving you bandwidth and time waiting for the upload to finish.
* If you’re not sure why one of your Dockerfile commands throws an error, try debugging locally. Create a new stage just before the issue happens (add a second `FROM` command), use `--target` to instruct the build process to stop at the problematic stage, and then `docker exec -it {container} /bin/bash` to enter interactive terminal inside your container. Afterwards, you can use shell commands in your base image to investigate further (e.g. `top` on ubuntu).

### Customize Image

We also support adding your own Dockerfile for users who need more control over their images due to build size optimization, extraneous dependencies, or requiring more complex startup process. You may optionally supply a path to your custom Dockerfile in step [#id-3.-containerize-your-game-server](#id-3.-containerize-your-game-server "mention"). We’ll now share a few “do it yourself” tips and best practices.

**Having issues when using Websockets, or HTTPS requests?**

* If you’re getting `Curl error 35: Cert handshake failed. Fatal error. UnityTls error code: 7` don’t despair, this is a known issue with older base (`FROM`) images including an expired root authority certificate. You can fix this by updating to a newer base image version (e.g. `ubuntu:22.04`), and running `update-ca-certificates` , add this to your Dockerfile:

  ```docker
  FROM ubuntu:22.04

  RUN apt-get install -y ca-certificates && \
      apt-get clean && \
      update-ca-certificates
  ```

**Always make sure you are working with a functioning server build.**

* Before assuming an issue is related to the custom Dockerfile, ensure your Unity server build can be started, and that the build process in Unity didn’t throw any exceptions or errors.

**Always test locally before uploading.**

* Testing your image locally will save you lots of time while waiting for the upload to finish. It’s also entirely free ✨ as it doesn’t require any Edgegap resources.
* When testing locally, make sure to set your internal port correctly:

  ```bash
  docker run \
    -p 7777/udp \
    -e ARBITRIUM_PORTS_MAPPING='{"ports":{"gameport":{"internal":7777}}}' \
    'registry.edgegap.com/<repository>:<tag>'
  ```

**Make sure you’ve got the basics down. Every Dockerfile needs a few essential commands:**

* `FROM {image}` is your base image, for Unity projects we usually use a long-term supported Linux, but any Linux-based base image will do. These are usually public images stored on dockerhub. Dockerfile reference here. [Dockerfile reference here](https://docs.docker.com/reference/dockerfile/#from).
* `COPY {source} {destination}` to copy your linux server build from your host machine inside the image, so you can start it later on. [Dockerfile reference here](https://docs.docker.com/reference/dockerfile/#copy).
* `USER {user}` should follow after a [useradd (ubuntu) command](https://manpages.ubuntu.com/manpages/bionic/man8/useradd.8.html) or equivalent, it’s best not to run everything as `root` to be on the safer side. [Dockerfile reference here](https://docs.docker.com/reference/dockerfile/#user).
* `CMD {command}` will be the last line, most likely calling a `StartServer.sh` or some kind of startup script to make sure your server initializes correctly once everything is set up. [Dockerfile reference here](https://docs.docker.com/reference/dockerfile/#cmd).
* do NOT use `VOLUME` - you will not be able to mount any local storage this way on Edgegap, consider our Endpoint Storage feature instead and use an S3 bucket, see [Endpoint Storage](https://docs.edgegap.com/docs/deployment/endpoint-storage),
* `EXPOSE 7777/UDP`  is not required! This will not actually make the internal server port available from outside the container, it's only a hint for the developer and the port needs to be
  * published when testing locally with `docker run <image> -p 7777/udp` ,
  * or mapped in [Edgegap Port Mapping](/learn/orchestration/application-and-versions#other-parameters-optional).

**Delay declaration of parameters until latest possible moment. Configurability > composability due to long server build times.** [**Apply this approach to Dockerfile commands to build and upload faster.**](https://medium.com/@esotericmeans/optimizing-your-dockerfile-dc4b7b527756)

* Scenario: you need to define parameters like deployment stage, version, game mode, map, player count per server, backup frequency, or similar.
* Bad solution: creating a separate image for every combination of your parameters. You will spend all of your time rebuilding the images with very little benefits from this approach.
* Better solution - substitute configuration parameters just in time:
  1. deployment parameters - supplied just before the deployment is made - matchmaking selectors passed as environment variables, or your custom session management system passing environment variables at deployment time,
  2. version parameters - shared for all deployments on an app version - deployment stage, artifact tag, third party secrets and endpoints, and similar; then
  3. one single image - contains and loads all configuration options when launched.

**Do NOT run databases on Edgegap deployments.**

* Edgegap deployments are not intended for long-running processes and may be terminated after a long period of runtime without prior notice. A database (even if distributed) running in this manner may be terminated and result in an irreversible loss of data. If you need a database, please consider a third party DBaaS.
* Consider using our [Managed Clusters](https://app.edgegap.com/cluster-management/clusters/list) for hosting databases and long running services.

{% hint style="info" %}
Running into a wall? We’re available in our [Community Discord](https://discord.gg/MmJf8fWjnt) and happy to help.
{% endhint %}


# Developer Tools

We build these tools for you with a simple mantra: “the keys to successful teams are fast iteration and frequent testing”. If you see an opportunity for improvement, please let us know in our [Community Discord](https://discord.gg/NgCnkHbsGp). We hope you will enjoy a smooth experience. 🚀

{% hint style="info" %}
[Find real world examples](/docs/sample-projects/unity-netcodes) which you can use as a project starter and modify further.
{% endhint %}

#### Semantic Versioning

Our developer tools and managed services use official [Semantic Versioning](https://semver.org/), indicating which updates are ✅ safe (minor, patch) and which may contain ⚠️ breaking changes (major).

**Once a version is released, it will never be modified/changed**.

## ⚡ Dedicated Servers Quickstart Plugin

Build and launch your dedicated game server into the cloud directly from your Unity Editor!

* [Dedicated Servers for Unity plugin - turnkey solution, source code, and full release notes.](https://github.com/edgegap/edgegap-unity-plugin)

{% hint style="success" %}
This plugin is provided 100% free of charge, under Terms and Conditions of Free Tier.
{% endhint %}

All Unity3D Long Term Support (LTS) versions after 2021.3+ are officially supported.

This plugin contains features intended to help you:

* build and containerize your dedicated servers for linux,
* test your server’s docker image locally,
* upload and deploy your server on Edgegap.

{% hint style="info" %}
This plugin does not modify any game behavior, it’s merely a convenient dev tool for Edgegap tasks. The plugin isn't included in your builds, as it’s only used during server build preparation.
{% endhint %}

#### Installation

Please refer to [the official plugin repository](https://github.com/edgegap/edgegap-unity-plugin) for detailed instructions on installation.

{% embed url="<https://youtu.be/3a5veDyxpoE>" %}

<details>

<summary>Install Unity Linux Build Support Modules</summary>

* Use Unity Hub to select tab **Installs**, access **Settings** and **Add Modules** for each Unity version you intend to use with Edgegap platform:

<figure><img src="/files/lNnd1bJ42H0dxX6TL6wm" alt=""><figcaption></figcaption></figure>

* Scroll down to select and install the following Unity modules:
  * **Linux Build Support (IL2CPP),**
    * **Linux Build Support (Mono),**
    * **Linux Dedicated Server Build Support**

<figure><img src="/files/7yeESZBpcwHUZJOQzkub" alt=""><figcaption></figcaption></figure>

</details>

<details>

<summary><a href="https://www.docker.com/products/docker-desktop/">Install Docker Desktop (or Docker CLI)</a></summary>

* [Install Docker Desktop from the official source](https://www.docker.com/products/docker-desktop/) (no account required).
* Restart your computer after completing the installation.

</details>

{% hint style="success" %}
See [Unity](/unity) to get started and for detailed instructions on plugin usage.
{% endhint %}

<details>

<summary>Troubleshooting and FAQ</summary>

Unity error: `[Package Manager Window] Error adding package: https://github.com/edgegap/edgegap-unity-plugin.git`

* To add our plugin via git URL, you will need git client installed ([#installation](#installation "mention")).

***

Unity Error: `Missing Linux Build Support`

* See [#usage-requirements](#usage-requirements "mention"), you’re most likely missing Linux Build Support modules, which you can install through Unity hub. Make sure to choose the same Unity version as your project’s.

</details>

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

{% hint style="info" %}
**For plugin developers** - if you wish to detect presence of this plugin in the users Editor, you can do so using a compiler directive `#if EDGEGAP_PLUGIN_SERVERS {your code} #endif` .
{% endhint %}

## ⭐ Software Development Kit

{% hint style="info" %}
**The latest version of Unity SDK is `3.0.0`**. All examples in this documentation are up to date.
{% endhint %}

This plugin has been tested, and supports Unity versions 2021.3.0f1+, including Unity 6 LTS.

{% hint style="success" %}
This plugin is provided 100% free of charge, under Terms and Conditions of Free Tier.
{% endhint %}

#### Requirements

<details>

<summary>Install a Git Client (for example <a href="https://git-scm.com/">git-scm</a>)</summary>

A git client is needed for Unity to download and install our Unity package automatically. You will not need to use git directly once it's installed.

</details>

#### Installation

1. Open your Unity Project,
2. Select `Window > Package Management > Package Manager` ,
3. Click the :heavy\_plus\_sign: icon and select `Add package from git URL...` ,
4. Input the URL of our SDK when prompted:

{% code title="" %}

```
https://github.com/edgegap/edgegap-unity-sdk.git
```

{% endcode %}

5. Click `Add`  and wait until installation is completed.

#### Import Samples

This package includes multiple samples, intended to be used individually (do not combine samples).

#### Verified Sources

This is the only official distribution channel for this SDK, do not trust unverified sources!

#### Update Package

Navigate to Edgegap SDK in Unity Package Manager and click `Update` .

{% hint style="warning" %}
**Imported Samples are not updated automatically!** Back up any custom property values, delete the sample scripts used in your scene currently and re-import samples.
{% endhint %}

{% hint style="info" %}
Some releases may contain breaking changes. This will be indicated by a new MAJOR version.
{% endhint %}

#### Updating to v3

This update includes many new [Server Browser](/unity/server-browser) utilities and examples, improves matchmaking error handling, and more. See [Release Notes](/docs/release-notes) for a complete list.

{% hint style="warning" %}
Unity SDK update v3 includes a few breaking changes. Please re-test your integration carefully.
{% endhint %}

<details>

<summary>Troubleshooting and FAQ</summary>

Unity: `[Package Manager Window] Error adding package: https://github.com/edgegap/edgegap-unity-sdk.git`

* To add our plugin via git URL, you will need git client installed.

***

Visual Studio: `type or namespace name could not be found` for Edgegap namespace.

1. In Unity Editor, navigate to **Edit / Preferences / External Tools / Generate .csproj files**.
2. Make sure you have enabled **Git packages**.
3. Click **Regenerate project files**.

</details>

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

## 📫 Distributed Relay Transports library

Find a custom Transport for your netcode library to utilize Distributed Relays in peer to peer games.

[See documentation](/learn/distributed-relay/relay-transport-samples):

* Download Edgegap Relay Samples:
  * [Mirror sample](https://github.com/edgegap/unity-mirror-relay-sample),
  * [FishNet sample](https://github.com/edgegap/unity-fishnet-relay-sample),
  * [Unity NGO sample](https://github.com/edgegap/unity-ngo-relay-sample).
* [Edgegap Relay Transport Library - source code and release notes for supported netcodes.](https://github.com/edgegap/distributed-relay-examples)


# Matchmaking

This SDK is an optional starter kit for Unity users, which can be extended and customized later.

## 💡 Features

{% columns %}
{% column %}

* Complete Examples
* Ping Automation
* Tickets, Groups, Teams
  {% endcolumn %}

{% column width="33.33333333333333%" %}

* Match Variables
* Type Definitions (C#)
* Local Dev Testing
  {% endcolumn %}

{% column width="33.33333333333333%" %}

* Cross-Platform
* Easy to Customize
* Automated Retry
  {% endcolumn %}
  {% endcolumns %}

## ✔️ Preparation

This plugin has been tested, and supports Unity versions 2021.3.0f1+, including Unity 6 LTS.

{% hint style="success" %}
This plugin is provided 100% free of charge, under Terms and Conditions of Free Tier.
{% endhint %}

#### Requirements

<details>

<summary>Install a Git Client (for example <a href="https://git-scm.com/">git-scm</a>)</summary>

A git client is needed for Unity to download and install our Unity package automatically. You will not need to use git directly once it's installed.

</details>

#### Installation

1. Open your Unity Project,
2. Select `Window > Package Management > Package Manager` ,
3. Click the :heavy\_plus\_sign: icon and select `Add package from git URL...` ,
4. Input the URL of our SDK when prompted:

{% code title="" %}

```
https://github.com/edgegap/edgegap-unity-sdk.git
```

{% endcode %}

5. Click `Add`  and wait until installation is completed.

#### Import Samples

This package includes multiple samples, intended to be used individually (do not combine samples).

#### Verified Sources

This is the only official distribution channel for this SDK, do not trust unverified sources!

#### Update Package

Navigate to Edgegap SDK in Unity Package Manager and click `Update` .

{% hint style="warning" %}
**Imported Samples are not updated automatically!** Back up any custom property values, delete the sample scripts used in your scene currently and re-import samples.
{% endhint %}

{% hint style="info" %}
Some releases may contain breaking changes. This will be indicated by a new MAJOR version.
{% endhint %}

#### Updating to v3

This update includes many new [Server Browser](/unity/server-browser) utilities and examples, improves matchmaking error handling, and more. See [Release Notes](/docs/release-notes) for a complete list.

{% hint style="warning" %}
Unity SDK update v3 includes a few breaking changes. Please re-test your integration carefully.
{% endhint %}

## 🍀 Getting Started

This guide assumes basic knowledge of [Matchmaking](/learn/matchmaking) concepts and a running Matchmaker.

{% hint style="success" %}
**We strongly recommend importing our Simple Example** to follow along code as you read this document. You can do so in `Unity Package Manager > Edgegap SDK > Samples` .
{% endhint %}

### Overview

Our SDK utilizes heavily [Dependency Injection](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/overview#the-concept) and [Observer](https://learn.microsoft.com/en-us/dotnet/standard/events/observer-design-pattern) programming patterns.

{% hint style="info" %}
This package integrates both [Server Browser](/learn/server-browser) and [Matchmaking](/learn/matchmaking), which can be used together or separately. You may freely reuse any scripts for your own customized forks and integrations.
{% endhint %}

This package includes:

* Runtime Files - will be compiled and bundled with your client and server builds:
  * Service-Specific Utilities:
    * [#client-agent](#client-agent "mention") - a complete client integration to reuse/extend.
    * API functions - endpoint definitions, error handling, and logging automations.
  * Service-Specific DTOs[^1] - typed data containers for Matchmaking API.
  * Shared Utilities - logging, HTTP, ping, observables, etc...
  * Shared DTOs[^1] - used by multiple Edgegap services to pass data around.
* Sample Files - bundled and compiled ONLY if imported in your project:
  * [#simple-example](#simple-example "mention") - example handlers for a [minimal configuration](/learn/matchmaking#simple-example).
  * [#region-picker](#region-picker "mention") - explore UI integration with manual region selection.

### Group Client

**Ping automation, ticket management, and host retrieval** are performed by Group Client.

Once instantiated, agent's **parent Monobehaviour (handler) must initialize the client** and provide:

* `onMonitorUpdate`  callback - observe service health changes,
* `onAssignmentUpdate`  callback - observe and react to host assignment changes.

Once initialized, this client will automatically provide validations and hook up logging observers, finishing with a single call to the monitoring API endpoint to indicate service health.

Client's handler is expected to take control and call client functions from this point:

* `Beacons`  to retrieve a list of available [Ping Beacons](/learn/orchestration/ping-beacons),
* `MeasureBeaconsRoundTripTime`  to provide ping measurements against a given set of beacons,
* `CreateGroup`  for the lobby leader to create a joinable group, which friends can be invited to,
* `JoinGroup`  to join an existing group using the group ID sent through 3rd party lobby/backend,
* `SetReady`  to mark group owner and members as ready and begin searching for matches,
* `ResumeMatchmaking`  load a cached group and continue searching in case client crashed,
* `StopMatchmaking`  to delete the ticket (if not matched) and abandon queue,
* `Status`  to verify Server Browser service health.

When a new player connection is established, player is expected to send their ticket ID to the game server using your netcode, to correlate connections with [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#injected-variables).

{% hint style="success" %}
Save connection details in client or game backend to reconnect in case of an unexpected crash.
{% endhint %}

## 🧪 Samples

Get started with samples including a complete functioning integration for both server and client.

### Simple Example

Includes a full player lifecycle implementation with ping measurement, ticket management, and host assignment retrieval. Demonstrates how to read injected match variables on server side.

Modify matchmaking attributes to expand this example to fit any configuration easily.

### Region Picker

Some players (or groups) have special localized conditions (such as ISP[^2] blockage, [country-wide blockage](#user-content-fn-3)[^3], or other) and may prefer choosing a region manually rather than based on ping alone.

Inspect our Region Picker sample and draw inspiration for your matchmaking UI implementation.

## ⚙️ Customization

This SDK is meant to be extended and modified, though some modifications may be risky:

✅ Handler - safely hook up UI observers and perform minor additions or modifications,

⚠️ Agent - modify player lifecycle management at your own risk,

⚠️ API - write your own integration from scratch, using cherry-picked utilities.

Handlers may observe any events emitted by Server and Client agents as described below.

{% hint style="warning" %}
Make sure to get familiar with [Matchmaking In-Depth](/learn/matchmaking/matchmaker-in-depth) concepts before making customizations.
{% endhint %}

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

### Observe Events

Group Client emits events (actions) for parent handler to observe and consume.

{% hint style="success" %}
Read event payloads by accessing  `.Current` state of any observable. 🔴 `Error` events contain full error message delimited by a newline character after the main event message.
{% endhint %}

Preview events emitted by observable `Monitor` :

<table data-full-width="true"><thead><tr><th width="125">Action Type</th><th width="450">Event Message</th><th>Description</th></tr></thead><tbody><tr><td>🟢 <code>Update</code> </td><td><code>healthy</code></td><td>All systems OK.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>unhealthy</code></td><td>Unexpected issue.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>get monitor failed</code></td><td>Misconfiguration or unexpected issue.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>get beacons failed</code></td><td>Unexpected issue.</td></tr></tbody></table>

Preview events emitted by observable `Group`:

<table data-full-width="true"><thead><tr><th width="125">Action Type</th><th width="450">Event Message</th><th>Description</th></tr></thead><tbody><tr><td>🟢 <code>Update</code> </td><td><code>created</code></td><td>Group created OK.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>group create failed</code></td><td>Failed to create group.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>joined</code></td><td>Group joined OK.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>group join failed</code></td><td>Failed to join group.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>resumed</code></td><td>Group resumed OK.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>group not found</code></td><td>Not a member of group, or group expired.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>conflict, abandon and restart</code></td><td>Please abandon current group before trying to create/join a new group.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>member updated [{ready}]</code></td><td>Member updated with new Ready value.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>member update failed</code></td><td>Failed to update group member.</td></tr><tr><td>🔵 <code>Notify</code></td><td><code>polling [{consecutive}/{maximum}]</code></td><td>Client initiated polling group status.</td></tr><tr><td>🔵 <code>Notify</code></td><td><code>polling stopped</code></td><td>Client stopped polling ticket status.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>polling failed, reached maximum retries</code></td><td>Client exhausted maximum consecutive polling retries. Check service status.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>polling failed</code></td><td>Client received a non-retryable error when polling. Check service status.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>group updated [{status}]</code></td><td>Detected group status change while polling.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>abandoned</code></td><td>Deleted ticket OK.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>abandon failed (not found)</code></td><td>Client couldn't find the ticket to delete, possibly expired.</td></tr><tr><td>🟡 <code>Warn</code></td><td><code>abandon failed (already matched)</code></td><td>Client couldn't delete a matched group. Disable abandonment or <a data-mention href="/pages/kBCQHitUL4u9O0iTPk7x#backfill-match">/pages/kBCQHitUL4u9O0iTPk7x#backfill-match</a> to replace the player.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>abandon failed</code></td><td>Failed to delete group or membership.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>removed</code></td><td>Group expired, local reference deleted.</td></tr></tbody></table>

[^1]: Data Transfer Object

[^2]: [Internet Service Provider](https://en.wikipedia.org/wiki/Internet_service_provider)

[^3]: most notably China or Russia


# Server Browser

This SDK is an optional starter kit for Unity users, which can be extended and customized later.

## 💡 Features

Gain access to prebuilt automated features by installing our SDK:

{% columns %}
{% column %}

* Complete Examples
* Lifecycle Management
* Capacity Management
  {% endcolumn %}

{% column width="33.33333333333333%" %}

* Filter Query Compiler
* Type Definitions (C#)
* Local Dev Testing
  {% endcolumn %}

{% column width="33.33333333333333%" %}

* Cross-Platform
* Easy to Customize
* Automated Retry
  {% endcolumn %}
  {% endcolumns %}

## ✔️ Preparation

This plugin has been tested, and supports Unity versions 2021.3.0f1+, including Unity 6 LTS.

{% hint style="success" %}
This plugin is provided 100% free of charge, under Terms and Conditions of Free Tier.
{% endhint %}

#### Requirements

<details>

<summary>Install a Git Client (for example <a href="https://git-scm.com/">git-scm</a>)</summary>

A git client is needed for Unity to download and install our Unity package automatically. You will not need to use git directly once it's installed.

</details>

#### Installation

1. Open your Unity Project,
2. Select `Window > Package Management > Package Manager` ,
3. Click the :heavy\_plus\_sign: icon and select `Add package from git URL...` ,
4. Input the URL of our SDK when prompted:

{% code title="" %}

```
https://github.com/edgegap/edgegap-unity-sdk.git
```

{% endcode %}

5. Click `Add`  and wait until installation is completed.

#### Import Samples

This package includes multiple samples, intended to be used individually (do not combine samples).

#### Verified Sources

This is the only official distribution channel for this SDK, do not trust unverified sources!

#### Update Package

Navigate to Edgegap SDK in Unity Package Manager and click `Update` .

{% hint style="warning" %}
**Imported Samples are not updated automatically!** Back up any custom property values, delete the sample scripts used in your scene currently and re-import samples.
{% endhint %}

{% hint style="info" %}
Some releases may contain breaking changes. This will be indicated by a new MAJOR version.
{% endhint %}

#### Updating to v3

This update includes many new [Server Browser](/unity/server-browser) utilities and examples, improves matchmaking error handling, and more. See [Release Notes](/docs/release-notes) for a complete list.

{% hint style="warning" %}
Unity SDK update v3 includes a few breaking changes. Please re-test your integration carefully.
{% endhint %}

## 🍀 Getting Started

This guide assumes basic knowledge of [Server Browser](/learn/server-browser) concepts and a running Server Browser.

{% hint style="success" %}
**We strongly recommend importing our Auto-Assign Example** to follow along code as you read this document. You can do so in `Unity Package Manager > Edgegap SDK > Samples` .
{% endhint %}

### Overview

Our SDK utilizes heavily [Dependency Injection](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/overview#the-concept) and [Observer](https://learn.microsoft.com/en-us/dotnet/standard/events/observer-design-pattern) programming patterns.

{% hint style="info" %}
This package integrates both [Server Browser](/learn/server-browser) and [Matchmaking](/learn/matchmaking), which can be used together or separately. You may freely reuse any scripts for your own customized forks and integrations.
{% endhint %}

This package includes:

* Runtime Files - will be compiled and bundled with your client and server builds:
  * Service-Specific Utilities:
    * [#server-agent](#server-agent "mention") - a complete server integration to reuse/extend.
    * [#client-agent](#client-agent "mention") - a complete client integration to reuse/extend.
    * API functions - endpoint definitions, error handling, and logging automations.
    * Filter Compiler - strongly typed utilities for building filter queries.
  * Service-Specific DTOs[^1] - typed data containers for Server Browser API.
  * Shared Utilities - logging, HTTP, ping, observables, etc...
  * Shared DTOs[^1] - used by multiple Edgegap services to pass data around.
* Sample Files - bundled and compiled ONLY if imported in your project:
  * [#auto-assign](#auto-assign "mention") - example handlers with auto-assigned reservations,
  * [#custom-search](#custom-search "mention") - example handlers with manual instance choice.

### Server Agent

**Server Lifecycle and Capacity management** are performed by Server Agent.

Once instantiated, agent's **parent Monobehaviour (handler) must initialize the agent** and provide:

* `onMonitorUpdate`  callback - observe service health changes,
* `onInstanceUpdate`  callback - observe and react to instance and slot changes,
* `onConfirmationsUpdate`  callback - observe and react to federated authentication.

Once initialized, this agent will automatically provide validations and hook up logging observers, finishing with a single call to the monitoring API endpoint to indicate service health.

Agent's handler is expected to take control and call agent functions from this point:

* `DiscoverInstance`  to create initial Server Instance and Slots and initiate heartbeat,
* `DeleteInstance`  once the match concludes / to prevent new players from joining,
* `ConfirmReservation`  when players join, to verify their identity and slot assignment,
* `UpdateSlot`  to update slot capacity (on player joined/abandoned) or modify metadata,
* `UpdateInstance`  to modify instance metadata,
* `Status`  to verify Server Browser service health.

{% hint style="success" %}
Confirmations and Slot/Instance Updates are **queued and performed in batches by default** (Heartbeat Mode) to maximize scalability. To iterate quicker during dev testing, use Greedy Mode.
{% endhint %}

{% hint style="warning" %}
**When updating metadata, all indices must be defined.** To unset non-indexed keys, simply omit them.
{% endhint %}

The agent automatically maintains a heartbeat to keep the server discoverable while running. If the agent can't reach your server browser for several consecutive heartbeats (configurable):

* less than maximum - instance will be automatically re-discovered,
* more than maximum - instance will be automatically deleted.

When a new player connection is established, player is expected to send their reservation ID (third party player ID) to the game server using your netcode, to perform reservation confirmation.

Once `onConfirmationsUpdate`  triggers, handler must perform additional actions:

* call `UpdateSlot`  to reduce available seats for any slots with confirmed reservations,
* accept or deny connection using netcode-specific methods.

When a player abandons game, handler is expected to increase available seats for this slot.

{% hint style="info" %}
Allow a short time period for players to reconnect before abandoning in case of unexpected crashes.
{% endhint %}

### Client Agent

**Searching for instances, pagination, filtering, and reservations** are performed by Client Agent.

Once instantiated, agent's **parent Monobehaviour (handler) must initialize the agent** and provide:

* `onMonitorUpdate`  callback - observe service health changes,
* `onInstancesUpdate`  callback - observe and react to instance list changes.

Once initialized, this agent will automatically provide validations and hook up logging observers, finishing with a single call to the monitoring API endpoint to indicate service health.

Agent's handler is expected to take control and call agent functions from this point:

* `ReserveSeats`  to create a capacity reservation for a particular instance/slot or auto-assign,
* `ListInstances`  to list instances with a specific filter, ordering, cursor, and page size,
* `GetNextPage`  to fetch more instances with the current parameters (filters, etc),
* `RefreshList`  to clear cache and reload first page, or refresh with a specific cursor,
* `GetInstanceDetails`  to fetch instance metadata and slots information for a specific instance,
* `Status`  to verify Server Browser service health.

When a new player connection is established, player is expected to send their reservation ID (third party player ID) to the game server using your netcode, to perform reservation confirmation.

{% hint style="success" %}
Save connection details in client or game backend to reconnect in case of an unexpected crash.
{% endhint %}

## 🧪 Samples

Get started with samples including a complete functioning integration for both server and client.

### Auto-Assign

Utilizes auto-assigned reservations, with client specifying only policy name. Server browser chooses an instance matching policy filter and slot with sufficient seats automatically.

### Custom Search

Includes a full implementation demonstrating how to search instances and slots, hook up UI elements, and let player choose where they wish to reserve capacity manually.

## ⚙️ Customization

This SDK is meant to be extended and modified, though some modifications may be risky:

✅ Handler - safely hook up UI observers and perform minor additions or modifications,

⚠️ Agent - modify lifecycle and capacity management at your own risk,

⚠️ API - write your own integration from scratch, using cherry-picked utilities.

Handlers may observe any events emitted by Server and Client agents as described below.

{% hint style="warning" %}
Make sure to get familiar with [Server Browser In-Depth](/learn/server-browser) concepts before making customizations.
{% endhint %}

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

### Server Events

Server Agent emits events (actions) for parent handler to observe and consume.

{% hint style="success" %}
Read event payloads by accessing  `.Current` state of any observable. 🔴 `Error` events contain full error message delimited by a newline character after the main event message.
{% endhint %}

Preview events emitted by observable `Monitor` :

<table data-full-width="true"><thead><tr><th width="125">Action Type</th><th width="450">Event Message</th><th>Description</th></tr></thead><tbody><tr><td>🟢 <code>Update</code> </td><td><code>healthy</code></td><td>All systems OK.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>unhealthy</code></td><td>Unexpected issue.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>get monitor failed</code></td><td>Misconfiguration or unexpected issue.</td></tr><tr><td>🟡 <code>Warn</code></td><td><code>request timeout clamped to heartbeat [{timeout}]</code></td><td>Prevents race conditions.</td></tr></tbody></table>

Preview events (actions) emitted by observable `Instance`:

<table data-full-width="true"><thead><tr><th width="125">Action Type</th><th width="450">Event Message</th><th>Description</th></tr></thead><tbody><tr><td>🟢 <code>Update</code> </td><td><code>discovered</code></td><td><a href="/pages/UkbJIQ8RFaAL5RDNZsvm#discover-instance">Instance Discovery</a> completed OK. May be triggered if instance lost connection due to temporary condition and re-discovered.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>discovery duplicate</code></td><td>Instance with this Request ID is already discovered.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>discovery failed</code></td><td>Unexpected issue with discovery.</td></tr><tr><td>🔵 <code>Notify</code></td><td><code>heartbeat ok</code></td><td>Heartbeat completed OK.</td></tr><tr><td>🟡 <code>Warn</code></td><td><code>heartbeat failed [{consecutive}/{maximum}]</code></td><td>Heartbeat failed, server couldn't reach Server Browser.</td></tr><tr><td>🔵 <code>Notify</code></td><td><code>instance update enqueued</code></td><td>Enqueued instance update for next batch (heartbeat/greedy).</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>instance updated</code></td><td>Instance metadata updated OK.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>instance update failed, enqueuing for retry</code></td><td>Instance update failed, possibly due to rate limiting or error.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>instance deleted</code></td><td>Instance is no longer discoverable by players.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>instance delete failed (not found)</code></td><td>Instance may have expired due to too many missed heartbeats.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>instance delete failed</code></td><td>Failed to delete Instance, possibly due to rate limiting or error.</td></tr><tr><td>🔵 <code>Notify</code></td><td><code>slot update enqueued [{slot}]</code></td><td>Enqueued slot update for next batch (heartbeat/greedy).</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>slot updated [{slot}]</code></td><td>Slot seats capacity and/or metadata updated OK.</td></tr><tr><td>🟡 <code>Warn</code></td><td><code>agent throttled concurrent slot update</code></td><td>Prevented attempt at concurrent update (race condition).</td></tr><tr><td>🔴 <code>Error</code></td><td><code>slot update failed (not found) [{slot}]</code></td><td>Slot with this name is not defined for this Instance yet.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>slot update failed (not enough seats) [{slot}]</code></td><td>Slot update tried to reduce available seats below zero.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>slot update failed, enqueuing for retry [{slot}]</code></td><td>Slot update failed, possibly due to rate limiting or error.</td></tr></tbody></table>

Preview events (actions) emitted by observable `Confirmations`:

<table data-full-width="true"><thead><tr><th width="125">Action Type</th><th width="450">Event Message</th><th>Description</th></tr></thead><tbody><tr><td>🔵 <code>Notify</code></td><td><code>enqueued [{player}]</code></td><td>Enqueued confirmation for next batch (heartbeat/greedy).</td></tr><tr><td>🟡 <code>Warn</code></td><td><code>duplicate [{player}]</code></td><td>Prevented attempt at duplicated confirmation (already queued).</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>confirmed</code></td><td>Confirmed reservations for individual slots, includes also expired and unknown player IDs for handler to resolve (accept/kick).</td></tr><tr><td>🔴 <code>Error</code></td><td><code>failed</code></td><td>Unexpected issue with confirmations. Check service status.</td></tr></tbody></table>

### Client Events

Client Agent emits events (actions) for parent handler to observe and consume.

{% hint style="success" %}
Read event payloads by accessing  `.Current` state of any observable. 🔴 `Error` events contain full error message delimited by a newline character after the main event message.
{% endhint %}

Preview events emitted by observable `Monitor` :

<table data-full-width="true"><thead><tr><th width="125">Action Type</th><th width="450">Event Message</th><th>Description</th></tr></thead><tbody><tr><td>🟢 <code>Update</code> </td><td><code>healthy</code></td><td>All systems OK.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>unhealthy</code></td><td>Unexpected issue.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>get monitor failed</code></td><td>Misconfiguration or unexpected issue.</td></tr></tbody></table>

Preview events emitted by observable `Instances`:

<table data-full-width="true"><thead><tr><th width="125">Action Type</th><th width="450">Event Message</th><th>Description</th></tr></thead><tbody><tr><td>🔵 <code>Notify</code></td><td><code>seats reserved</code></td><td>Seat reservation OK.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>seats reservation failed (not found)</code></td><td><a data-mention href="#auto-assign">#auto-assign</a> - policy name not found (deleted or inactive).<br><a data-mention href="#custom-search">#custom-search</a> - instance or slot not found.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>seats reservation failed (reached capacity)</code></td><td><a data-mention href="#auto-assign">#auto-assign</a> - policy reached maximum capacity.<br><a data-mention href="#custom-search">#custom-search</a> - slot reached maximum capacity.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>seats reservation failed</code></td><td>Seat reservation failed, possibly invalid policy, request ID, or slot ID.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>instance list retrieved</code></td><td>Fetched list of instances OK.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>instance list next page retrieved</code></td><td>Fetched next page of instances OK.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>instance list last page reached</code></td><td>Failed to fetch next page, try refreshing or changing filters.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>instance list next page retrieval failed</code></td><td>Failed to fetch next page, possibly due to an invalid cursor.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>instance details retrieved</code></td><td>Fetched details of a listed instance OK.</td></tr><tr><td>🟢 <code>Update</code> </td><td><code>instance not cached, prepending</code></td><td>Fetched details of instance outside current list.</td></tr><tr><td>🔴 <code>Error</code></td><td><code>instance details retrieval failed</code></td><td>Failed to fetch details, possibly due to invalid request ID.</td></tr></tbody></table>

[^1]: Data Transfer Object


# Orchestration

Orchestration and DevOps[^1] are at the core of Edgegap platform. Building strong processes around best practices is key to efficiently leveraging new technologies and services for teams of every size.

Setting up your project in the right way is paramount for long term success:

1. **Focus on your core business** instead of building hyper-reusable scaffolding around it.
2. **Minimize vendor lock-in** with cloud native approach and loose service coupling.
3. **Make your development experience enjoyable** and iterate faster with automation.

{% hint style="success" %}
Optimize your resource usage and [leverage fractional vCPU allocation](/learn/orchestration/application-and-versions#resource-requirements) to reduce overall server cost.
{% endhint %}

**Skip the painful trial and error phase and start with a strong foundation:**

* Create template [Apps and Versions](/learn/orchestration/application-and-versions) to start new server [Deployments](/learn/orchestration/deployments) within seconds.
  * Scale rapidly with [Deployments](/learn/orchestration/deployments#match-bound) short lived sessions,
  * or pre-scale manually with [Deployments](/learn/orchestration/deployments#regional-standby) for predictable traffic.
* Establish user sessions with [Matchmaking](/learn/matchmaking), [Server Browser](/learn/server-browser), or with a custom solution.
  * Pick teammates, opponents, and servers with [Ping Beacons](/learn/orchestration/ping-beacons) for low latency.
* Handle server state [Persistence](/learn/orchestration/persistence) reliably to mitigate outages and rollbacks.
  * Reserve standby compute with [Private Fleets](/learn/orchestration/private-fleets) for persistent 24/7 servers.
  * Configure and customize [Managed Clusters](/learn/advanced-features/managed-clusters) for highly available kubernetes apps.
* Upload new server builds and manage versioning easily with our free [Container Registry](/learn/advanced-features/edgegap-container-registry).

Keep reading to learn more about best practices and recommendations for your setup.

[^1]: Developer Operations


# Apps and Versions

Learn about versioning and applications - concepts and best practices for deeper understanding.

## 📦 Applications

Applications encapsulate server projects. This separation of context is particularly useful if you:

* work on multiple games or non-game projects (consolidated billing),
* work on external projects as a co-developer (transfer ownership later),
* depend on multiple loosely coupled server types with different scaling patterns or requirements.

You can manage your Applications on Edgegap using our plugins, [dashboard](https://app.edgegap.com/application-management/applications/list), or our API.

{% hint style="success" %}
Explore our [Applications API reference](https://docs.edgegap.com/api/#tag/Applications), or read more about our [Management API](https://docs.edgegap.com/api/).
{% endhint %}

## 🏷️ App Versions

As you develop your application and continuously produce new builds, you will need to store each build as a separate version to:

* **maintain compatibility** between your clients and server,
* compare various aspects of your **incremental releases** (performance, user sentiment),
* test **multiple app versions simultaneously** (development, quality assurance, staging, beta).

{% hint style="info" %}
Each App version points to one build artifact of your choice. Multiple versions may point to the same build.
{% endhint %}

You can manage your App versions on Edgegap using our [dashboard](https://app.edgegap.com/application-management/applications/list), or our API.

{% hint style="success" %}
Explore our [App versions API reference](https://docs.edgegap.com/api/#tag/Applications/operation/app-version-post), or read more about [API](https://docs.edgegap.com/api/).
{% endhint %}

Each version is uniquely identified within it’s parent Application by **App version name**. You are free to decide your own naming convention. Here’s a few popular examples to inspire your choice:

* `2024.01.30-16.23.00-UTC` - timestamps are transparent for keeping many past versions,
* `1.1.0` - [semantic versioning](https://semver.org/) is a great choice to communicate scope of changes,
* `dev` , `staging`, `qa`, `prod` - keeping only the latest version per environment is very easy,
* `blue`, `green` - versions can be used as aliases for a rolling update release strategy.

{% hint style="success" %}
You can change your approach at any time, as long as you maintain client/server compatibility.
{% endhint %}

{% hint style="info" %}
You may disable any App or Version in our [dashboard](https://app.edgegap.com/application-management/applications/list) to **safeguard against human (dev) errors**.
{% endhint %}

{% hint style="info" %}
Free Tier is limited to 2 Applications, 2 versions, and 5 GB of Container Registry storage.
{% endhint %}

### Combine Versioning Strategies

Oftentimes, the best solution is a mix of versioning strategies, for example:

* using timestamps or semantic versioning for dev builds, for more granular tracking;
* keeping `staging`, `qa` and `prod` versions with environment-specific parameters;
* alternating `blue` and `green` versions as aliases for [zero matchmaking downtime updates](https://docs.edgegap.com/docs/gen2-matchmaker#rolling-updates-ab-tests).

## 🧱 Required Parameters

These fundamental parameters must be always defined.

### Resource Requirements

In addition to the **version’s name**, several parameters are required to create a new version:

* **vCPU** - how many virtual CPU units your app needs to run (1024 units = 1 vCPU),
  * **the minimum allowed vCPU amount is 0.25 vCPU (256 units),**
  * this setting can’t be edited on an existing App version, you must create a new version.
* **Memory** - how many megabytes of RAM your app needs to run (1024MB = 1GB),
  * this setting can’t be edited on an existing App version, you must create a new version.
* **GPU** - how many Graphical Processing Units your app needs to run,
  * this feature is not available yet, please contact us if you’re interested.

{% hint style="success" %}
Versions automatically include RAM in 2:1 RAM-vCPU ratio, **allowing up to 512MB of RAM per 0.25 vCPU**.
{% endhint %}

{% hint style="info" %}
Our server machines use AMD/Intel CPUs with clock speed 2.4 - 3.2 GHz, varying by location. To ensure your server has sufficient resources available, reach out on [Community Discord](https://discord.gg/MmJf8fWjnt).
{% endhint %}

### Image Details

These parameters will help our system decide which build of your server should be started later:

* **Registry** - `registry.edgegap.com` if you’re using our [Container Registry](https://docs.edgegap.com/docs/container/edgegap-container-registry),
  * to use a third party registry input your third party registry docker credentials,
  * registry serves as a shared storage service for your and other user’s repositories.
* **Image Repository** - refers to your Application’s dedicated repository,
  * find all of your repositories on our [dashboard’s Container Registry page](https://app.edgegap.com/registry-management/repositories/list),
  * each repository may include multiple tags of your server image.
* **Tag** - refers to a specific build artifact (version) of your server image,
  * our plugins copy the tag values from App version names by default,
  * you can view locally stored tags in Docker Desktop Images or using docker CLI.

{% hint style="danger" %}
:x: **DO NOT - overwrite existing tags or use `latest` tag** to avoid deploying outdated (cached) builds.\
:white\_check\_mark: **DO - increase your version tag always** to deploy the intended build and prevent release issues.
{% endhint %}

* **Private Registry** - if your repository’s access is protected (private repository), we’ll also need:
  * **Username Token** - your registry’s programmatic access username,
  * **Password Token** - your registry’s programmatic access password,
  * for Edgegap [Container Registry](https://docs.edgegap.com/docs/container/edgegap-container-registry), you may [copy these values from our dashboard](https://app.edgegap.com/registry-management/repositories/list),
  * these are not required for public repositories.

<details>

<summary>Troubleshooting and FAQ</summary>

I received error `401 Unauthorized` when pushing my server image.

* This means you haven’t signed in to your container registry. See Container Registry for [Edgegap Container Registry instructions](https://docs.edgegap.com/docs/container/edgegap-container-registry#getting-your-credentials), or the equivalent for your registry provider. Repeating your last operation will not resolve the error.

***

I received error `403 Forbidden` when pushing my server image.

* This means that either your currently signed in registry user doesn’t have sufficient permissions (typically to push a new image), or that you are signed into the incorrect registry provider. Try signing out and signing in with the correct provider and user with sufficient permissions. Repeating your last operation will not resolve the error.

***

What is the difference between a registry, repository, and a project?

* Think of registry as a storage facility, repository as a storage unit, and project as a storage unit number. Each registry typically includes many repositories, some public, some private to organizations and users.
* Example registry: `registry.edgegap.com` .
* Example repository: `registry.edgegap.com/my-edgegap-org/my-game-server`.
* Example project name: `my-game-server` .

***

When pushing new image tags / builds my changes are not reloading correctly.

* Make sure that every time you rebuild, you push with a new image tag. Edgegap’s internal caching system uses tag names and in case you overwrite a tag value (e.g. `latest`) it will not pick up on the new build.

***

Can I tag the same build artifact multiple times?

* Yes, you can tag the same artifact multiple times without issues, serving as multiple aliases to the same build. Keep reading to learn how to remove the tags later.

***

What happens when I delete a tag? Why can’t I delete a specific artifact using a hash?

* Deleting a tag will also cause the associated build artifact to be deleted, if there are no other tags associated with the artifact at the time of [the API request](https://docs.edgegap.com/api/#tag/Container-Registry/operation/image-tag-delete).
* Due to docker API standards and to ensure best possible user experience, we only provide an interface for deleting tags. See point above regarding deleting build artifacts.

</details>

## ⚙️ Optional Parameters

These parameters may be configured to further customize your deployments.

### Injected Variables

Custom environment variables will be injected for all deployments on this version:

* common examples include: engine arguments, third party secrets and endpoints,
* see [Deployments](/learn/orchestration/deployments#injected-environment-variables) for understanding different ways environment variables can be injected depending on deployment’s context, in addition to app version variables,
* each environment variable may contain up to 4KB (kilobytes) of string data.

{% hint style="warning" %}
Make sure to **set your sensitive variables (secrets, tokens) as hidden** for added security!
{% endhint %}

### Active Caching

:star2: [**Upgrade to Pay as You Go tier**](https://app.edgegap.com/user-settings?tab=memberships) **to unlock 0.5 second deployment time worldwide!**

**Speed up deployments and start servers within seconds, no standby servers required.** Server image associated with this app version will be preloaded in all of our worldwide locations automatically.

Caching will take full effect once your app version's caching level reaches 🟢 Good.

{% hint style="success" %}
Multiple app versions may reuse the same image tag. **Enabling cache for one version will enable it for all versions linked with the same image tag automatically**, making parametrized deployments easy.
{% endhint %}

{% hint style="info" %}
Image is also passively cached at deployment, only on the host machine where it was deployed.
{% endhint %}

{% hint style="warning" %}
**Images are removed from cache if they aren't deployed for 72 consecutive hours.**
{% endhint %}

### Port Mapping

Each server requires at least one port in order to accept incoming client connections:

* **Port** value refers to the **internal port** value, usually from your netcode integration,
* **Protocol** will depend on your netcode integration transport,
* **Name** is a human-readable identifier for your own needs, may be the same as Port,
* **Verifications** can be enabled to ensure your container is initialized before marked READY.

{% hint style="success" %}
Most games will only require adding a single UDP port mapping for port `7777`.
{% endhint %}

While internal ports for the server process are defined as part of app version, **external ports are assigned at random once a deployment is created**, so that a potential malicious party (hacker) is slowed down and detected before they can cause damage.

<figure><img src="/files/P2jJ5MPUA2Dv51w4qen4" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Add more ports in your port mapping if your server communicates over multiple protocols.
{% endhint %}

### Safety Guardrails

These parameters help with various edge cases and general server troubleshooting:

* **Time Constraints** - these features can help you manage deployments’ resource lifecycle:
  * **Game Max Duration** can be set to gracefully shut down your servers after a given period, or set to `-1`  with [app version API create/edit](/docs/api/versioning#post-v1-app-app_name-version) for [Persistence](/learn/orchestration/persistence) with [Private Fleets](/learn/orchestration/private-fleets).
  * **Maximum time to deploy** can help you clean up deployments taking too long to start.
* **Process Restart Policy** - controls deployment behavior when your server process stops.
  * Always Restart (default) - will restart on successfully exit code (0) and any error exit.
  * Never Restart (recommended) - deployment stops on success and error exit codes.
  * Restart On Crash - restart only on error exit codes, useful for Persistent servers.

{% hint style="info" %}
Free Tier is limited to 2 Applications, 2 versions, and 5 GB of Container Registry storage.
{% endhint %}

### Log Storage

To export server logs after deployment stops, configure [Endpoint Storage](/docs/endpoint-storage) using an S3 bucket.

{% hint style="warning" %}
Logs of versions without external Storage will be deleted on deployment termination.
{% endhint %}

## ⏩ Update Consistency

In order to ensure that none of the parameters change when you create a new App version through our [dashboard](https://app.edgegap.com/application-management/applications/list), we recommend using the **Duplicate** feature in the top right corner of your previous App version’s dashboard page. When duplicating, you may edit any parameters before saving.

{% hint style="success" %}
**Duplicating or editing your App versions does not require rebuilding your server image.**
{% endhint %}

{% hint style="info" %}
See [Matchmaker Rolling Updates](https://docs.edgegap.com/docs/gen2-matchmaker#rolling-updates-ab-tests) for further **automation of releases**.
{% endhint %}


# Deployments

Learn about deployments and their lifecycle - concepts and best practices for deeper understanding.

## 🗺️ Orchestration

Start new servers within seconds to meet capacity demands with our cloud native edge computing approach. We treat servers as [cattle rather than pets](https://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/) - replacing faulty instances entirely instead of nursing each one manually.

{% hint style="info" %}
Your orchestration choice will **impact your devops cost, server cost, and scalability**.
{% endhint %}

{% hint style="success" %}
[Reach out in Discord](https://discord.gg/MmJf8fWjnt) to learn about hybrid orchestration options and optimizing your hosting cost.
{% endhint %}

To fully understand all pros and cons, let’s compare various orchestration methods. Some games will use multiple methods of orchestration depending on game loop design.

### Match-Bound

Short-lived (time-limited) servers scale down on match end, providing **best cost performance ratio**.

Sessions are typically automated through a [Matchmaking](/unity/matchmaking) service, which deploys servers just in time using strict rules, and optionally allows filling ongoing servers to [boost match fill rates](https://edgegap.com/blog/how-session-fill-rate-affects-your-multiplayer-hosting-costs).

👍 **Advantages**

* Best cost efficiency - scaling in real time to meet player demand minute by minute.
* Lowest devops cost due to region-less hosting, Edgegap automates 99% of tasks.
* Lowest ping due to 615+ sites in Edgegap’s public cloud infrastructure.
* Fastest scale up (burst-ability) in case of unexpected traffic spike.
* Highest standard of security and player cheating prevention (server authority).
* Minimal impact of unexpected server crash on players, only affecting a single match.

👎 **Disadvantages**

* Adopting a new orchestration mental model requires some ramp up effort initially.
* Servers running longer than 24 hours will be automatically terminated.

🧩 **Best Suited For**

* Latency-sensitive games - **when netcode optimization can't overcome high ping:**
  * First Person Shooters, Fighting Games, VR & XR (virtual and extended reality), …
* Games with an **upper limit on match duration by design**,
  * Battle Royale, PvPvE[^1], Coop Shooters, MOBA, Sports Games, ARPGs & Dungeon Crawlers, …

{% hint style="info" %}
Edgegap automatically scales all 615+ server locations up/down based on player activity in each region. Prepare for success - seamlessly [scale to 14 million concurrent users in 60 minutes](https://edgegap.com/resources/performance-benchmark).
{% endhint %}

### Regional Standby

Persistent world and social MMO game **server lifespan often exceeds individual player sessions**.

Sessions are usually assigned through a [Server Browser](/learn/server-browser) based on player preference (automated by region or custom search), with horizontal deployment pre-scaling based on regional capacity.

👍 **Advantages**

* Familiar and easy to understand, old-school approach for battle-scarred veterans.
* Highest standard of security and player cheating prevention (server authority).
* Easily predictable cost based on monthly commitment.

👎 **Disadvantages**

* Higher hosting cost - each region requires one or more idle standby servers (burst capacity).
* Higher devops cost - scaling, operations, and maintenance duplicated per region.
* Regions with smaller player base experience high ping due to joining servers far away.

🧩 **Best Suited For**

* Persistent worlds with user-generated content stored on server even when players go offline.
  * MMOs, Sandboxes with base building or object placement, Extraction Shooters, ...
* latency-tolerant games - **when server authoritative real-time physics aren’t required**:
  * mobile games, Coop Games, TCGs/CCGs, Turn-Based Strategies, …
* Asynchronous multiplayer, **where server crashes have minimal impact on player experience:**
  * race against ghosts, loot enemy base, timer-based building/farming games, …
* applications with heavy initialization process - when preparing servers takes minutes.

### Peer to Peer

Shift development efforts from ~~dedicated servers~~ to **relay netcode for non-competitive games**.

Related topics: listen servers, player-host authority, NAT punch-through.

👍 **Advantages**

* Lowest hosting cost, requiring only Relay servers to solve NAT punch-through.
* Lowest devops cost - maintenance required only for client builds and distribution channels.
* Minimal impact of unexpected server crash on players, only affecting a single match.
* Easy to implement and fast time to prototype, without any backend development required.

👎 **Disadvantages**

* Increased peer-to-peer netcode development effort requiring concurrent programming skills.
* Worst ping times and most sensitive to unfavorable network conditions (e.g. mobile internet).
* Weakest security, vulnerable to man-in-the-middle attacks and session hijacking.
* Risk dropping sessions when host leaves unless you implement custom host migration.

🧩 **Best Suited For**

* Coop & casual games - **when cheating doesn’t take away from fun or break the game**,
  * Kids Games, Exploration Games, Adventures, …

{% hint style="success" %}
See our [Distributed Relays](https://docs.edgegap.com/docs/distributed-relay-manager) for service enabling peer to peer with best in class latency and security.
{% endhint %}

## 📍 Server Placement

No matter which orchestration method you choose, picking the right server location for a group of players is critical in ensuring the best possible ping, and optimal player experience. Learn about different strategies for server placement, and how they impact your players.

{% hint style="info" %}
Your server placement strategy will **impact your players’ experience, retention, and your game reviews**.
{% endhint %}

{% hint style="success" %}
**Edgegap deploys in the** [**best possible location**](#server-score) **with available capacity**, for fast and low-latency matches.
{% endhint %}

<figure><img src="/files/ad8Pn38FKMdIwoIKi6JO" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
See [#deployment-balance-points](#deployment-balance-points "mention") to **analyze server placement in real time**, at scale.
{% endhint %}

### Server Score

Server score strategy uses Edgegap’s patented methodology, which **optimizes placement of servers for each match individually**. Performs non-intrusive telemetry to approximate each player’s network proximity to our server locations and choose the server which delivers best:

* **responsiveness** - provides lowest ping for all players on average,
* **fairness** - provides a balanced and fair ping for all players.

{% hint style="success" %}
Our [Matchmaker](/learn/matchmaking) uses **Server Score strategy by default, to ensure best possible experience**. To use this strategy with [Deploy APIs](https://docs.edgegap.com/api/#tag/Deployments), input players’ public IPs or geo coordinates in your deploy request.
{% endhint %}

**Unresponsive placement** - server is far away, high ping for all players:

<figure><img src="/files/Hi1iNdS0nkerkkhEFodf" alt=""><figcaption></figcaption></figure>

**Unfair placement** - uneven ping, one player is at a disadvantage:

<figure><img src="/files/PGLB2GUqpDmOLjixTyfx" alt=""><figcaption></figcaption></figure>

**Good placement example** - responsive and fair ping for all players:

<figure><img src="/files/DwCPbDKTDVoTUuLNm0WU" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
This strategy is **especially effective for hosting a group of players far away from each other** (North America vs. Europe, or West coast vs. East coast), frequently the case with pre-made lobbies.
{% endhint %}

### Geolocation

Alternatively, **provide target latitude & longitude coordinates or a public IP address of a ping beacon in preferred region**. This approach requires additional client-side geo-lookup implementation, fully relying on developer's solution.

{% hint style="warning" %}
This strategy is not recommended for [#match-bound](#match-bound "mention") orchestration, except for applications with strict regulatory requirements for inter-regional data transfers, or when player IP is unavailable.
{% endhint %}

{% hint style="success" %}
Alternatively, let players **pick a persistent (always online) server** from a list with [Server Browser](/learn/server-browser).
{% endhint %}

### Region Lock

Some designs or audiences are not intended to cross into neighboring regions for compliance, technical, or operational reasons. Consider [Server Browser](/learn/server-browser) with [regional scaling policies](/learn/server-browser#automated-scaling).

{% hint style="warning" %}
This strategy is not recommended for [#match-bound](#match-bound "mention") orchestration, except for applications with strict regulatory requirements for inter-regional data transfers, or when player IP is unavailable.
{% endhint %}

## 🟢 Connection Quality

Some games (and some players) are more sensitive to latency or lag than others. While player reports are a great indicator of incidents or regression bugs at scale, **players may lack deep understanding of networking concepts** and are quick to assign blame to studios, netcode, or servers.

Root cause of some issues may be hidden from players, so cooperation of studio and hosting provider may be crucial. **Edgegap’s priority is always to provide the best possible service.**

If you’re receiving numerous player reports, experiencing widespread outages, or repeated issues, please reach out to us immediately through a support ticket in our platform.

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

#### Low Latency

Player latency is a combination of latency from transferring data between:

* **physical devices -** the physical signal travelling across [Internet networking topology](https://en.wikipedia.org/wiki/Internet#Routing),
* **host to host** - resulting from protocol, transport, and security measures,
* **process to process** - resulting from (un)boxing and processing data in client/server.

Edgegap reduces physical latency by placing servers closer to your players for shorter responses and lower number of network hops. With locations across 17 cloud and bare metal providers, you get **best-in-class ping for players anywhere in the world**.

Server and internet coverage globally (not only with Edgegap) is limited due to factors like:

* **infrastructure availability** - internet connection quality in a given region may not be sufficient,
* **natural factors** - highly complex server racks require mostly stable environment.

#### High Availability

Availability of servers in various locations around the world will vary over time, changing multiple times throughout the day. Edgegap automatically **scales up/down** locations **on demand**, considering:

* **burst traffic** - deployments made within a 15 minute period,
* **vCPU requirements** - more vCPU per deployment increases overall demand for specific location,
* **provider offering** - some remote locations have less provider options available,
* **machine availability** - some locations may only offer 4 vCPU or 8 vCPU machines,
* **studio requests** for testing, quality assurance, early access, closed betas, or tournaments.

All applications' deploy requests are combined to assess location demand. All organizations have equal allocation priority by default, with the **possibility to add private server pools for enterprise customers requiring specific hardware or locations**.

{% hint style="success" %}
Please **reach out to us to plan a release**, or if you have any requests regarding location availability.
{% endhint %}

#### Player Issue Resolution

Player issues may be rooted in server bugs or provider incidents, but may also arise from third parties such as local ISPs, game services, bugs in low level libraries, infrastructure providers, or other sources.

When troubleshooting player reports or incidents, consider factors:

* **matchmaking quality** - players should be close to each other (same region) for [#server-placement](#server-placement "mention") to yield best results:
  * see [Matchmaking](/learn/matchmaking) and [Ping Beacons](/learn/orchestration/ping-beacons) for our recommendations,
  * see [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#player-tracing) to learn how to find server logs related to player reports,
* **regional issues:**
  * localized Internet Service Providers (ISPs) may be resolving an incident momentarily,
  * some regions (e.g. China, Russia) may be restricted due to localized sanctions,
* **caching level** - Edgegap will prioritize fast deployments in cached locations:
  * [activate caching to deploy your servers within seconds](/learn/orchestration/application-and-versions#other-parameters-optional),
* **maximum time to deploy** - deployments may fail due to slow and heavy initialization process:
  * see [Apps and Versions](/learn/orchestration/application-and-versions#safety-guardrails) to increase the timeout period,
  * delay initialization steps until absolutely necessary,
* **server image or integration issues**.

{% hint style="success" %}
**Display deployment IDs in client match history UI** to trace player reports when troubleshooting.
{% endhint %}

{% hint style="info" %}
Notify users about widespread bugs, temporary issues, and outages to mitigate negative sentiment.
{% endhint %}

## 🔄 Deployment Lifecycle

Edgegap deployments go through several lifecycle stages, denoted by deployment status.

#### 1. Start a Deployment

A deployment for **testing purposes** may be started with:

* [Unreal Engine](/unreal-engine) - Docker Extension or plugin for Unreal Engine projects,
* [Unity](/unity) - plugin for Unity projects,
* [Dashboard Web UI](https://app.edgegap.com/deployment-management/deployments/list) - easy to use web interface for testing server integration.

A deployment for **live production environment** should be started with:

* [Matchmaking](/learn/matchmaking) - find other players and start servers on-demand ([#match-bound](#match-bound "mention")).
* [Server Browser](/learn/server-browser) - pre-warm servers with long initialization ([#regional-standby](#regional-standby "mention")).
* [Deploy API](https://docs.edgegap.com/api/#tag/Deployments/operation/deploy) - server-to-server customized integration (custom scaling).

{% hint style="success" %}
**Save** `request_id`  **(Deployment ID) and tag Deployments** to identify and troubleshoot issues later.
{% endhint %}

{% hint style="info" %}
When testing with [Deploy API](https://docs.edgegap.com/api/#tag/Deployments/operation/deploy), you may override default Dockerfile `CMD` with custom command.
{% endhint %}

#### 2. Deploying

Once a deployment is started, our system will perform a number of steps in rapid succession:

* Telemetry - we’re measuring network responsivity from available data centers to each player,
* Deployment - we’re reserving capacity and preparing to start your server container,
* Container Boot - we’re starting the container, installing dependencies, and initializing,
* Post Processing - we’re adding log storage, monitoring, and finalizing the deployment.

{% hint style="success" %}
**Enable** [**Active Caching in your App Version**](/learn/orchestration/application-and-versions#active-caching) **to deploy servers within seconds.**
{% endhint %}

{% hint style="warning" %}
**Too Many Requests 429** - to ensure stability and prevent surprise invoices, we rate limit your organization at **40 req/s**. [Contact us](mailto:info@edgegap.com) to plan releases, estimate launch traffic, and prepare for success.
{% endhint %}

#### 3. Deployment Ready

Your container is fully initialized and your server is starting up now. For a few seconds to a minute, your server may be still initializing and may not respond to player requests until your game engine (or custom runtime) is fully ready to accept player connections.

{% hint style="success" %}
Once the deployment is Ready, **retry player connection until successful**, or until pre-defined client timeout.
{% endhint %}

{% hint style="danger" %}
**Server crashes are handled according to your** [**process restart policy**](/learn/orchestration/application-and-versions#safety-guardrails)**.** [Server state may be lost](/learn/orchestration/persistence#state-management).
{% endhint %}

#### 4. Deployment Error

Your deployment may end up in Unprocessable state at any point in time, for unexpected reasons. This is more likely to happen while testing your integration, or testing new server builds.

**You are not charged for Error deployments, which are automatically stopped after 24 hours.**

Troubleshooting steps:

* Verify Edgegap Service Status with [our uptime monitoring page](https://status.edgegap.com/).
* Try testing your server container locally using Docker Desktop to rule out Edgegap issues.

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

{% hint style="success" %}
**When asking for help,** **include your deployment ID and any useful details** so we can investigate promptly!
{% endhint %}

#### 5. Deployment Stopped

**We never stop your servers without your directive**, to prevent impacting your players’ experience negatively. Your deployment may be stopped for these reasons:

* **Self-Stop via** [**DELETE\_URL**](/learn/orchestration/deployments#injected-environment-variables) - deployment stopped itself after players left and match ended,
  * see [Unreal Engine](/unreal-engine#stop-deployments) and [Unity](/unity#stop-deployments) guides for stopping deployments correctly,
* **Stop from your backend** - your backend stopped this deployment using [Deployments API](https://docs.edgegap.com/api/#tag/Deployments/operation/deployment-delete),
* **Game Max Duration** - the allotted time in your [Apps and Versions](/learn/orchestration/application-and-versions#safety-guardrails) has expired,
* [Private Fleets](/learn/orchestration/private-fleets) Host running your deployment was deleted through a scheduled action.

{% hint style="info" %}
Once a deployment is stopped, **we trigger graceful termination** by sending `SIGTERM` signal to your main process, allowing a short termination period. Once expired, a `SIGKILL` signal is sent to stop deployment.
{% endhint %}

## 👀 Observability

Allow game servers to interoperate with third parties and gain operational insights.

### Discoverability

Once Ready, deployment is assigned a URL ([fqdn](https://en.wikipedia.org/wiki/Fully_qualified_domain_name)) and an external port for each internal port.

{% hint style="success" %}
Use **deployment tags (up to 40 chars) to easily mark your deployments** and [#filter-deployments](#filter-deployments "mention").
{% endhint %}

{% hint style="info" %}
**Outbound traffic (to clients or backend) from your game servers is never blocked** or filtered.
{% endhint %}

#### **Websockets (WS) and Secure Websockets (WSS)**

To use websocket-based netcode with Edgegap, you have two options:

* **managed certificate**, set up in 1 minute without writing any code:
  * configure your [Apps and Versions](/learn/orchestration/application-and-versions) to **use Websocket (WS) and Enable TLS Upgrade,**
  * use Edgegap URL to connect clients (e.g. `https://5fa53fa00a57.pr.edgegap.net/`)
* **self-managed certificate**, if you want to use your own custom domain:
  * configure your [Apps and Versions](/learn/orchestration/application-and-versions) to **use Secure Websocket (WSS)**,
  * configure your own TLS certificate flow with a custom DNS record (e.g. on [Cloudflare](https://www.cloudflare.com/application-services/products/ssl/)).

{% hint style="danger" %}
Uncaught server exceptions will cause the deployment's container to restart and invalidate TLS security. In such case, [stop your server](#id-5.-deployment-stopped) and [rematch players to a new deployment](/learn/matchmaking#custom-lobby). [Server state may be lost](/learn/orchestration/persistence#state-management).
{% endhint %}

### Injected Variables <a href="#injected-environment-variables" id="injected-environment-variables"></a>

Game servers often need additional information, such as server IP, internal port values, or other. Injecting read-only environment variables is a reliable cloud-agnostic way to pass parameters.

{% hint style="success" %}
Get variable values using [GetEnvironmentVariable in C#](https://learn.microsoft.com/en-us/dotnet/api/system.environment.getenvironmentvariable?view=net-8.0) or [GetEnvironmentVariable in C++](https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Core/GenericPlatform/FGenericPlatformMisc/GetEnvironmentVariable).
{% endhint %}

{% hint style="info" %}
See [App Version Variables](/learn/orchestration/application-and-versions#injected-variables) and [Matchmaker Variables](/learn/matchmaking/matchmaker-in-depth#injected-variables) in addition to Deployment Variables below.
{% endhint %}

#### **Custom Variables**

Define up to 20 custom variables for each deployment, each containing up to 4KB of string data.

{% hint style="warning" %}
**Avoid using reserved names (below), your custom variables will be overwritten otherwise!**
{% endhint %}

Access important information by reading variables injected to your servers by Edgegap:

#### **Identifiers**

* **`ARBITRIUM_REQUEST_ID`**  - e.g. `f68e011bfb01` .
  * Unique deployment ID, also referred to as request ID. Used to retrieve more information.
  * Deployment URLs always have format `{ARBITRIUM_REQUEST_ID}.pr.edgegap.net`.
* **`ARBITRIUM_PUBLIC_IP`**  - e.g. `162.254.141.66` .
  * Public IP address of this host, can be used to connect instead of URL.
* **`ARBITRIUM_HOST_ID`**  - e.g. `alpha-north-america-70364ef8` .
  * Unique identifier of the machine hosting your deployment, shared with other deployments.
* **`ARBITRIUM_DEPLOYMENT_TAGS`**  - e.g. `tag1,tag2` .
  * Comma-delimited user-defined deployment tags, [useful for easy searching and filtering](#filter-deployments).
* **`ARBITRIUM_PRIVATE_FLEET_ID`** - e.g. `PUBLIC_CLOUD` , or fleet ID if hosted on [Private Fleets](/learn/orchestration/private-fleets).

#### Resource Specifications

* **`ARBITRIUM_HOST_IN_PRIVATE_FLEET`** - e.g. `false` , indicating if hosted on [Private Fleets](/learn/orchestration/private-fleets).
* **`ARBITRIUM_HOST_BASE_CLOCK_FREQUENCY`**  - e.g. `2000` , processor frequency in MHz.
* **`ARBITRIUM_DEPLOYMENT_VCPU_UNITS`**  - e.g. `256`, allocated vCPU units (1024 = 1 vCPU).
* **`ARBITRIUM_DEPLOYMENT_MEMORY_MB`**  - e.g. `512`, allocated RAM in MB (1024 = 1 GB).

#### **Lifecycle Management**

* **`ARBITRIUM_DELETE_URL`**  - e.g. `https://api.edgegap.com/v1/self/stop/9f511e17/660`.
  * Callable from the deployment, [deployment will be gracefully stopped](#id-5.-deployment-stopped).
  * Requires unique one-time `ARBITRIUM_DELETE_TOKEN` in `Authorization` header.
* **`ARBITRIUM_DELETE_TOKEN`**  - e.g. `7df4cd933df87084b34ae80d8abde293`.
* **`ARBITRIUM_CONTEXT_URL`**  - e.g. `https://api.edgegap.com/v1/context/9170f5211e17/17`.
  * Only callable from the deployment, returns more deployment details.
  * Requires unique `ARBITRIUM_CONTEXT_TOKEN` in `Authorization` header.
* **`ARBITRIUM_CONTEXT_TOKEN`**  - e.g. `dfaf50b9333b9ee07b22ed247e4a17e6`.

#### **Discoverability**

* **`ARBITRIUM_PORT_GAMEPORT_INTERNAL`**  - e.g. `7777` , internal port for server listener.
* **`ARBITRIUM_PORT_GAMEPORT_EXTERNAL`**  - e.g. `31504` , external port for client connections.
  * External port values are randomized for each deployment for security purposes.
* **`ARBITRIUM_PORT_GAMEPORT_PROTOCOL`**  - e.g. `UDP` , protocol of your netcode transport.

{% hint style="success" %}
Examples assume you have named your port `gameport` (default). **Each port adds an additional set of sanitized** [Apps and Versions](/learn/orchestration/application-and-versions#port-mapping) **variables:** `@Super Port!` ⇒ `ARBITRIUM_PORT_SUPER_PORT_INTERNAL` .
{% endhint %}

* **`ARBITRIUM_BEACON_ENABLED`**  - e.g. `true`, if deploying on [Private Fleets](/learn/orchestration/private-fleets) with [Ping Beacons](/learn/orchestration/ping-beacons).
* **`ARBITRIUM_HOST_BEACON_PUBLIC_IP`**  - e.g. `139.177.198.69` , public IP of the closest beacon.
* **`ARBITRIUM_HOST_BEACON_PORT_UDP_EXTERNAL`**  - e.g. `30199`, for ping measurement over UDP.
* **`ARBITRIUM_HOST_BEACON_PORT_TCP_EXTERNAL`**  - e.g. `30456`, for ping measurement over TCP.

#### **Structured Information (JSON as string)**

{% hint style="info" %}
Environment variables are **stored as stringified JSONs**, parse them using an SDK or a custom method.
{% endhint %}

<details>

<summary><strong>ARBITRIUM_DEPLOYMENT_LOCATION</strong>:<br>- Detailed information about deployment location.</summary>

```json
ARBITRIUM_DEPLOYMENT_LOCATION="{
  "city": "Montreal",
  "country": "Canada",
  "continent": "North America",
  "administrative_division": "Quebec",
  "timezone": "Eastern Time",
  "latitude": 45.513707,
  "longitude": -73.619073
}"
```

</details>

<details>

<summary><strong>ARBITRIUM_PORTS_MAPPING</strong>:<br>- Detailed information about your internal and external ports.</summary>

```json
ARBITRIUM_PORTS_MAPPING="{
  "ports": {
    "gameport": {
      "name": "Game Port",
      "internal": 7777,
      "external": 31504,
      "protocol": "UDP"
    },
    "webport": {
      "name": "Web Port",
      "internal": 8888,
      "external": 31553,
      "protocol": "TCP"
    }
  }
}"
```

</details>

### Dashboard Monitoring

Our [Dashboard](https://app.edgegap.com/) provides utilities to monitor your server scalability and assist with operations.

#### Analytics

{% hint style="success" %}
Find [analytics dashboards in the sidebar menu](https://app.edgegap.com/analytics/dashboards/list) under category Server Hosting & Orchestration.
{% endhint %}

:star2: [**Upgrade to Pay as You Go tier**](https://app.edgegap.com/user-settings?tab=memberships) **to unlock detailed server performance metrics and insights:**

* **General Insights:** monitor releases with live server count per version + resource usage overview,
* **CPU Insights**: troubleshoot lagging servers due to processor-heavy operations,
* **Memory Insights**: mitigate server restarts due to exceeding allocated memory,
* **Networking Insights:** detect inefficient networking patterns and optimize netcode.

<figure><img src="/files/BixlUURe9fQmaCVZRYmT" alt=""><figcaption></figcaption></figure>

#### Deployment Map

{% hint style="success" %}
Find deployment map in [your deployment details page on Dashboard](https://app.edgegap.com/deployment-management/deployments/list).
{% endhint %}

Preview deployment location, available locations, and estimated player locations on the map:

<figure><img src="/files/N9e1fcgW3GYgUyiasXmT" alt=""><figcaption></figcaption></figure>

#### Deployment Balance Points

{% hint style="success" %}
Find deployment balance points heatmap in [your application details page on Dashboard](https://app.edgegap.com/application-management/applications/list).
{% endhint %}

Preview deployment balance points heatmap and filter by [Apps and Versions](/learn/orchestration/application-and-versions). Balance points are approximate locations with equal network proximity to each player in a given deployment:

<figure><img src="/files/4gISO5ZFPcBUfgn9RbqS" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
Balance point hotspots in strange locations (e.g. Greenland) indicate matchmaking of players far from each other. Learn about [#connection-quality](#connection-quality "mention") and [Ping Beacons](/learn/orchestration/ping-beacons) to optimize your matchmaking.
{% endhint %}

#### Deployment Logs

{% hint style="success" %}
Find deployment logs in [your deployment details page on Dashboard](https://app.edgegap.com/deployment-management/deployments/list).
{% endhint %}

Deployment logs display information about [#deployment-lifecycle](#deployment-lifecycle "mention"):

<figure><img src="/files/CLEGgfTdWsWa5R15Kg02" alt=""><figcaption></figcaption></figure>

#### Container Logs

{% hint style="success" %}
Find container logs in [your deployment details page on Dashboard](https://app.edgegap.com/deployment-management/deployments/list).
{% endhint %}

Inspect your game server’s logs in case of issues, or when debugging:

<figure><img src="/files/uhXihiuDe9eJMbHQIcEu" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
**Once deployment stops, container logs are deleted.** Set up [third party S3 log storage](/docs/endpoint-storage) to save logs.
{% endhint %}

#### Container Metrics

{% hint style="success" %}
Find container metrics in [your deployment details page on Dashboard](https://app.edgegap.com/deployment-management/deployments/list).
{% endhint %}

Review container metrics (processor, memory, networking) to:

* identify common connection issues when [#troubleshooting](#troubleshooting "mention"),
* detect inefficient implementation patterns causing spikes in resource usage,
* pinpoint inefficient resource usage in particular scenarios,
* verify changes in your server’s resource usage during optimization,
* benchmark your server initialization resource consumption and duration.

History metrics display value averages with 1 minute time period, available in Free tier.

:star2: [**Upgrade to Pay as You Go tier**](https://app.edgegap.com/user-settings?tab=memberships) **to unlock precise metrics with 1 second time intervals.**

<figure><img src="/files/SE0UosH81HmeabKTBXeb" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
[Contact us](mailto:info@edgegap.com) prior to your release to request live hosting support for large scale releases.
{% endhint %}

### Context & Status

Additional deployment information can be retrieved in JSON format:

* from inside the deployment (game server), using [Deployment Context API](https://docs.edgegap.com/api/#tag/Context/operation/context-get),
* from outside the deployment (backend / third party), using [Deployment Status API](https://docs.edgegap.com/api/#tag/Deployments/operation/deployment-status-get).

{% hint style="info" %}
Context API (from deployment) requires Context API token, while Status API uses your Edgegap token.
{% endhint %}

{% hint style="danger" %}
**Too Many Requests 429 - Context and Status APIs are rate limited at 20 req/s per organization.**
{% endhint %}

{% hint style="success" %}
**Use** [#webhooks](#webhooks "mention") **for automations (custom matchmaking) to avoid rate limiting and ensure scalability.** Context and Status APIs are intended only for development, QA, and troubleshooting ops purposes.
{% endhint %}

### Filter Deployments

To quickly search amongst all deployments, you can [use our dashboard](https://app.edgegap.com/deployment-management/deployments/list):

<figure><img src="/files/MJwHTyEVCL2ctWgPj1Pd" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
Alternatively, let players **pick a persistent (always online) server** from a list with [Server Browser](/learn/server-browser).
{% endhint %}

[List deployments with API](https://docs.edgegap.com/api/#tag/Deployments/operation/deployments-get) and apply filters with backend integrations:

<table><thead><tr><th width="237">Deployment Attribute</th><th width="193">Operators</th><th>Example Value</th></tr></thead><tbody><tr><td><a href="/pages/0UXQAhtFuL0FkdoUmYTh#deployment-lifecycle"><code>status</code></a></td><td><a data-footnote-ref href="#user-content-fn-2"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-3"><code>neq</code></a></td><td><code>"ready"</code> or <code>"error"</code></td></tr><tr><td><a href="#observability"><code>request_id</code></a></td><td><a data-footnote-ref href="#user-content-fn-2"><code>eq</code></a> </td><td><a data-footnote-ref href="#user-content-fn-4"><code>"7e709a0d8efd"</code></a></td></tr><tr><td></td><td><a data-footnote-ref href="#user-content-fn-5"><code>in</code></a>  or <a data-footnote-ref href="#user-content-fn-6"><code>nin</code></a></td><td><a data-footnote-ref href="#user-content-fn-4"><code>[ "7e709a0d8efd", "4ba353100b4b" ]</code></a></td></tr><tr><td><a href="#discoverability"><code>tags</code></a></td><td><a data-footnote-ref href="#user-content-fn-2"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-3"><code>neq</code></a></td><td><code>"tagA"</code></td></tr><tr><td></td><td><a data-footnote-ref href="#user-content-fn-5"><code>in</code></a>  or <a data-footnote-ref href="#user-content-fn-6"><code>nin</code></a></td><td><code>[ "tagA", "tagB" ]</code></td></tr><tr><td><a href="#id-1.-start-a-deployment"><code>created_at</code></a></td><td><a data-footnote-ref href="#user-content-fn-2"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-7"><code>lte</code></a>  or <a data-footnote-ref href="#user-content-fn-8"><code>gte</code></a></td><td><a href="https://en.wikipedia.org/wiki/ISO_8601"><code>2025-05-12T20:03:20Z</code></a></td></tr><tr><td><a href="/pages/o1QaIOSMNzSkg8Ggd8ab"><code>application</code></a></td><td><a data-footnote-ref href="#user-content-fn-2"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-3"><code>neq</code></a></td><td><code>"my-app"</code></td></tr><tr><td></td><td><a data-footnote-ref href="#user-content-fn-5"><code>in</code></a>  or <a data-footnote-ref href="#user-content-fn-6"><code>nin</code></a></td><td><code>[ "my-app", "my-other-app" ]</code></td></tr><tr><td><a href="/pages/o1QaIOSMNzSkg8Ggd8ab"><code>version</code></a></td><td><a data-footnote-ref href="#user-content-fn-2"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-3"><code>neq</code></a></td><td><code>"1.0.0"</code></td></tr><tr><td></td><td><a data-footnote-ref href="#user-content-fn-9"><code>in</code></a>  or <a data-footnote-ref href="#user-content-fn-6"><code>nin</code></a></td><td><code>[ "1.0.0", "prod" ]</code></td></tr><tr><td><a href="/pages/58CVGf4IKRrTIMgc5xr5"><code>fleet_name</code></a></td><td><a data-footnote-ref href="#user-content-fn-2"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-3"><code>neq</code></a></td><td><code>"my-app-fleet-europe"</code></td></tr><tr><td></td><td><a data-footnote-ref href="#user-content-fn-5"><code>in</code></a>  or <a data-footnote-ref href="#user-content-fn-6"><code>nin</code></a></td><td><code>[ "fleet-eu", "fleet-us" ]</code></td></tr><tr><td></td><td><a data-footnote-ref href="#user-content-fn-10"><code>ilike</code></a></td><td><code>"%-eu%"</code></td></tr><tr><td><a href="/pages/58CVGf4IKRrTIMgc5xr5"><code>host_name</code></a></td><td><a data-footnote-ref href="#user-content-fn-2"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-3"><code>neq</code></a></td><td><code>"alpha-north-america-95fab093"</code></td></tr><tr><td></td><td><a data-footnote-ref href="#user-content-fn-5"><code>in</code></a>  or <a data-footnote-ref href="#user-content-fn-6"><code>nin</code></a></td><td><code>[ "alpha-north-america-95fab093" ]</code></td></tr><tr><td></td><td><a data-footnote-ref href="#user-content-fn-10"><code>ilike</code></a></td><td><code>"%north-america%"</code></td></tr></tbody></table>

{% hint style="info" %}
Each attribute can have at most 1 filter operator in a single request. See [API Reference](/docs/api) for more.
{% endhint %}

Sort results by multiple fields in the order they appear in request:

| Deployment Attribute                                               | Order                                                                   |
| ------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| [`created_at`](#id-1.-start-a-deployment)                          | [`asc`](#user-content-fn-11)[^11] or [`desc`](#user-content-fn-12)[^12] |
| [`available_session_sockets`](broken://pages/LnFb0k5UDlebSXKRuXL5) | [`asc`](#user-content-fn-13)[^13] or [`desc`](#user-content-fn-14)[^14] |

Example filter queries:

<details>

<summary>List <a href="#id-4.-deployment-error">Deployments in Error</a> to troubleshoot and remove.</summary>

Encoded URL:

```
https://api.edgegap.com/v1/deployments?query={"filters":[{"field":"status","operator":"eq","value":"error"},{"field":"application","operator":"eq","value":"my-app"},{"field":"version","operator":"eq","value":"green"}],"order_by":[{"field":"created_at","order":"desc"}]}
```

Formatted JSON query:

```json
{
  "filters": [
    {
      "field": "status",
      "operator": "eq",
      "value": "error"
    },
    {
      "field": "application",
      "operator": "eq",
      "value": "my-app"
    },
    {
      "field": "version",
      "operator": "eq",
      "value": "green"
    }
  ],
  "order_by": [
    {
      "field": "created_at",
      "order": "desc"
    }
  ]
}
```

</details>

<details>

<summary>List <a href="/pages/kBCQHitUL4u9O0iTPk7x#rolling-updates-and-ab-tests">Deployments with outdated App version</a> to confirm a release is complete.</summary>

Encoded URL:

```
https://api.edgegap.com/v1/deployments?query={"filters":[{"field":"status","operator":"eq","value":"ready"},{"field":"application","operator":"eq","value":"my-app"},{"field":"version","operator":"eq","value":"blue"}],"order_by":[{"field":"created_at","order":"desc"}]}
```

Formatted JSON query:

```json
{
  "filters": [
    {
      "field": "status",
      "operator": "eq",
      "value": "ready"
    },
    {
      "field": "application",
      "operator": "eq",
      "value": "my-app"
    },
    {
      "field": "version",
      "operator": "eq",
      "value": "blue"
    }
  ],
  "order_by": [
    {
      "field": "created_at",
      "order": "desc"
    }
  ]
}
```

</details>

{% hint style="success" %}
Don’t forget to add the `Authorization` header with your Edgegap API token in the request.
{% endhint %}

### Webhooks

Receive simple HTTP notifications in your game backend for changes in [#deployment-lifecycle](#deployment-lifecycle "mention") by specifying a webhook URL in your [deployment API request](/docs/api/dedicated-servers#post-deployments). Available for:

* On Ready: deployment container [started successfully](#id-1.-start-a-deployment) (server starts initializing after this),
* On Error: deployment couldn't be started and a [#id-4.-deployment-error](#id-4.-deployment-error "mention") occurred,
* On Terminate: [#id-5.-deployment-stopped](#id-5.-deployment-stopped "mention") and game server is no longer reachable.

Ready and Error webhooks will never be triggered for the same deployment.

<details>

<summary>Example Webhook Payload</summary>

```json
{
  "request_id": "f68e011bfb01",
  "application": "my-game-server",
  "version": "2024.01.30-16.23.00-UTC",
  "fqdn": "f68e011bfb01.pr.edgegap.net",
  "public_ip": "162.254.141.66",
  "deployed_at": "2026-02-10T20:35:48Z",
  "termination_scheduled_at": "2026-02-10T21:35:48Z",
  "ports": {
    "gameport": {
      "external": 31504,
      "internal": 7777,
      "protocol": "UDP",
      "name": "gameport",
      "tls_upgrade": false,
      "link": "f68e011bfb01.pr.edgegap.net:31504",
      "proxy": null
    }
  },
  "location": {
    "city": "Montreal",
    "country": "Canada",
    "continent": "North America",
    "administrative_division": "Quebec",
    "timezone": "Eastern Time",
    "latitude": 45.513707,
    "longitude": -73.619073
  },
  "tags": [
    "tag1",
    "tag2"
  ],
  "host_id": "alpha-north-america-70364ef8",
  "host_in_private_fleet": false,
  "private_fleet_id": "PUBLIC_CLOUD",
  "vcpu_units": 256,
  "memory_mib": 512
}
```

</details>

{% hint style="warning" %}
**Webhooks are not retried**, so if your backend does not process the request due to rate limiting or an error, the webhook may be lost. Use webhooks only for **non-critical use cases or debugging purposes**.
{% endhint %}

{% hint style="info" %}
Webhooks observe deployment lifecycle, but are not aware of your scene/level initialization state. To observe loading progress of your scene/level, implement a custom webhook in your game server.
{% endhint %}

## 🚨 Troubleshooting

When troubleshooting deployments:

1. verify there are no errors in your [#deployment-logs](#deployment-logs "mention") and [#container-logs](#container-logs "mention"),
2. run your server locally to rule out integration bugs,
3. review troubleshooting steps on this page,
4. reach out to us in [Community Discord](https://discord.gg/MmJf8fWjnt) and include your deployment ID.

{% hint style="info" %}
See [#player-issue-resolution](#player-issue-resolution "mention") for our recommendations on navigating player community feedback.
{% endhint %}

<details>

<summary>Can’t connect clients to server - <code>Request timed out.</code>, <code>请求超时</code> , <code>ConnectionFailed</code> , or <code>Port verification failed</code>.</summary>

* First, make sure that the deployment is Ready, and there are no runtime exceptions or errors in your deployment log. If your deployment stopped, inspect logs in our [Dashboard](https://app.edgegap.com/deployment-management/deployments/list).
* If you’re using Mirror netcode you need to have ["Auto Start Server”](https://mirror-networking.gitbook.io/docs/hosting/edgegap-hosting-plugin-guide#build-and-push) selected in your `NetworkManager` , rebuild, push, and redeploy your server.
* If you’re using FishNet netcode you need to enable [“Start on Headless”](https://fish-networking.gitbook.io/docs/manual/components/managers/server-manager#settings-are-general-settings-related-to-the-servermanager) in your `ServerManager`, rebuild, push, and redeploy your server.
* If you’re using Photon Fusion 2 netcode, please ensure that your server is passing the deployment public IP, external port and the `roomCode` on the server, and the same room code in the client in the [“NeworkRunner.StartGame”](https://doc.photonengine.com/fusion/current/manual/network-runner#creating-or-joining-a-room) parameter `StartGameArgs`. Deployment ID (e.g. `b63e6003b19f`) is a great choice as it’s globally unique and easily accessible to client by [Matchmaker](/learn/matchmaking/matchmaker-in-depth) assignment and to the [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#injected-environment-variables).
* Next, please verify that your port setting in your server build’s netcode settings matches the internal port in your [App version](https://app.edgegap.com/application-management/applications/list). You can change the port mapping by editing the [App version](https://app.edgegap.com/application-management/applications/list) without rebuilding. Find your protocol in your netcode integration.
* Please ensure that your game client is connecting to the **external port** shown on your Deployment details page, this value will be always randomized due to security reasons.
* If you’re using Secure Websocket (WSS) protocol in your netcode integration, please ensure that your [App version](https://app.edgegap.com/application-management/applications/list) port configuration for the WSS port has TLS Upgrade enabled.
* Are you located in China and are using [Smart Fleets](https://docs.edgegap.com/docs/deployment/session/fleet-manager/fleet)? Your connection may be blocked by the Great Firewall. Consider adding a server located in China to your fleet, or using a VPN to connect.

</details>

<details>

<summary>My deployment stopped/restarted and I can’t access it’s logs anymore.</summary>

* In case the server process crashes due to an exception, our system will attempt restarting the server automatically. Consider testing your server locally to uncover the root cause.
* We only keep logs for the duration of the deployment, if you wish to inspect logs after deployment stops, please [integrate a third party log storage](https://docs.edgegap.com/docs/deployment/endpoint-storage).
* See [#id-5.-deployment-stopped](#id-5.-deployment-stopped "mention") to discover all causes for stopping your deployment.

</details>

<details>

<summary>My deployment stopped automatically after X minutes.</summary>

* Free Tier deployments have a 60 minute time limit, please consider upgrading your account.
* All deployments will be terminated after 24 hours of runtime following our server sanitization policy, for infrastructure maintenance, and to prevent racking up unexpected costs when deployment wasn’t shut down properly. For long-running servers, consider using [Private Fleets](/learn/orchestration/private-fleets) with [Persistence](/learn/orchestration/persistence).
* See [#id-5.-deployment-stopped](#id-5.-deployment-stopped "mention") to discover all causes for stopping your deployment.

</details>

<details>

<summary>My deployment is ready but I’m not able to connect for several minutes afterwards.</summary>

* Once a deployment is Ready, your game engine initialization begins. This process may take anywhere from seconds to minutes, and the server doesn’t accept to player connections during this period.
* Consider optimizing your server initialization to decrease this time period.
* Game clients should retry connection in 1 second intervals for a limited amount of time (depending on your initialization duration), after which they return to matchmaking.
* Consider adding a loading scene so the server can perform initialization (and travel in case of Unreal Engine) at the same time as clients, while synchronizing state of both.

</details>

<details>

<summary>My Meta Quest device throws <code>HTTP 0: Cannot resolve destination host</code> .</summary>

* When building Unity apps for Android target, your Internet Access permission may be removed from the output APK client build artifact automatically.
* Re-add the permissions in (requires rebuilding client afterwards):
  * Project Settings / OpenXR / :gear: Meta Quest Support / Force Remove Internet Permissions (uncheck).
  * Player Settings / Internet Access (set to require).

</details>

<details>

<summary>What will happen if a player leaves my deployment?</summary>

* By default, servers don’t reject player connections. Authenticating players is up to your devs, since many different methods and player authentication providers can be used.
* Game clients may store connection information locally to attempt reconnecting in case of unexpected client crashes.
* To allow players to join games in progress, consider using [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#backfill) or [Sessions](https://docs.edgegap.com/docs/deployment/session).

</details>

<details>

<summary>My server shows 100% CPU utilization after becoming ready.</summary>

* This may not be an issue, as game engines tend to perform CPU-heavy operations during server initializations. If the CPU usage doesn’t drop after 2-3 minutes from deployment start, you may need to optimize your server or increase app version resources.
* Reducing tick rate can impact CPU usage as the server performs less messaging operations.
* If you’re using Mirror netcode you need to have ["Auto Start Server”](https://mirror-networking.gitbook.io/docs/hosting/edgegap-hosting-plugin-guide#build-and-push) selected in your `NetworkManager` , rebuild, push, and redeploy your server.
* If you’re using FishNet netcode you need to enable [“Start on Headless”](https://fish-networking.gitbook.io/docs/manual/components/managers/server-manager#settings-are-general-settings-related-to-the-servermanager) in your `ServerManager`, rebuild, push, and redeploy your server.
* You’re limited to 1.5 vCPU and 3GB of memory (RAM) in Free Tier.
* You may increase allocated resources when creating a new app version. You can duplicate your App version in our Dashboard and adjust these values as needed, without rebuilding your server or image.

</details>

<details>

<summary>My deployment is restarting repeatedly and shows error `OOM kill` .</summary>

* This behavior is caused by exceeding allocated memory amount. Consider optimizing memory usage with object pooling, compression, or removing unneeded objects in your scene.
* Ensure your project is loading the default scene containing your `NetworkManager` and the scene is included in Unity’s Build Settings.
* You’re limited to 1.5 vCPU and 3GB of memory (RAM) in Free Tier.
* You may increase allocated resources when creating a new app version. You can duplicate your App version in our Dashboard and adjust these values as needed, without rebuilding your server or image.

</details>

<details>

<summary>Sometimes, my server’s memory (RAM) usage spikes to a high value, is that a problem?</summary>

* As long as you stay within the allocated app version memory amount, this is not an issue.&#x20;
* Exceeding the allocated app version memory amount will cause \`OOM kill\` (see above).

</details>

<details>

<summary>Will my server performance be impacted by other servers running on the same machine?</summary>

* No, our platform ensures that allocated resources will not be used by other studios, or other servers on shared infrastructure. With Edgegap, there are no noisy neighbors.

</details>

[^1]: sessions may last up to 24 hours

[^2]: equals

[^3]: not equals

[^4]: request\_id (deployment ID)

[^5]: in array

[^6]: not in array

[^7]: lower than or equal

[^8]: greater than or equal

[^9]: &#x20;in array

[^10]: case insensitive pattern matching:

    * use `%`  for any sequence
    * use `_`  for any one character

[^11]: ascending, oldest first

[^12]: descending, newest first

[^13]: ascending, full first

[^14]: descending, empty first


# Private Fleets

Reserve standby capacity on monthly basis and choose your preference between Virtual Machines (Performance) or Bare Metal (Overdrive) specifications.

**Private Fleets are an optional addition to Edgegap Cloud (default),** a popular hosting option:

* enabling [Persistence](/learn/orchestration/persistence) with 24/7 always online servers, and
* favorable [Deployments](/learn/orchestration/deployments#match-bound) pricing with predictable traffic.

🌟 [**Upgrade to Pay as You Go tier**](https://app.edgegap.com/user-settings?tab=memberships) **to unlock private fleets hosting with standby capacity!**

{% hint style="success" %}
&#x20;Alternatively, see [Deployments](/learn/orchestration/deployments#match-bound) cloud orchestration to utilize fractional vCPU pricing.
{% endhint %}

## ✔️ Introduction

Private Fleets let you pick specific host locations and reserve dedicated standby compute capacity within our globally available provider-agnostic infrastructure, suitable for:

* **live games with predictable daily traffic** **patterns** and stable player communities,
* **persistent 24/7 servers** featuring open worlds with user-generated content placed by players,
* **MMO games** prioritizing social experiences with high amount of concurrent users per server.

## ↕️ Allocations

Orchestrating your fleet capacity efficiently can make or break your cost savings. Finding the optimal ratio between Fleet and Cloud Overflow hybrid requires multiple iterations.

### 1. Schedule Hosts

Planning your Private Fleet capacity is an important recurring monthly DevOps activity.

Scheduling additional hosts for your fleet ahead of time is recommended if you expect any special events impacting your capacity such as:

* global or regional launch, or a major update,
* blanket discount, or free test weekend,
* streamer event, tournament, or other public event.

{% hint style="warning" %}
The minimum commitment period for fleet hosts is one (1) month. Once your host is started, **the earliest date for scheduling host deletion and reducing cost is end of month**.
{% endhint %}

{% hint style="success" %}
Use early signals (e.g. wishlists) to project regional demand and mitigate overprovisioning. Struggling to project traffic with confidence? Start low and [#overflow-to-cloud](#overflow-to-cloud "mention").
{% endhint %}

#### Locations

Drop a pin on the map and preview your primary/fallback locations for new hosts.

Select a fallback strategy to control suitable locations for each centroid:

* **Proximity (default)** - 700 km centroid radius, for precise control.
* **Redundancy** - 1,400 km centroid radius, allowing more fallback locations.

<figure><img src="/files/x0bFaPA4VNnKjeArxTZc" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Location availability depends on chosen hardware specification. Overdrive availability is limited.
{% endhint %}

📢 API provisioning under development, [let us know](https://discord.gg/NgCnkHbsGp) about your Continuous Integration needs!

### 2. Discover Hosts

{% hint style="warning" %}
**Starting your Deployments manually, pasting URL and ports will not cut it for a live game.**
{% endhint %}

Automate popular game flows for managing sessions and scaling on demand with either:

{% columns %}
{% column width="33.33333333333333%" %}
[Matchmaking](/learn/matchmaking):

* Shorter Rounds
* On-Demand Matches
* Skill Rating and/or Custom Rules
  {% endcolumn %}

{% column width="33.33333333333333%" %}
[Server Browser](/learn/server-browser):

* Persistent or Rounds
* Social Regional Hubs
* Auto-Assign and/or\
  Custom Search
  {% endcolumn %}

{% column width="33.33333333333333%" %}
Custom Backend:

* Migrate Live Games
* [Deploy with v2 API](/docs/api/dedicated-servers)
* [Observe Webhooks](/learn/orchestration/deployments#webhooks)
  {% endcolumn %}
  {% endcolumns %}

Your session orchestration must be aware of available hosts to utilize fleet capacity:

<figure><img src="/files/CZ54U6AMTTJgT4MLeBPF" alt=""><figcaption></figcaption></figure>

**On-Demand** deployments:

* Matchmaking maintains a cached list of hosts, refreshed every minute.
* Matchmaking assigns one of the available beacons to each active host.
* Players receive host assignment depending on the best matched beacon.

**Pre-Warmed** deployments:

* Your DevOps administrator reviews available hosts and [configures policies](/learn/server-browser#automated-scaling).
* Players get an auto-assigned seat, or search and choose a server.

{% hint style="info" %}
Custom integrations fetch hosts and deploy servers using [Dedicated Servers](/docs/api/dedicated-servers#private-fleets) API.
{% endhint %}

### 3. Deploy Servers

Start deployments on Private Fleet using the same parameters as Cloud deployments, with the **added option to specify a prioritzed list of host IDs you wish to deploy to**.

Deployments will be started on the first private host with sufficient capacity in your request. If no listed host has capacity available at the time, deployments will [#overflow-to-cloud](#overflow-to-cloud "mention").

#### Overflow to Cloud

{% hint style="warning" %}
**Unexpectedly high player traffic may exceed your current fleet capacity**, causing some deployments to utilize Cloud Overflow instead of your Private Fleet. Cloud limitations apply.
{% endhint %}

Prepare for this possibility and review differences:

<table><thead><tr><th width="185">Behavior</th><th>Private Fleet</th><th>Cloud</th></tr></thead><tbody><tr><td>Server Placement</td><td><a href="#id-2.-discover-hosts">Beacons or Custom</a></td><td><a href="/pages/0UXQAhtFuL0FkdoUmYTh#server-score">Server Score</a></td></tr><tr><td>Max. Game Duration</td><td>Unlimited (see <a data-mention href="/pages/VOlexArJSLKHpTJQOwSm">/pages/VOlexArJSLKHpTJQOwSm</a>)</td><td>24 hours</td></tr><tr><td>CPU frequency</td><td>Depends on <a data-mention href="#hardware">#hardware</a></td><td>2.0 GHz</td></tr><tr><td>CPU:Memory Ratio</td><td>Depends on <a data-mention href="#hardware">#hardware</a></td><td>Increments 1 vCPU : 2 GB<br>(fractional vCPU possible)</td></tr><tr><td>Billing Model</td><td>Per Host, Monthly</td><td>Per vCPU and minutes used</td></tr></tbody></table>

## ☁️ Hardware

Pick one of our generic host specifications:

| Specification                                                            |                                                                                                                                     Performance |                                                                                                                                       Overdrive |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------: | ----------------------------------------------------------------------------------------------------------------------------------------------: |
| Best Suited For                                                          |                                                                                        <p>match-based games,<br>or with predictable traffic</p> |                                                                                  <p>CPU-intensive games (AI),<br>or above 1:2 CPU/RAM ratio</p> |
| CPU Specifications                                                       | <p>16 vCPU<br><a data-footnote-ref href="#user-content-fn-1">2.0</a> - <a data-footnote-ref href="#user-content-fn-2">3.7</a> GHz frequency</p> | <p>16 vCPU<br><a data-footnote-ref href="#user-content-fn-1">3.7</a> - <a data-footnote-ref href="#user-content-fn-2">5.1</a> GHz frequency</p> |
| RAM Specifications                                                       |                                                                                                                                           32 GB |                                                                                                                                           64 GB |
| Free Egress Included                                                     |                                                                                                                                   5 TB per Host |                                                                                                                                   5 TB per Host |
| <p><strong>Price, pre-paid monthly</strong></p><p>(commitment based)</p> |                                                                                                                            **$250.00 per Host** |                                                                                                                            **$290.00 per Host** |

{% hint style="success" %}
Estimate cloud/fleet ratios with [pricing calculator](https://edgegap.com/resources/pricing/calculator#important-notes). Get final fleet price [using dashboard reservation](https://app.edgegap.com/private-fleet-management/private-fleets/list).
{% endhint %}

**Your first month's reservation price and free egress quota are pro-rated.** This means you are charged only for the portion of time until the end of current month.

**Reservations automatically renew on the&#x20;first day of every month at 00:00 UTC until deleted.** Your card will be automatically charged the monthly amount for renewed hosts at this time.

{% hint style="warning" %}
A fleet with [active hosts](#lifecycle) may not swap specifications. Create a new fleet or delete active hosts first.
{% endhint %}

{% hint style="info" %}
Small amount of resources on each host is reserved for Operating System, Monitoring, and Orchestration.
{% endhint %}

#### Modifying Schedule and Deleting Hosts

You may modify or delete a schedule at any time prior to the scheduled time, at no charge. You may designate any number of reserved (active) private hosts for deletion at the end of the current billing period, prior to the renewal date. Deleted hosts will not be charged for subsequent renewals. Any deployments running on private hosts scheduled for deletion will be gracefully shut down at the time of deletion without additional warnings.

#### Delayed Start or Insufficient Provider Capacity

In the event of delayed private host start or insufficient provider capacity, your payment amount and invoice for the scheduled or renewal periods will be adjusted accordingly.

## 🔄 Host Lifecycle

Private Fleet hosts follow a simple lifecycle sequence.

#### 1. Pending

New hosts have been requested, we're starting them in order of distance from your pin.

In our [List Hosts API](/docs/api/dedicated-servers#get-private-fleets-fleet-name-hosts), hosts in this stage are indicated with status `PENDING`.

#### 2. Active

Host is active and responsive, ready to [/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-start-a-deployment](https://docs.edgegap.com/learn/orchestration/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-start-a-deployment "mention").

In our [List Hosts API](/docs/api/dedicated-servers#get-private-fleets-fleet-name-hosts), hosts in this stage are indicated with status `ACTIVE`.

#### 3. Scheduled for Deletion

While stile active, this host will be deleted at the end of the current billing period (month). This action can be reverted (cancelled) at any time until deletion takes place.

In our [List Hosts API](/docs/api/dedicated-servers#get-private-fleets-fleet-name-hosts), hosts in this stage are indicated with non-null `delete_schedule` .

#### 4. Deleting

When a host reaches the scheduled deletion time, new deployments will no longer be assigned to it.

In our [List Hosts API](/docs/api/dedicated-servers#get-private-fleets-fleet-name-hosts), hosts in this stage are indicated with status `PERMANENTLY_REMOVING`.

{% hint style="warning" %}
Deployments running on private hosts will be [gracefully stopped](https://docs.edgegap.com/learn/orchestration/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped) once the host is deleted.
{% endhint %}

{% hint style="success" %}
**Prevent frustration due to rollbacks and gameplay disruption** by notifying players and rerouting new deployments to other private fleet hosts, once a private host scheduled for deletion is nearing shutdown.
{% endhint %}

#### 5. Error and Other

In case of issues with the provider or the host, our platform marks the host as `DEFECTIVE`  and attempts to start a new host as replacement at no additional charge. The new host will always be started in the same location and bear the same name. For a short period of time, you may see both the defective and the new replacement host with the same name.

When transitioning between states, a [host may shortly](#user-content-fn-3)[^3] report `SERVICE_UNAVAILABLE` .

We reserve a few other states to indicate special circumstances: `ERROR` , `DOWN`, `MAINTENANCE` .

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

[^1]: base frequency

[^2]: turbo - short CPU bursts

[^3]: for a few milliseconds


# Persistence

Learn how to manage persistent worlds with 24/7 always online deployments on [Private Fleets](/learn/orchestration/private-fleets).

Many genres (MMOs, Sandboxes, Social Games) leverage persistent worlds to let players:

* meet and socialize with new friends; to nurture organic player communities,
* explore a living open world filled with user-generated content placed by players,
* engage in epic raid battles lasting hours with large groups or entire guilds.

Explore strategies to **provide the best possible player experience, keep cost under control, and remove player frustration due to outages or rollbacks**. Enhance the traditional server model by bringing the advantages of edge computing packaged for easy use by game developers.

{% hint style="success" %}
&#x20;Alternatively, see [Deployments](/learn/orchestration/deployments#match-bound) cloud orchestration to utilize fractional vCPU pricing.
{% endhint %}

## ✔️ Preparation

To enable persistent, uninterrupted 24/7 always online deployments:

* [create a new (or update an existing one) App Version with our API](/docs/api/versioning#post-v1-app-app_name-version),
  * specify `"max_duration": -1`  to prevent automated termination after 24h,
* Use [private fleet deploy API](/docs/api/dedicated-servers#post-private-fleets-deploy) to start [standby servers](/learn/orchestration/deployments#regional-standby) with [Persistence](/learn/orchestration/persistence).
  * Choose between Virtual Machines (Performance) or Bare Metal (Overdrive) specifications.

{% hint style="warning" %}
**Test your server scaling and** [**termination process**](https://docs.edgegap.com/learn/orchestration/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped) **to verify reliability of your cost controls. Server state stored in memory will be lost once the deployment is stopped, see** [#configuration-and-state](#configuration-and-state "mention").
{% endhint %}

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

## 🔑 Server Ownership

Explore pros and cons of modern and traditional ownership models with an edge computing twist.

### Studio Hosting

Server hosting is traditionally managed by the studio, covering cost of hosting from game revenue.

👍 **Advantages**

* transparent product pricing - cost of hosting is covered by license/subscription of player,
* strong client/server compatibility with loose coupling of clients, services, and scaling,
* more resilient to cheating and reverse engineering due to closed source nature of servers.

👎 **Disadvantages**

* community modding support is limited to ensure server integrity and stability.

### Community Servers

Let your players host and fund their own servers and remove need for third party rental services. Funnel revenue through your studio instead of third party lacking insight into quality of end user experience.

👍 **Advantages**

* enhanced modding support through curated list of modded [Apps and Versions](/learn/orchestration/application-and-versions),
* improved player feedback loop due to closer collaboration with community,
* reduced financial risk due to players covering cost of hosting.

👎 **Disadvantages**

* more operations for studio - moderating player requests and collecting payments,
* weaker client/server compatibility due to increased number of modded versions,
* prone to cheaters due to distributed codebase and possibility of reverse engineering.

## 🥛 Capacity & Scaling

Learn advanced techniques to optimize server availability, hosting cost, and quality of service.

### Capacity

**Deployments don't track or manage active player connections** after you [/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-start-a-deployment](https://docs.edgegap.com/learn/orchestration/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-start-a-deployment "mention") to give you absolute control and freedom to implement any design.

Implement capacity management to ensure your servers:

* maximize cost savings - [benchmark](/learn/orchestration/deployments#container-metrics) and utilize server resources efficiently,
* provide smooth gameplay - prevent overloading servers with too many concurrent players,
* prevent bad reviews due to crashes - catch and handle unexpected exceptions.

To ensure efficient server capacity management:

* release player slots if [players matched to game server](/learn/matchmaking/matchmaker-in-depth#player-tickets) don't connect within a few seconds,
* frequently send a minimal heartbeat message from clients to server to keep track of activity,
* disconnect clients and release player slots if no activity is detected for several seconds,
* prevent players from being added to servers with full capacity and no available player slots.

{% hint style="success" %}
See [Server Browser](/learn/server-browser) for automated capacity handling with our managed service.
{% endhint %}

{% hint style="warning" %}
**Amount of reserved deployment resources can't be changed during runtime.** Scale horizontally with new server instances utilizing adjusted [application versions](/learn/orchestration/application-and-versions) requiring more CPU or memory resources.
{% endhint %}

### Scale Up

**Scaling persistent servers doesn't require "guesstimating"** regional traffic or server cost. Reserve [Private Fleets](/learn/orchestration/private-fleets) capacity for [low tide](#user-content-fn-1)[^1] and automatically overflow to cloud during unexpected peaks.

{% hint style="success" %}
**Enable** [**Active Caching in your App Version**](/learn/orchestration/application-and-versions#active-caching) **to deploy servers within seconds.**
{% endhint %}

Implement server scaling strategies to:

* enable large scale hosting while carefully protecting against abuse,
* minimize wasted server cost due to empty standby servers,
* prevent long queue times by responding to increased player demand quickly.

<figure><img src="/files/uI5C3PnwdwG69XWTWitS" alt=""><figcaption><p>Auto-scaling Reference Architecture</p></figcaption></figure>

#### Integration Key Points

1. Clients integrate Scaling Authority - [Matchmaking](/learn/matchmaking), [Server Browser](/learn/server-browser), or a custom solution.
   1. Discover other players, reserve capacity in running servers, or request new servers if needed.
2. Scaling Authority assigns running servers or starts new servers to serve players.
3. Server notifies Scaling Authority in real time about server start/stop and player connection changes.
   1. Scaling Authority deletes (expires) stale records of unresponsive (crashed) servers.
4. Clients connect and establish game sessions with Server directly, proceed to gameplay.

**We strongly recommend scaling based on number of connections instead of physical load** (CPU & RAM), since momentary fluctuations in physical load may result in unpredictable availability.

{% hint style="warning" %}
**Amount of reserved deployment resources can't be changed during runtime.** Scale horizontally with new server instances utilizing adjusted [application versions](/learn/orchestration/application-and-versions) requiring more CPU or memory resources.
{% endhint %}

### Scale Down

Efficient scale-down policies are key to optimizing cost, but [shutting down servers](/docs/api/dedicated-servers#v1-self-stop-request_id-access_point_id) without caution may impact player experience negatively. **Consider these factors and test changes before releasing:**

**Is your detection of player activity / disconnection reliable?**

* Does absence of input reliably indicate player inactivity? Players often use bots, macros, and other techniques to fake activity and maintain active connection to avoid queue times.
* Are there any actions taken by active players often which are hard to fake?
* Is using bots or macros an issue or a feature with [#studio-hosting](#studio-hosting "mention") servers?

**Is shutting off servers easily and quickly reversible (scale back up)?**

* Once reaching [/pages/0UXQAhtFuL0FkdoUmYTh#id-3.-deployment-ready](https://docs.edgegap.com/learn/orchestration/pages/0UXQAhtFuL0FkdoUmYTh#id-3.-deployment-ready "mention"), your server may require additional time to perform engine initialization and [#state-management](#state-management "mention") (restoration of state). Do you incur any additional costs for compute or data transfer with game services? Does this wait time impact player experience?
* Can you hide server loading with a loading scene, mini-game, a lobby, or through other means?

**Are players bound to specific server instances or can they migrate easily?**

* How does connecting to a different server influence player's account, purchase history, social experience, progression, inventory, and other gameplay aspects?
* Review your [#recovery-objectives](#recovery-objectives "mention") and ensure critical data isn't lost.
* Implement automated methods or player tools for restoring critical data.
* Provide human support and communicate with your community about outages and issues.

## 🔎 Discoverability

To find active servers accepting new players, implement one or more discovery methods:

* Let players browse servers and pick from a list with [Server Browser](/learn/server-browser).
* Start new games when sufficient players join [Matchmaking](/learn/matchmaking).
  * Add players to [replace leavers in existing matches with backfill](/learn/matchmaking/matchmaker-in-depth#backfill).
* [Implement custom or third party game backend to discover servers](/learn/advanced-features/managed-clusters#nakama-by-heroic-labs).

## 💭 Configuration & State

Integrate services to define initial server requirements and manage player and server state.

### Configuration Management

Configuration refers to the **initial data passed to your server during deployment:**

* [environment-specific injected variables](/learn/orchestration/application-and-versions#other-parameters-optional):
  * e.g. client/server version compatibility data,
* [server location](/learn/orchestration/deployments#arbitrium_deployment_location-detailed-information-about-deployment-location), [server ports](/learn/orchestration/deployments#arbitrium_ports_mapping-detailed-information-about-your-internal-and-external-ports) and [other server information](/learn/orchestration/deployments#injected-environment-variables),
* [matchmaking information](/learn/matchmaking/matchmaker-in-depth#injected-environment-variables) or [custom deployment variables](/learn/orchestration/deployments#custom-variables),
* third party integration parameters, keys, and secrets.

{% hint style="info" %}
**Configuration is immutable** - it's read once after starting your server and doesn't change later on.
{% endhint %}

### State Management

State refers to data describing the **result of a series of previous player actions and server events:**

* player connection, player-controlled state changes (e.g. [Pawn](https://dev.epicgames.com/documentation/en-us/unreal-engine/pawn-in-unreal-engine)),
* changes related to objects contained in the level/scene (e.g. [Actor](https://dev.epicgames.com/documentation/en-us/unreal-engine/actors-in-unreal-engine), [Game Object](https://docs.unity3d.com/6000.0/Documentation/Manual/GameObjects.html)),
* changes related to [game mode](https://dev.epicgames.com/documentation/en-us/unreal-engine/game-mode-and-game-state-in-unreal-engine#gamemodes), [game state](https://dev.epicgames.com/documentation/en-us/unreal-engine/game-mode-and-game-state-in-unreal-engine#gamestate), or [game scene](https://docs.unity3d.com/6000.0/Documentation/Manual/CreatingScenes.html) information.

{% hint style="info" %}
**State data changes frequently.** Clients are selectively updated on relevant information by server authority.
{% endhint %}

{% hint style="success" %}
**Perform frequent state backups to prevent data loss** in case of unexpected client or server issues:

* asynchronously in real time using a third party service, e.g. [Nakama by Heroic Labs](/learn/advanced-features/managed-clusters#nakama-by-heroic-labs),
* on client/server startup or shutdown, as deserialized state files in [cloud object storage](https://www.linode.com/products/object-storage/).
  {% endhint %}

Game objects typically designate an owner who controls them, this can be either server or a player.&#x20;

#### Server Owned Objects

Server owned objects can be manipulated only by server. Connected players have limited read access to server owned objects. Server owned objects are usually not shared with other servers.

{% hint style="success" %}
Server owned objects can be **loaded by a replacement server in case of unexpected server crash**. Use [deployment ID](/learn/orchestration/deployments#basic-information) to identify your new save file when first launched, and to store server owned object state.
{% endhint %}

#### Player Owned Objects

Player owned objects can be manipulated both by players and server. Assigning ownership of persistent objects to players makes migration to other servers easier later on.

{% hint style="success" %}
**Back up state of player owned objects on player device or a game backend** between play sessions.
{% endhint %}

{% hint style="info" %}
Prevent cheating by validating changes with server authority. Authority and ownership can be separate.
{% endhint %}

### Recovery Objectives

In case of issues, some categories of data may be more sensitive to data loss, for example:

* account, subscription, purchase, and microtransaction data - **critical**,
* progression, achievement, leaderboards, and inventory data - **important**,
* cheat detection, moderation, performance, and error tracking data - **important**,
* player behavior, social, chat data - **low importance**.

{% hint style="success" %}
**Implement restoring purchases from independent transaction history source** for best experience.
{% endhint %}

We highly recommend discussing the following amongst your team:

* categories of data handled in your game clients and servers,
* importance and sensitivity of each category for your business and players,
* Recovery Point Objective (RPO) - acceptable amount of data loss before serious harm occurs,
* Recovery Time Objective (RTO) - acceptable amount of downtime before serious harm occurs.

{% hint style="danger" %}
**Server crashes are handled according to your** [**process restart policy**](/learn/orchestration/application-and-versions#safety-guardrails)**.** [Server state may be lost](/learn/orchestration/persistence#state-management).
{% endhint %}

## 👀 Observability

Long running persistent servers bring new observability challenges, specifically detecting anomalies in monitoring, logging, and bug tracking.

We **strongly recommend implementing alerts for server restarts** to gain more visibility over issues.

Our [Endpoint Storage](/docs/endpoint-storage) **log integration** **only transfers logs** **after** [/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped](https://docs.edgegap.com/learn/orchestration/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped "mention"), add custom logs and bug tracking (such as [Sentry](https://sentry.io/welcome/)) to troubleshoot partial failures and bugs.

{% hint style="success" %}
Consider reserving [Private Fleets](/learn/orchestration/private-fleets) standby capacity for games with predictable traffic patterns.
{% endhint %}

[^1]: lowest CCU during the day


# Ping Beacons

Use Ping Beacons to prevent matching with players in a far regions and improve player experience.

## 🟢 Connection Quality

Server connection quality varies for each player and is impacted by many factors, including:

* wifi signal strength (if wireless or mobile),
* Internet Service Provider condition,
* local network conditions,
* networking protocol used,
* usage of Virtual Private Networks (VPNs),
* or country-specific restrictions.

In order to minimize player ping and **provide the best possible player match** and make [Deployments](/learn/orchestration/deployments#server-placement) optimal, game clients **utilize Ping Beacons to measure latency** to several major networking nodes worldwide in real time. Measuring real packet round-trip time accounts for all of the factors mentioned above and provides the most accurate metric at any given point in time.

Keep in mind that **high beacon ping doesn’t equate to high server ping**, since servers are populated more densely than beacons. Number of beacons is balanced real-time for best coverage, least required data transfer, and shortest time to complete measurement.

In addition to Edgegap’s monitoring, we recommend **implementing your own client-side analytics** to keep track of your player demographics and connection quality when matchmaking, so issues can be discovered, triaged, and resolved as fast as possible.

## 🗼 About Ping Beacons

Retrieve a list of beacons that players can ping directly from:

* Matchmaker [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#matchmaking-api) from a game client,
* Edgegap API [endpoint /locations/beacons](https://docs.edgegap.com/api/#tag/Locations/operation/location-beacon-list) using [Edgegap API token](https://app.edgegap.com/user-settings?tab=tokens) (from your backend).

Ping beacons may be used for multiple purposes simultaneously:

* prevent high latency matches by [#measuring-round-trip-time](#measuring-round-trip-time "mention") automatically.
* provide a [#hub-selection-ui](#hub-selection-ui "mention") for players to cherry-pick favorite hubs,
* provide a [#region-selection-ui](#region-selection-ui "mention") for players to exclude far away regions,

{% hint style="info" %}
**Too Many Requests 429** - to ensure platform stability, we **rate limit your organization at** **40 req/s** for this API endpoint. Implement an [#active-cache](#active-cache "mention") to prevent hitting the rate limit.
{% endhint %}

{% hint style="success" %}
[#measuring-round-trip-time](#measuring-round-trip-time "mention") using **ICMP, UDP, or TCP ping is not rate limited**.
{% endhint %}

## 🗺️ Region Selection UI

Prevent users matchmaking against other users in a particular region by offering a list of regions to enable or disable in your game UI.

**Automatically disable high latency regions:**

* list all available beacons,
* perform [#measuring-round-trip-time](#measuring-round-trip-time "mention") to all beacons,
* **disable region if measured latency against all beacons exceeds a given threshold.**

Your threshold depends on game design specifics, we recommend matching in regions with latency below 250ms (milliseconds).

{% hint style="info" %}
To let players override [Deployments](/learn/orchestration/deployments#server-placement) provide beacon IP when [matchmaking](/learn/matchmaking/matchmaker-in-depth#endpoint-tickets) or [deploying](/docs/api/dedicated-servers#deployments). For best player experience, warn players this may result in increased latency due to remotely located server.
{% endhint %}

Some players with high ping to all beacons due to ISP[^1] issues or slow connection (e.g. wireless/mobile) may cause lags and degrade game experience for others. To mitigate this issue:

* Gradually expand allowed latency maximum and difference (see [Advanced Example Config](/learn/matchmaking#advanced-example)),
  * players with high ping may have to wait longer than usual to find a match.
* Alternatively, allow players to override measurement with manual region selection, only sending fake ping values for the player-selected regions only (e.g. 25ms for fast match),
  * this may negatively impact player experience of the players' teammates and opponents.

{% hint style="info" %}
**High beacon ping doesn’t always result in high server ping**. Deployments are available in more locations than beacons. Beacons are orchestrated in real time to prioritize global coverage and reliability.
{% endhint %}

{% hint style="success" %}
See [Matchmaking](/learn/matchmaking) for **automated ping measurement using our SDKs**.
{% endhint %}

## 📍 Hub Selection UI

Some game players prefer being able to select from an exhaustive list of locations.

**Automatically disable high latency Hubs:**

* list all available beacons,
* perform [#measuring-round-trip-time](#measuring-round-trip-time "mention") to all beacons,
* **disable hubs if measured latency exceeds a given threshold,**
* identify enabled Hubs in game UI using the City beacon property.

Your threshold will depend on your game design specifics, but we generally recommend matching in Hubs with latency below 150ms (milliseconds).

We also recommend choosing the name “Hub” in your UI, or a different **name that does not imply these are the only server locations available**. Edgegap orchestrate game servers worldwide across 615+ physical locations and 17+ data center providers to ensure deployment to the ideal location.

{% hint style="info" %}
To let players override [Deployments](/learn/orchestration/deployments#server-placement) provide beacon IP when [matchmaking](/learn/matchmaking/matchmaker-in-depth#endpoint-tickets) or [deploying](/docs/api/dedicated-servers#deployments). For best player experience, warn players this may result in increased latency due to remotely located server.
{% endhint %}

Some players with high ping to all beacons due to ISP[^1] issues or slow connection (e.g. wireless/mobile) may cause lags and degrade game experience for others. To mitigate this issue:

* Gradually expand allowed latency maximum and difference (see [Advanced Example Config](/learn/matchmaking#advanced-example)),
  * players with high ping may have to wait longer than usual to find a match.
* Alternatively, allow players to override measurement with manual region selection, only sending fake ping values for the player-selected regions only (e.g. 25ms for fast match),
  * this may negatively impact player experience of the players' teammates and opponents.

{% hint style="info" %}
**High beacon ping doesn’t always result in high server ping**. Deployments are available in more locations than beacons. Beacons are orchestrated in real time to prioritize global coverage and reliability.
{% endhint %}

{% hint style="success" %}
See [Matchmaking](/learn/matchmaking) for **automated ping measurement using our SDKs**.
{% endhint %}

## 🌡️ Measuring Round-Trip Time

In game development both terms “ping” and “latency” usually refer to [packet Round-Trip Time](https://www.cloudflare.com/learning/cdn/glossary/round-trip-time-rtt/).

See [/pages/xxQpefaT7MKuSh6JHPrg#id-5.-game-integration](https://docs.edgegap.com/learn/orchestration/pages/xxQpefaT7MKuSh6JHPrg#id-5.-game-integration "mention") for automatic ping measurement:

* Unity [Developer Tools](/unity/developer-tools#software-development-kit):
  * [install package using Unity Package Manager for free](https://github.com/edgegap/edgegap-unity-sdk),
  * [explore our getting started guide and complete examples](/unity/matchmaking).
* Unreal Engine [Developer Tools](/unreal-engine/developer-tools#integration-kit):
  * [read the documentation](https://egik.betide.studio/) by Betide Studios,
  * [install from Fab Marketplace](https://www.fab.com/listings/ff17ad88-12a1-49cf-9a41-31695ed11e16) (free for Personal use),
  * [import simple example blueprint](https://blueprintue.com/blueprint/m33u1okj/) (matchmaking) and customize to your needs.

Detailed overview of latency measurement process in [Matchmaker](/learn/matchmaking/matchmaker-in-depth):

<figure><img src="/files/KvaRgmgFHGRTfL1kBv7w" alt=""><figcaption></figcaption></figure>

## ⏰ High Availability

{% hint style="danger" %}
Beacons are automatically rescaled in real time - adding/removing/replacing existing beacons. Your clients and backend should account for this and **reload list of beacons before each matchmaking round**.
{% endhint %}

## 🧠 Active Cache

**If not using** [**Matchmaker**](/learn/matchmaking) (e.g. Advanced Matchmaker users), we recommend implementing additional scalability insurance before a large scale release. **Create your own game backend service** which keeps a centralized cache in memory, responding with beacon locations to game clients without using our Beacons List API for each client request.

This service should actively **make a single API request to update list of beacons every 60 seconds.**

[^1]: Internet Service Provider


# Matchmaking

Get started with Matchmaking quickly and explore example scenarios for various genres.

Matchmaking in match-based games generally aims to:

* **find other players** based on criteria like region, latency, skill, or game parameters;
* **search for servers** to join based on available capacity \[or ping, region, skill, map, mode];
* **start new server** if existing servers are full or don't satisfy player criteria.

Player experience comes first, defining our core objectives:

* high match fill rate and social feature integration (play with friends in groups),
* fast matches with controlled match quality (low latency, shared preferences),
* reliable and predictable matchmaking process with global availability.

{% hint style="success" %}
Alternatively, let players **pick a persistent (always online) server** from a list with [Server Browser](/learn/server-browser).
{% endhint %}

Follow along this video to get started with our Matchmaker service:

{% embed url="<https://youtu.be/HxtvzvJ1FTk>" %}

## ✔️ Preparation

**Testing this service is entirely free, no credit card required.**

Free Tier allows up to 3 hours of runtime on our shared test cluster, after each restart.

This tutorial assumes you have already:

* [understood Edgegap’s deployment model](https://docs.edgegap.com/learn/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-just-in-time-deployment-dedicated-servers),
* published your server application on Edgegap ([Unreal Engine](/unreal-engine), [Unity](/unity)),
* successfully connected from a game client to your server on Edgegap.

### Matchmaking Architecture

This guide will focus on **Matchmaking API and Backfill API**.

<figure><img src="/files/r1v8CqJvPRdis1dcuYmB" alt=""><figcaption></figcaption></figure>

There are four (4) important flows of data when matchmaking is involved:

1. [Matchmaking API](/learn/matchmaking/matchmaker-in-depth#matchmaking-api) **is used by Game Clients to communicate with Matchmaker:**
   1. for group management, server assignment, and monitoring,
   2. for ping measurement with [Ping Beacons](/learn/orchestration/ping-beacons).
2. [Deployments](/learn/orchestration/deployments) API is used to deploy, scale, and manage your Dedicated Servers by Matchmaker.
3. [Netcode Transports](/docs/sample-projects) are used to communicate between Game Clients and Dedicated Servers.
4. [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#backfill-match) **to replace or add players to a running Server.**

{% hint style="info" %}
After release, **your matchmaker will need to run 24/7** to ensure players across the world can join servers.
{% endhint %}

## 🍀 Simple Example

Start with a simple example and test the basic matchmaking player flow:

* creating the matchmaker instance on the shared [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#hosting-cluster),
* defining rules and settings in your matchmaker [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#configuration),
* testing player flow and managing tickets with [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#matchmaking-api).

### 1. Set Up on Free Tier

☑️ [Register for your free Edgegap account](https://app.edgegap.com/auth/register) and open the [Matchmaker dashboard page](https://app.edgegap.com/matchmaker-management-v2/matchmakers/list).

☑️ Click on **Create Matchmaker** first, then input:

* matchmaker name - for your own reference, e.g. `quickstart-dev` ,
* upload our Simple Example JSON configuration.

🍀 **Simple Example (Minimal Recommended Configuration):**

{% hint style="danger" %}
**Make sure to change the application** **`name`**  **and** **`version`**  **to match your** [Apps and Versions](/learn/orchestration/application-and-versions).
{% endhint %}

<pre class="language-json"><code class="lang-json">{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "profiles": {
    "simple-example": {
      "ticket_expiration_period": "5m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m",
      "application": {
        "name": "<a data-footnote-ref href="#user-content-fn-1">my-game-server</a>",
        "version": "<a data-footnote-ref href="#user-content-fn-2">2024.01.30-16.23.00-UTC</a>"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 1,
              "min_team_size": 2,
              "max_team_size": 2
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 100,
              "max_latency": 200
            }
          }
        },
        "expansions": {}
      }
    }
  }
}
</code></pre>

**Troubleshooting and FAQ:**

<details>

<summary><code>The application configuration is not valid for profile XYZ.</code></summary>

* We couldn’t find your [Apps and Versions](/learn/orchestration/application-and-versions), please verify `application`  values.

</details>

<details>

<summary><code>Docker image for '2024.01.30-16.23.00-UTC' is not cached.</code></summary>

[**🌟 Upgrade to Pay as You Go tier**](https://app.edgegap.com/user-settings?tab=memberships) **to unlock** [**instant deployments with Caching**](https://docs.edgegap.com/learn/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-start-a-deployment)**.**

* 4GB+ uncached images may take longer to deploy, resulting in [/pages/0UXQAhtFuL0FkdoUmYTh#id-4.-deployment-error](https://docs.edgegap.com/learn/pages/0UXQAhtFuL0FkdoUmYTh#id-4.-deployment-error "mention"). Consider optimizing your server image size ([Unreal Engine](/unreal-engine#optimize-server-build-size) / [Unity](/unity#optimize-server-build-size)).
* You may proceed anyway, though we recommend testing your deployment time.

</details>

☑️ If no validation errors appear, hit **Create and Start** and wait for the process to complete. This will result in a new free cluster starting, with your Simple Example matchmaker.

✅ You may now proceed to the next step.

### 2. Explore Configuration

As we release updates to Matchmaker, each new version uses [semantic versioning](/learn/matchmaking/matchmaker-in-depth#changelog) to clearly communicate the impact of changes by interpreting format `major.minor.patch`:

* 🔥 `major` versions include breaking changes and require integration review,
* 🌟 `minor` versions include substantial backwards-compatible improvements,
* 🩹 `patch` versions include bug fixes and minor improvements.

[Inspect tickets](/learn/matchmaking/matchmaker-in-depth#matchmaking-api) to better understand and debug possible matchmaking flows while in development. We recommend disabling inspect API for your live matchmaker.

**Some** [**deployments may result in Errors**](https://docs.edgegap.com/learn/pages/0UXQAhtFuL0FkdoUmYTh#id-4.-deployment-error)**.** We attempt to resolve this by retrying deployment up to `max_deployment_retry_count` times automatically (without client confirmation).

To ensure that unexpected client crashes or abandoned tickets do not linger and take up your matchmaker resources, unmatched tickets will be cancelled after `ticket_expiration_period` causing their status to change to `CANCELLED` and then permanently deleted after `ticket_removal_period` .

{% hint style="info" %}
[Learn how matchmaker maximizes match fill rate and reduce queue times to less than 10 seconds.](/learn/matchmaking/matchmaker-in-depth#find-match)
{% endhint %}

The core of our matchmaking logic is configured in [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#matchmaking-profiles). Each profile is a completely isolated matchmaking queue, pointing to [Apps and Versions](/learn/orchestration/application-and-versions#app-versions) with pre-defined amount of required CPU and memory (RAM) resources.

[In-Depth Look](/learn/matchmaking/matchmaker-in-depth#matchmaking-rules) in the initial rule set must be met for players to be grouped together, each defined by three properties:

* name of your choosing, e.g. - `match size`,
* rule type, also known as operator, e.g. - `player_count`,
* and lastly operator attributes, e.g. `team_count` or `max_team_size`.

**Player Count Rule**

This is a special rule defining how many players need to match to initiate assignment:

* `team_count` refers to number of teams, 1 team may be used for cooperative or free-for-all modes,
* `min_team_size` refers to the minimum number of players per team.
* `max_team_size` refers to the maximum number of players per team.

Our simple example demonstrates a cooperative game with 2 players.

{% hint style="warning" %}
Player Count rule **is required and may only be defined once** in your initial configuration rules.
{% endhint %}

**Latencies Rule**

**`latencies`** is a special rule optimizing the ping of player matches:

* reduce client-server latency by removing regions with high latency (above threshold),
* improve match fairness by grouping players with similar latency (below difference).

<details>

<summary>Rule Example: <code>beacons</code></summary>

`beacons` rule configured with `"difference": 100, "max_latency": 200`  will match:

:white\_check\_mark: Alice and Bob may match:

* Tokyo is discarded (>200 ms),
* latency for Chicago within 100 ms absolute difference.

<table><thead><tr><th width="180">Beacon City</th><th width="80">Match</th><th width="132">abs(A - B) [ms]</th><th width="164">Alice [ms]</th><th width="164">Bob [ms]</th></tr></thead><tbody><tr><td>Chicago</td><td><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span></td><td>75.0</td><td>12.3</td><td>87.3</td></tr><tr><td>Los Angeles</td><td><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td><a data-footnote-ref href="#user-content-fn-3">113.2</a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td>145.6</td><td>32.4</td></tr><tr><td><del>Tokyo</del></td><td>n/a</td><td>n/a</td><td><a data-footnote-ref href="#user-content-fn-4"><del>233.2</del></a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td><a data-footnote-ref href="#user-content-fn-4"><del>253.2</del></a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td></tr></tbody></table>

:x: Alice and Charlie will never match:

* no beacons have < 200 ms latency for both players,
* Alice lives in North America - Illinois,
* Charlie lives in Asia - Japan.

<table><thead><tr><th width="180">Beacon City</th><th width="80">Match</th><th width="132">abs(A - B) [ms]</th><th width="164">Alice [ms]</th><th width="164">Charlie [ms]</th></tr></thead><tbody><tr><td><del>Chicago</del></td><td>n/a</td><td>n/a</td><td>12.3</td><td><a data-footnote-ref href="#user-content-fn-4"><del>215.6</del></a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td></tr><tr><td><del>Los Angeles</del></td><td>n/a</td><td>n/a</td><td>145.6</td><td><a data-footnote-ref href="#user-content-fn-4"><del>238.3</del></a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td></tr><tr><td><del>Tokyo</del></td><td>n/a</td><td>n/a</td><td><a data-footnote-ref href="#user-content-fn-4"><del>233.2</del></a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td>24.2</td></tr></tbody></table>

</details>

{% hint style="warning" %}
Rule `latencies`  is **optional and may only be defined once in your initial configuration** rules.
{% endhint %}

✅ You may now proceed to the next step.

### 3. Review Instance Details

☑️ Review details of your new matchmaker in our dashboard once it’s initialized:

<figure><img src="/files/ZzccSP5Qd9bqzdhV2lFZ" alt=""><figcaption></figcaption></figure>

* **Status** indicates service health, may be ONLINE, OFFLINE, or ERROR.
* **Identifier** helps Edgegap staff find your matchmaker quickly if you need help troubleshooting.
* **Started at** can be useful to track down the latest update time.
* **Size** corresponds to one of our [Pricing Tiers](https://edgegap.com/resources/pricing#matchmaker).
* **API URL** will be used by Game Clients and Game Servers to communicate with your matchmaker.
* **Swagger URL** is a handy openAPI specification GUI we provide to explore API schema.
* **Auth Token** is a unique secret token used by Game Clients and Game Server for authentication.

{% hint style="danger" %}
**Edgegap staff will never ask for your tokens. Regenerate your token if you suspect a security breach.**
{% endhint %}

To test your new matchmaker, **you will need the Swagger URL, API URL and Auth Token**.

✅ You may now proceed to the next step.

{% hint style="info" %}
To update your matchmaker rules in development, edit your configuration and restart it.
{% endhint %}

{% hint style="success" %}
See [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#rolling-updates-and-ab-tests) for live games and zero-downtime updates.
{% endhint %}

### 4. Test Tickets API

{% hint style="info" %}
**Please wait for up to 5 minutes** after starting your matchmaker to allow DNS propagation to complete.
{% endhint %}

☑️ First, **open your Swagger URL** to inspect your openAPI schema in the swagger GUI:

<figure><img src="/files/ZPTzrdacqVpxfwIQLeHY" alt=""><figcaption></figcaption></figure>

☑️ Click on **Authorize** 🔒, paste your **Auth Token**, and confirm by clicking on **Authorize**.

<figure><img src="/files/JDrcplnvxoGIVK9b0qZQ" alt=""><figcaption></figcaption></figure>

☑️ Scroll down to **Ticket API** **- POST /tickets**, expand and click **Try it out**.

<figure><img src="/files/UuG5GbvqqtBF3fMhaKQ8" alt=""><figcaption></figcaption></figure>

☑️ Preview your request:

* notice `player_ip` set to `null` - this will cause Matchmaker to use the IP address automatically added to your request (see [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#server-to-server-api) for alternatives),
* `profile` refers to your [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#matchmaking-profiles),
* `attributes` include values for your matchmaker rules, in this case for the `latencies` rule,
  * rule `player_count` is the only rule which doesn’t require any attributes in player tickets.

☑️ Click **Execute** and review the response to your player ticket request:

* `id` is your unique matchmaking ticket ID, save this to check on your ticket later,
* `profile` confirming the choice of [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#matchmaking-profiles),
* `group_id` is a unique [group ID issued to every ticket, a solo player is represented as a group of 1](/learn/matchmaking/matchmaker-in-depth#group-up),
* `team_id` is a unique team ID issued to every player once `TEAM_FOUND` status is reached,
  * [each team can contain multiple groups, not exceeding the configured team size](/learn/matchmaking/matchmaker-in-depth#matchmaking-rules),
* `player_ip` is the resolved public IP address of the player, regardless of the identification method,
* `assignment` is set to `null` to indicate the ticket has not been matched or assigned to a server,
* `created_at` provides information about when the player ticket was created for game UI usage,
* `status` indicates the current status of the ticket, all tickets start in `SEARCHING` .

<figure><img src="/files/OJ2ZwYW3WVumkWCYQO4y" alt=""><figcaption></figcaption></figure>

☑️ Create a second ticket by hitting **Execute** again, so our two players match and a server is started.

☑️ Collapse POST /tickets and open **GET /tickets/{ticketId}**, then click **Try it out**.

☑️ Input ticket ID from the response in previous step and click **Execute**.

<figure><img src="/files/lVS9TwluGFDrnznNi6cy" alt=""><figcaption></figcaption></figure>

☑️ Review the updated assignment for your player ticket:

* status changed to `MATCH_FOUND` first, while keeping `assignment` set to `null` to indicate players have matched and a server is being assigned,

<figure><img src="/files/stcbotbtDaJxCMTq60DB" alt=""><figcaption></figcaption></figure>

☑️ Click **Execute** again to check on your ticket, and review the updated assignment for your ticket:

* status changed to `HOST_ASSIGNED` with `assignment` containing details of the assigned server.

<figure><img src="/files/4yv6hCLSVAjqTUZrvNQc" alt=""><figcaption></figcaption></figure>

<details>

<summary>Troubleshooting &#x26; FAQ</summary>

My ticket is stuck in `SEARCHING` .

* Please verify that you’ve created enough tickets with overlapping criteria adhering to your configuration.

***

My ticket is stuck switching between `MATCH_FOUND` and `TEAM_FOUND` repeatedly.

* Free Tier accounts are limited to 1 deployment at a time. Please consider upgrading or stop your current deployment to start a new one.

***

My ticket goes straight to `CANCELLED`.

* Your ticket reached it’s expiration. Recreate a new ticket or increase the expiration period in your configuration for testing purposes.

***

I receive HTTP 404 Not Found when checking on my ticket.

* Your ticket was removed either by a DELETE request, or by reaching it’s removal period (starts after ticket is expired, defined in your configuration). Recreate a new ticket or increase the expiration/removal periods in your configuration for testing purposes.

</details>

☑️ [Inspect your new deployment in our dashboard](https://app.edgegap.com/deployment-management/deployments/list):

* notice each deployment is tagged with all ticket IDs and profile for added traceability.

<figure><img src="/files/B2EG6bYg0Cj8LQ9QPwC0" alt=""><figcaption></figcaption></figure>

A few seconds after finding a match, memberships proceed to `status:HOST_ASSIGNED`  indicating that your [deployment is now ready and your game server is initializing](https://docs.edgegap.com/learn/pages/0UXQAhtFuL0FkdoUmYTh#id-3.-deployment-ready).

Each player reads their `ticket_id`  and  `assignment`  and attempt connection using the [**FQDN**](#user-content-fn-5)[^5] **(deployment URL)** and the **External Port**. Your game server may be still initializing at this time, so **players must retry connection several times**, until exceeding your usual server initialization time:

{% tabs %}
{% tab title="Unreal Engine" %}
To **connect from PIE (Editor)** during development and testing, press the tilde key `~`  and type `open {URL}:{port}`  and wait for your editor to load the map.

To **connect from a game client build** (and in live production environment) try

* Unreal Engine [⚡ Integration Kit](https://docs.edgegap.com/learn/unreal-engine-games/developer-tools#integration-kit):
  * [install from Fab Marketplace](https://www.fab.com/listings/ff17ad88-12a1-49cf-9a41-31695ed11e16) (free for Personal use),
  * [import simple example blueprint](https://blueprintue.com/blueprint/m33u1okj/) and customize to your needs.

{% hint style="success" %}
In case of failed connections or dark screen consult our [troubleshooting guide](/unreal-engine#troubleshooting-and-faq).
{% endhint %}
{% endtab %}

{% tab title="Unity" %}
To **connect your Unity Editor** or **game client** to your cloud deployment, input:

* **Deployment** **URL** pointing to the server's IP, usually in `NetworkManager` component.
* **External port** mapping to the [server's internal listen port](/learn/orchestration/application-and-versions#port-mapping), usually in a Transport component.

{% hint style="success" %}
In case of connection timeout or other issues consult our [troubleshooting guide](/unity#troubleshooting-and-faq-4).
{% endhint %}
{% endtab %}
{% endtabs %}

{% hint style="info" %}
We do not ask players to confirm the match, as we aim to provide shortest possible time to gameplay, high match fill rate, and minimize queue dodging and match cancellations.
{% endhint %}

Players should **save their assignment ID persistently between game restarts**, so that in case of game client crash they can retrieve the connection details and attempt reconnecting.

{% hint style="success" %}
See [Matchmaking](/learn/matchmaking) and our SDKs with automatic reconnect function and more.
{% endhint %}

☑️ Try connecting from your game client to the assigned server.

{% hint style="warning" %}
If you’re experiencing high latency, your netcode integration may be configured to simulate network latency. **Disable VPN when testing** for more realistic conditions and receive a [low-latency deployment](/learn/orchestration/deployments#server-placement).
{% endhint %}

☑️ Once you verify you’re able to connect to your Deployment without issues and are done testing, **Stop your Deployment** to free up capacity in your account for the next build.

✅ You may now proceed to the next step.

{% hint style="info" %}
Find the openAPI specification for testing at `{matchmaker-url}/swagger/v1/swagger.json`.
{% endhint %}

### 5. Game Integration

Matchmaker integrates with:

* **Game Client**, to [manage groups, memberships, assignments, and tickets](/learn/matchmaking/matchmaker-in-depth#group-up),
* **Dedicated Server**, to [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#backfill-match) after a player leaves.

☑️ In **Game Client**, we recommend providing ticket status updates to players using in-game UI for best player experience. See:

* Unity [Developer Tools](/unity/developer-tools#software-development-kit):
  * [install package using Unity Package Manager for free](https://github.com/edgegap/edgegap-unity-sdk),
  * [explore our getting started guide and complete examples](/unity/matchmaking).
* Unreal Engine [Developer Tools](/unreal-engine/developer-tools#integration-kit):
  * [read the documentation](https://egik.betide.studio/) by Betide Studios,
  * [install from Fab Marketplace](https://www.fab.com/listings/ff17ad88-12a1-49cf-9a41-31695ed11e16) (free for Personal use),
  * [import simple example blueprint](https://blueprintue.com/blueprint/m33u1okj/) (matchmaking) and customize to your needs.

☑️ In **Game Client**, ensure you’re handling retryable `429 Too Many Requests`  errors with an exponential backoff and retry, giving matchmaker time to recover during sudden traffic bursts.

☑️ In **Game Client**, ensure you’re handling non-retryable errors:

* `404 Not Found` - ticket has been deleted,
* `500 Internal Server Error` - temporary service outage.

☑️ In **Game Server**, read player preferences and initial server context:

1. [Injected Variables (Matchmaker)](/learn/matchmaking/matchmaker-in-depth#injected-environment-variables) **to retrieve initial players’ matchmaking data.**
2. [Injected Variables (App Versions)](/learn/orchestration/application-and-versions#other-parameters-optional) for version parameters, settings, and secrets.
3. [Injected Variables (Deployment)](/learn/orchestration/deployments#injected-environment-variables) for deployment information, IP, location, etc...

{% hint style="info" %}
Use [GetEnvironmentVariable in C#](https://learn.microsoft.com/en-us/dotnet/api/system.environment.getenvironmentvariable?view=net-8.0) or [GetEnvironmentVariable in C++](https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Core/GenericPlatform/FGenericPlatformMisc/GetEnvironmentVariable) to get variable values.
{% endhint %}

☑️ Once players connect, **Game Server and Game Clients** start a loading scene - 3D scene, a lobby-like social UI, or a loading screen with a progress bar, to indicate initialization is progressing.

☑️ Ensure your [deployment will be stopped](https://docs.edgegap.com/docs/deployment#stop-a-deployment) properly once the match concluded.

🙌 Congratulations, you’ve completed Matchmaking integration! To learn more, keep reading.

## 🏁 Advanced Example

A full fledged configuration utilizing all matchmaking features including [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#matchmaking-profiles), [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#matchmaking-rules), and [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#rule-expansion) may look like this:

<details>

<summary>🏁 Advanced Example (Complete Example Configuration)</summary>

```json
{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "allowed_cors_origins": [
    "https://*.my-game-server.com"
  ],
  "profiles": {
    "advanced-example": {
      "ticket_expiration_period": "5m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m"
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 1,
              "min_team_size": 4,
              "max_team_size": 4
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 125,
              "max_latency": 125
            }
          },
          "elo_rating": {
            "type": "number_difference",
            "attributes": {
              "max_difference": 50
            }
          },
          "selected_game_mode": {
            "type": "string_equality"
          },
          "selected_map": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "backfill_group_size": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {
          "30": {
            "elo_rating": {
              "max_difference": 150
            },
            "beacons": {
              "difference": 125,
              "max_latency": 250
            }
          },
          "60": {
            "elo_rating": {
              "max_difference": 200
            }
          },
          "180": {
            "match_size": {
              "team_count": 1,
              "min_team_size": 1,
              "max_team_size": 4
            },
            "beacons": {
              "difference": 99999,
              "max_latency": 99999
            }
          }
        }
      }
    }
  }
}
```

</details>

## 🥛 Backfill Showcase

Building on [#simple-example](#simple-example "mention"), this configuration showcases [Backfill](/learn/matchmaking/matchmaker-in-depth#backfill-match) with [Groups](/learn/matchmaking/matchmaker-in-depth#group-up).

<details>

<summary><span data-gb-custom-inline data-tag="emoji" data-code="1f95b">🥛</span> Backfill Configuration Example</summary>

```json
{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "profiles": {
    "backfill-example": {
      "ticket_expiration_period": "30s",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m",
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 1,
              "min_team_size": 4,
              "max_team_size": 4
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 100,
              "max_latency": 200
            }
          },
          "backfill_group_size": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {}
      }
    }
  }
}
```

</details>

Optionally, some games may have special matchmaking needs, such as:

* allow new players to join games in progress (friends or "randoms"),
* replace players who abandon (leavers) after server starts to avoid restarting match,
* allow spectators to join and observe tournament or friends’ matches (e-sports),
* centralize players in larger servers to provide more social interactions (MMOs).

Backfill is a **server-owned ticket representing players currently connected to the server.** This ensures newly added players will respect your matchmaking rules when matched with current players.

{% hint style="warning" %}
[In-Depth Look](/learn/matchmaking/matchmaker-in-depth#backfill-match) to replace Seat/Match sessions. Matchmaker only supports Default session.
{% endhint %}

<figure><img src="/files/Mjs71y4xaPuLjPP2eBMD" alt=""><figcaption><p>Backfill Scenarios Visualized</p></figcaption></figure>

{% hint style="success" %}
**Backfills ignore** `player_count`  **rule, and always match exactly one group**. `backfill_group_size`  controls team capacity with round-robin strategy, filling teams evenly in a controlled manner.
{% endhint %}

**The steps to complete a successful backfill are:**

1. Server creates one Backfill per team missing players, using values from:
   * Real `assignment`  data retrieved from [Deployments](/learn/orchestration/deployments#injected-environment-variables) (deployment).
   * Currently connected players' `tickets`:
     * from [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#injected-variables) (matchmaker), previous backfills' `assigned_ticket` response, or mock data manipulated to match specific players,
     * replace `backfill_group_size`  values with possible group sizes [up to available capacity](#user-content-fn-6)[^6],
2. Game clients create new tickets (memberships) and include `backfill_group_size`  values:
   * `"1"`  if the player is matchmaking alone.
   * [`"2"`  if the player is a part of a matchmaking group with 2x members total](#user-content-fn-7)[^7].
   * `"new"`  if players enabled starting new games in addition to joining in-progress games.
3. Game clients proceed to [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#find-match) and pair players with the matching backfill.
4. If the backfilled group didn't completely fill the team, the server may repeat this process with the newly backfilled players' tickets, to add more players and reach desired team sizes.

{% hint style="info" %}
To create a Backfill-only profile, set `min_team_size`  to 999,999 and disable ticket + ticket matches.
{% endhint %}

<details>

<summary>🥛 Backfill Example (Backfill Showcase)</summary>

```json
{
  "profile": "backfill-example",
  "attributes": {
    "assignment": {
      "request_id": "cd28e6c66554",
      "fqdn": "cd28e6c66554.pr.edgegap.net",
      "public_ip": "192.168.2.14",
      "ports": {
        "game": {
          "internal": 7777,
          "external": 56890,
          "link": "cd28e6c66554.pr.edgegap.net:56890",
          "protocol": "UDP"
        },
        "web": {
          "internal": 22,
          "external": 57440,
          "link": "cd28e6c66554.pr.edgegap.net:57440",
          "protocol": "TCP"
        },
        "server": {
          "internal": 80,
          "external": 50110,
          "link": "cd28e6c66554.pr.edgegap.net:50110",
          "protocol": "TCP"
        }
      },
      "location": {
        "city": "Montreal",
        "country": "Canada",
        "continent": "North America",
        "administrative_division": "Quebec",
        "timezone": "America/Toronto"
      }
    }
  },
  "tickets": {
    "c3d057h5h6f7j889fk43": {
      "player_ip": "174.25.48.238",
      "attributes": {
        "beacons": {
          "New York": 12.2,
          "Los Angeles": 45.3,
          "Paris": 78.3
        },
        "backfill_group_size": [
          "2",
          "1"
        ]
      },
      "group_id": "192bb97e-7fd6-4d86-8ce4-61c53c9fef16",
      "id": "c3d057h5h6f7j889fk43",
      "created_at": "2024-08-20T13:38:05.251393+00:00"
    },
    "cqg0bg9583s738h9dkf6": {
      "player_ip": "217.34.85.142",
      "attributes": {
        "beacons": {
          "New York": 21.0,
          "Los Angeles": 30.2,
          "Paris": 101.1
        },
        "backfill_group_size": [
          "2",
          "1"
        ]
      },
      "group_id": "aea7df3c-d391-4ea3-a3ec-dded422fe7c8",
      "id": "cqg0bg9583s738h9dkf6",
      "created_at": "2024-08-20T13:38:05.251393+00:00"
    }
  },
  "assigned_ticket": null
}
```

</details>

<details>

<summary>🥛 Backfill Assignment Example (Backfill Showcase)</summary>

```json
{
  "profile": "backfill-example",
  "attributes": {
    "assignment": {
      "request_id": "cd28e6c66554",
      "fqdn": "cd28e6c66554.pr.edgegap.net",
      "public_ip": "192.168.2.14",
      "ports": {
        "game": {
          "internal": 7777,
          "external": 56890,
          "link": "cd28e6c66554.pr.edgegap.net:56890",
          "protocol": "UDP"
        },
        "web": {
          "internal": 22,
          "external": 57440,
          "link": "cd28e6c66554.pr.edgegap.net:57440",
          "protocol": "TCP"
        },
        "server": {
          "internal": 80,
          "external": 50110,
          "link": "cd28e6c66554.pr.edgegap.net:50110",
          "protocol": "TCP"
        }
      },
      "location": {
        "city": "Montreal",
        "country": "Canada",
        "continent": "North America",
        "administrative_division": "Quebec",
        "timezone": "America/Toronto"
      }
    }
  },
  "tickets": {
    "c3d057h5h6f7j889fk43": {
      "player_ip": "174.25.48.238",
      "attributes": {
        "beacons": {
          "New York": 12.2,
          "Los Angeles": 45.3,
          "Paris": 78.3
        },
        "backfill_group_size": [
          "2",
          "1"
        ]
      },
      "group_id": "192bb97e-7fd6-4d86-8ce4-61c53c9fef16",
      "id": "c3d057h5h6f7j889fk43",
      "created_at": "2024-08-20T13:38:05.251393+00:00"
    },
    "cqg0bg9583s738h9dkf6": {
      "player_ip": "217.34.85.142",
      "attributes": {
        "beacons": {
          "New York": 21.0,
          "Los Angeles": 30.2,
          "Paris": 101.1
        },
        "backfill_group_size": [
          "2",
          "1"
        ]
      },
      "group_id": "aea7df3c-d391-4ea3-a3ec-dded422fe7c8",
      "id": "cqg0bg9583s738h9dkf6",
      "created_at": "2024-08-20T13:38:05.251393+00:00"
    }
  },
  "assigned_ticket": {
    "profile": "backfill-example",
    "player_ip": "244.13.201.244",
    "attributes": {
      "beacons": {
        "New York": 30.2,
        "Los Angeles": 10.5,
        "Paris": 123.9
      },
      "backfill_group_size": [
        "new",
        "1"
      ]
    },
    "id": "cqg0bg550h7uujd77khg",
    "group_id": "e0cf41c0-f88f-456e-a032-03b1d6821a9a",
    "created_at": "2024-08-20T13:38:08.251393+00:00",
    "status": "HOST_ASSIGNED"
  }
}
```

</details>

{% hint style="info" %}
See [Mirror Seat Management](https://docs.edgegap.com/docs/sample-projects/mirror-on-edgegap#bonus-seat-sessions-management) and [FishNet Seat Management](https://docs.edgegap.com/docs/sample-projects/fishnet-on-edgegap#bonus-seat-sessions-management) for **player connection monitoring**.
{% endhint %}

## ⚔️ Competitive Games

Competitive games focus on players competing against each other to achieve victory, whether as individuals (free for all) or teams. Ensure fair and balanced matches by pairing players or teams of similar skill levels, and maintain game pace by quickly finding fair competition.

<details>

<summary>⚔️ Competitive Game Example</summary>

```json
{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "profiles": {
    "casual-example": {
      "ticket_expiration_period": "5m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m",
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 2,
              "min_team_size": 5,
              "max_team_size": 5
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 125,
              "max_latency": 150
            }
          },
          "selected_maps": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "backfill_group_size": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {
          "30": {
            "beacons": {
              "difference": 125,
              "max_latency": 250
            }
          },
          "180": {
            "beacons": {
              "difference": 99999,
              "max_latency": 99999
            }
          }
        }
      }
    },
    "competitive-example": {
      "ticket_expiration_period": "5m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m"
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 2,
              "min_team_size": 5,
              "max_team_size": 5
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 125,
              "max_latency": 150
            }
          },
          "versus_ranks": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {
          "120": {
            "beacons": {
              "difference": 125,
              "max_latency": 250
            }
          }
        }
      }
    },
    "challenger-example": {
      "ticket_expiration_period": "5m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m"
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 2,
              "min_team_size": 5,
              "max_team_size": 5
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 125,
              "max_latency": 150
            }
          },
          "elo_rating": {
            "type": "number_difference",
            "attributes": {
              "max_difference": 50
            }
          }
        },
        "expansions": {
          "120": {
            "beacons": {
              "difference": 125,
              "max_latency": 250
            }
          }
        }
      }
    }
  }
}
```

</details>

You may **define multiple teams with 1 or more players each**, for example:

<table><thead><tr><th width="300">Game Mode</th><th>Team Count</th><th>Team Size</th><th>Total Players</th></tr></thead><tbody><tr><td>5v5 FPS</td><td>2</td><td>5</td><td><strong>10</strong></td></tr><tr><td>5v5 MOBA</td><td>2</td><td>5</td><td><strong>10</strong></td></tr><tr><td>20x3 Battle Royale</td><td>20</td><td>3</td><td><strong>60</strong></td></tr><tr><td>10p Free For All</td><td>1</td><td>10</td><td><strong>10</strong></td></tr></tbody></table>

Define multiple [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#matchmaking-profiles) for game mode specific rules and settings, and [expand as needed](/learn/matchmaking/matchmaker-in-depth#rule-expansion).

* **For all matches:**
  * restrict [matchmaking latency](/learn/matchmaking/matchmaker-in-depth#ping-optimization) to prevent matching players far away,
  * [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#group-up) for pre-made parties and prevent exceeding team sizes,
  * slowly relax latency restrictions over time to find more players,
  * allocate more CPU or memory with different [Apps and Versions](/learn/orchestration/application-and-versions#app-versions) for specific profiles,
* **For casual matches:**
  * omit rank restrictions to maximize match speed and match fill rate,
  * let players provide their map preferences to find a map suitable for everyone,
  * specify backfill group size to replace leavers without exceeding team sizes,
  * remove latency limitations to guarantee a match after 3 minutes (180s) of queue time.
* **For competitive matches:**
  * restrict rank to only allow opponents with similar skill level,
  * use rank-up or rank-down ranks to match players at the league's rank extremes.
* **For the top 1% of high skill matches (challengers):**
  * use numerical skill ratings (ELO) to gain fine control over skill distribution in matches,
  * wait longer before relaxing latency requirements due to lower amount of players.

{% hint style="success" %}
Using multiple profiles to **separate casual game modes, competitive game modes, and top-tier challenger** players allows you to customize rules and expansions for each type of player separately.
{% endhint %}

## 🤝 Cooperative Games

Cooperative games require players to work together as a team towards a common goal, or AI opponent. Align players with similar preferences and gameplay habits. Replace players who leave, and improve [Ping Beacons](/learn/orchestration/ping-beacons#connection-quality) to provide a responsive player experience.

<details>

<summary>🤝 Cooperative Game Example</summary>

```json
{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "profiles": {
    "cooperative-example": {
      "ticket_expiration_period": "3m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m",
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 1,
              "min_team_size": 4,
              "max_team_size": 4
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 125,
              "max_latency": 150
            }
          },
          "selected_difficulty": {
            "type": "string_equality"
          },
          "selected_map": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "player_level": {
            "type": "number_difference",
            "attributes": {
              "max_difference": 10
            }
          },
          "backfill_group_size": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "moderation_flags": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {
          "30": {
            "beacons": {
              "difference": 125,
              "max_latency": 250
            },
            "player_level": {
              "max_difference": 20
            }
          },
          "60": {
            "match_size": {
              "team_count": 1,
              "min_team_size": 2,
              "max_team_size": 4
            }
          },
          "150": {
            "match_size": {
              "team_count": 1,
              "min_team_size": 1,
              "max_team_size": 4
            }
          }
        }
      }
    }
  }
}
```

</details>

With team count 1 and maximum team size of 4, **require up to 4 players per match**.

Define multiple [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#matchmaking-profiles) for game modes specific rules and settings:

* start with minimum of 4 players to keep players in queue and maximize match fill rate,
* restrict [matchmaking latency](/learn/matchmaking/matchmaker-in-depth#ping-optimization) to prevent matching players far away,
* let players choose a particular game difficulty to suit everybody’s skill level,
* let players provide their map preferences to find a map suitable for everyone,
* restrict player level difference to require similar degree of game progression,
* specify backfill group size to replace leavers without exceeding server capacity,
* use moderation flags to separate low-karma players and cheaters from general population,
* [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#group-up) for pre-made parties and to fill teams without exceeding server capacity,
* allocate more CPU or memory using different [Apps and Versions](/learn/orchestration/application-and-versions#app-versions) for other profiles.

Start with the ideal conditions, and [expand restrictions](/learn/matchmaking/matchmaker-in-depth#rule-expansion) to ensure quick matches:

* relax latency restrictions over time to find more players,
* increase allowed player level difference to find more players,
* decrease minimum team size to require less players and start the game sooner,
  * server may fill empty slots with AI teammates,
  * or [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#backfill-match) to add players later,
* set minimum team size to 1 to launch the game solo after 150s of queue time

## 🎈 Social Games

Social games focus on building connections and relationships between players through collaboration, communication, and shared experience. Support high number of players, maximize match fill rate, and align player preferences and gameplay habits. Replace players who leave, and ensure high [Ping Beacons](/learn/orchestration/ping-beacons#connection-quality) to provide a responsive player experience.

<details>

<summary>🎈 Social Game Example</summary>

```json
{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "profiles": {
    "social-example": {
      "ticket_expiration_period": "3m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m",
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "attributes": {
              "team_count": 1,
              "min_team_size": 50,
              "max_team_size": 50
            },
            "type": "player_count"
          },
          "beacons": {
            "attributes": {
              "difference": 125,
              "max_latency": 150
            },
            "type": "latencies"
          },
          "selected_mode": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "backfill_group_size": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "moderation_flags": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {
          "15": {
            "beacons": {
              "difference": 125,
              "max_latency": 250
            },
            "match_size": {
              "team_count": 1,
              "min_team_size": 20,
              "max_team_size": 50
            }
          },
          "30": {
            "match_size": {
              "team_count": 1,
              "min_team_size": 10,
              "max_team_size": 50
            }
          },
          "150": {
            "match_size": {
              "team_count": 1,
              "min_team_size": 1,
              "max_team_size": 50
            }
          }
        }
      }
    }
  }
}
```

</details>

With team count 1 (free for all) and maximum team size of 50, **require up to 50 players per match**.

Define [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#matchmaking-profiles) for game modes specific rules and settings:

* restrict [matchmaking latency](/learn/matchmaking/matchmaker-in-depth#ping-optimization) to prevent matching players far away,
* let players provide their game mode preferences and find a mode suitable for everyone,
* specify backfill group size to replace leavers without exceeding server capacity,
* use moderation flags to separate low-karma players and cheaters from general population,
* [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#group-up) for pre-made lobbies or to fill teams without exceeding server capacity,
* allocate more CPU or memory using different [Apps and Versions](/learn/orchestration/application-and-versions#app-versions) for other profiles.

Start with the ideal conditions, and [expand restrictions](/learn/matchmaking/matchmaker-in-depth#rule-expansion) to ensure quick matches:

* relax latency restrictions over time to find more players,
* slowly decrease minimum team size to require less players and start the game sooner,
  * server may fill empty slots with AI players,
  * or [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#backfill-match) to add players later,
* set minimum team size to 1 to launch the game solo after 150s of queue time.

[^1]: replace with your own application name

[^2]: replace with your own application version

[^3]: maximum difference exceeded

[^4]: maximum latency exceeded

[^5]: Fully Qualified Domain Name

[^6]: e.g. for 3 free slots = \["3", "2", "1"]

[^7]: replace "2" with the number of actual group members


# In-Depth Look

Learn more about Edgegap’s no-code matchmaker concepts in-depth and customize to your needs.

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

## ✔️ Introduction

Matchmaking in match-based games generally aims to:

* **find other players** based on criteria like region, latency, skill, or game parameters;
* **search for servers** to join based on available capacity \[or ping, region, skill, map, mode];
* **start new server** if existing servers are full or don't satisfy player criteria.

Player experience comes first, defining our core objectives:

* high match fill rate and social feature integration (play with friends in groups),
* fast matches with controlled match quality (low latency, shared preferences),
* reliable and predictable matchmaking process with global availability.

{% hint style="success" %}
Alternatively, let players **pick a persistent (always online) server** from a list with [Server Browser](/learn/server-browser).
{% endhint %}

**Get started within 5 minutes and test the all features for free, no credit card required.**

Upgrade when you're ready for a more powerful, private (dedicated) cluster. Native integration with Edgegap [Deployments](/learn/orchestration/deployments) delivers best-in-class ping no matter where your players are located.

{% hint style="info" %}
Free Tier allows 3 hours of runtime after each restart. Your matchmaker will run on shared infrastructure with limited resources, suitable for testing. **After your public release, matchmaker needs to run 24/7.**
{% endhint %}

There are three essential concepts to each Matchmaker:

* [#hosting-cluster](#hosting-cluster "mention") - underlying server infrastructure, fully managed and operated by Edgegap.
* [#configuration](#configuration "mention") - set of rules and settings which define how the matchmaker operates.
* 🌐 Service Instance **-** live matchmaking service running 24/7 on the Cluster, using Configuration to match players together and produce deployment (server) assignments.

{% hint style="success" %}
[Update your matchmaker version frequently](#changelog) to **take advantage of new features and bug fixes.**
{% endhint %}

## ▶️ Start Matchmaking

**Get started quickly - add our SDK starter sample to your game**:

* Unreal Engine [Developer Tools](/unreal-engine/developer-tools#integration-kit):
  * [read the documentation](https://egik.betide.studio/) by Betide Studios,
  * [install from Fab Marketplace](https://www.fab.com/listings/ff17ad88-12a1-49cf-9a41-31695ed11e16) (free for Personal use),
  * [import simple example blueprint](https://blueprintue.com/blueprint/m33u1okj/) (matchmaking) and customize to your needs.
* Unity [Developer Tools](/unity/developer-tools#software-development-kit):
  * [install package using Unity Package Manager for free](https://github.com/edgegap/edgegap-unity-sdk),
  * [explore our getting started guide and complete examples](/unity/matchmaking).

Learn about matchmaking process to customize, troubleshoot, and optimize your game integration:

<figure><img src="/files/QiJVfqrO6FazbQfcqDge" alt=""><figcaption><p>Matchmaking Sequence</p></figcaption></figure>

1. [Authenticate Player](#authenticate) - prevents pirated copies from playing online,
2. [Create Lobby](#create-group) - join up with your friends and share player/match preferences,
3. [**Group Up**](#group-up) **- register your Lobby as a Matchmaking Group,**
4. [**Find Match**](#find-match) **- get ready and start looking for a match (new or existing),**
   1. Assign Server & Inject Tickets - server is automatically assigned after a few seconds,
5. [**Connect and Authenticate**](#connect-to-server) **- attempt secure connection to game server,**
   1. Confirm Identity - server verifies identity of game client using third party tokens,
   2. Accept Player or Kick Player - server decides if the player is allowed to join.

### Authenticate

All requests must send an `Authorization`  HTTP header with your secret **Auth Token:**

<pre><code>Authorization: <a data-footnote-ref href="#user-content-fn-1">xxxxxxxx-e458-4592-b607-c2c28afd8b62</a>
</code></pre>

{% hint style="warning" %}
**Keep your tokens secret and safe! Edgegap staff will never ask you for your tokens.**
{% endhint %}

{% hint style="success" %}
**This token may be included in your game client safely, as it doesn't grant access to Edgegap API.**
{% endhint %}

Individual players can be identified using their ticket ID, available on clients and server. Optionally, add custom authentication or limits with a custom proxy using [#server-to-server-api](#server-to-server-api "mention") API.

### Group Up

Creating a Group (party) ensures players join the same team and server with their friends.

{% hint style="success" %}
Create a group marked ready to [#find-match](#find-match "mention") quickly as a **solo player without group members**.
{% endhint %}

<figure><img src="/files/1eEiIqOj6dP9fJcehxVm" alt=""><figcaption><p>Group Lifecycle Activity Diagram</p></figcaption></figure>

#### Lobby and Group

Use a Lobby service if your game design requires setting player-controlled matchmaking preferences (e.g. character choice, difficulty, map, etc.). As players join and leave the Lobby, they also update the matchmaking Group to prepare for finding a match later.

{% hint style="success" %}
**Don't have time for a Lobby service?** Guide players to share group IDs through Discord or DMs.
{% endhint %}

<table><thead><tr><th width="390">Game Design - Feature / Requirement</th><th>Pre-Match Lobby</th><th>Matchmaker Group</th></tr></thead><tbody><tr><td><a data-footnote-ref href="#user-content-fn-2">invite friends to play with me</a></td><td>✅</td><td>✅</td></tr><tr><td>modify my player/match preferences</td><td>✅</td><td>❌</td></tr><tr><td>view other lobby member preferences</td><td>✅</td><td>❌</td></tr><tr><td>store and manage custom key-value data</td><td>✅</td><td>❌</td></tr><tr><td>notify group members that I'm ready to play</td><td>❌</td><td>✅</td></tr><tr><td>show matchmaking progress and find match</td><td>❌</td><td>✅</td></tr><tr><td>get team assignment for a player/group</td><td>❌</td><td>✅</td></tr><tr><td>retrieve game server connection details</td><td>❌</td><td>✅</td></tr></tbody></table>

Our cross-platform matchmaker supports all commercial and custom Lobby services:

<table><thead><tr><th>Lobby Service (Third Party)</th><th width="120" data-type="checkbox">Unreal Engine</th><th width="75" data-type="checkbox">Unity</th><th width="50" data-type="checkbox">PC</th><th width="90" data-type="checkbox">Consoles</th><th width="65" data-type="checkbox">VR/XR</th><th width="100" data-type="checkbox">Mobile</th></tr></thead><tbody><tr><td><a href="https://dev.epicgames.com/docs/game-services/lobbies-and-sessions/lobbies/lobbies-intro">Epic Online Services Lobby</a><br>(Epic Games)</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><a href="https://partner.steamgames.com/doc/features/multiplayer/matchmaking#friends">Steamworks Lobby</a><br>(Valve Corporation)</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><a href="https://heroiclabs.com/docs/nakama/concepts/groups/">Nakama Group</a><br>(Heroic Labs)</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><a href="https://learn.microsoft.com/en-us/gaming/playfab/community/associations/groups/quickstart">Playfab Lobby</a><br>(Microsoft)</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><a href="https://docs.braincloudservers.com/learn/key-concepts/multiplayer/lobbies/#lobby-experience">brainCloud Lobby</a><br>(bitHeads)</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr><tr><td><a href="https://developer.apple.com/documentation/gamekit/connecting-players-with-their-friends-in-your-game">Gamekit Friends</a><br>(Apple)</td><td>true</td><td>true</td><td>false</td><td>false</td><td>false</td><td>true</td></tr><tr><td>Custom Lobby<br>(your company)</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td><td>true</td></tr></tbody></table>

Lobby owner (the player sending invites) must also create the matchmaking group.

**Store your group's ID in your shared lobby data**, so other lobby members can easily find and join a matchmaking group associated with the third party lobby. Players invited to the group use the group ID to **create their memberships (join)**, and to **securely store their** [**matchmaking attributes**](#matchmaking-rules)**.**

{% hint style="warning" %}
**Once a group starts matchmaking, it can't be joined.** [#abandon-queue](#abandon-queue "mention") and create a new one.
{% endhint %}

#### Ping Optimization

If [#configuration](#configuration "mention") includes [`latencies` rule](#rule-example-elo_rating) all group members send their [Ping Beacons](/learn/orchestration/ping-beacons) measurements to **prevent matching players in distant regions** or much higher/lower ping (latency).

{% code title="Example Game Client Ping Measurements in milliseconds" %}

```json
{
  "Chicago": 224.4,
  "Frankfurt": 23.2,
  "Tokyo": 167.4
}
```

{% endcode %}

#### **Abandon Queue**

Group owner may delete the group, automatically deleting all group memberships. Deleting the group after matchmaking started will cancel all memberships, and delete them shortly after.

Group members (besides owner) may delete their memberships (leave group) at any time before [#find-match](#find-match "mention"). Deleting a membership afterwards will cancel matchmaking for the whole group.

{% hint style="info" %}
Once matchmaking is cancelled, members are [removed from matchmaking automatically](#matchmaking-profiles) and notified through membership `status:CANCELLED`  in their next status polling response.
{% endhint %}

Once cancelled, if the group wishes to restart matchmaking, the group owner must re-create the group, share the new group ID to members, and have them re-create their memberships.

**Once a match is found, group can't be deleted** (`409 Conflict`), and will be [removed automatically](#connect-to-server). Your server should allow some time (e.g. 60s) for players to connect before assuming a player abandoned.

If your server flags a player as abandoned, you may:

* replace the leaver with an AI character to immediately start the match,
* or create a [backfill](#backfill-match) to find a new player to replace the leaver,
* or proceed without replacing the leaver, if your game design permits variable player count.

### Find Match

To start looking for a match, all members and the owner must mark themselves ready.

{% hint style="success" %}
To let the group owner **start matchmaking immediately, mark memberships ready at creation**. Once the owner marks themselves ready the matchmaking will start, since everyone is ready.
{% endhint %}

{% hint style="info" %}
For best experience, **provide status updates to players using in-game UI**.
{% endhint %}

**All players must poll their membership in regular intervals** (recommended 3-5s) to detect when matchmaking starts, and to communicate matchmaking progress through in-game UI.

Players should **save their membership and group IDs persistently**, allowing them to restart the game and resume without losing matchmaking progress in case of game client crash.

Once we find enough players to put into the same team adhering to your [#matchmaking-rules](#matchmaking-rules "mention"), players will be notified in their membership response with `status:TEAM_FOUND`.

Deleting a membership at this stage will result in all group memberships being cancelled and all other groups' assigned to the same team returning to `status:SEARCHING` .

Teams continue matchmaking with other teams using the overlapping values across their groups (or average in case of `number_difference` ) until enough teams are assembled. Memberships indicate this with response  `status:MATCH_FOUND` , which means your [deployment is being started](https://docs.edgegap.com/learn/matchmaking/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-start-a-deployment).

**Matchmaker aims to maximize match fill rate, and will not proceed to `MATCH_FOUND` until either:**

1. enough teams are matched with the configured maximum team size,
2. or if [#rule-expansion](#rule-expansion "mention") defined AND expansion time reached, AND enough teams are matched with the configured minimum team size,
3. or the configured ticket expiration time elapsed AND enough teams are matched with the configured minimum team size.

If neither scenario succeeds before configured ticket expiration, group and tickets are cancelled.

{% hint style="info" %}
Experiencing long queue times during testing, or with players in less popular regions? Set a lower ticket expiration period (e.g. 30s) and re-create group (or tickets) on client side upon expiration.
{% endhint %}

Ticket expiration automatically resets whenever a group (or a player) is matched to a team.

{% hint style="success" %}
Store `team_id`  and `match_id` in your game backend to display team members information in-game.
{% endhint %}

{% hint style="info" %}
Every player receives a **unique Ticket ID, which can be used to** [#authenticate](#authenticate "mention") **with game servers.**
{% endhint %}

If the player has been matched and assigned to a game server, their ticket is deleted automatically. Players who abandon queue after `status:HOST_ASSIGNED`  can be replaced with [backfill](#backfill-match).

Once players receive `status:HOST_ASSIGNED`  they proceed to [#connect-to-server](#connect-to-server "mention").

### Connect to Server

A few seconds after finding a match, memberships proceed to `status:HOST_ASSIGNED`  indicating that your [deployment is now ready and your game server is initializing](https://docs.edgegap.com/learn/matchmaking/pages/0UXQAhtFuL0FkdoUmYTh#id-3.-deployment-ready).

Each player reads their `ticket_id`  and  `assignment`  and attempt connection using the [**FQDN**](#user-content-fn-3)[^3] **(deployment URL)** and the **External Port**. Your game server may be still initializing at this time, so **players must retry connection several times**, until exceeding your usual server initialization time:

{% tabs %}
{% tab title="Unreal Engine" %}
To **connect from PIE (Editor)** during development and testing, press the tilde key `~`  and type `open {URL}:{port}`  and wait for your editor to load the map.

To **connect from a game client build** (and in live production environment) try

* Unreal Engine [⚡ Integration Kit](https://docs.edgegap.com/learn/unreal-engine-games/developer-tools#integration-kit):
  * [install from Fab Marketplace](https://www.fab.com/listings/ff17ad88-12a1-49cf-9a41-31695ed11e16) (free for Personal use),
  * [import simple example blueprint](https://blueprintue.com/blueprint/m33u1okj/) and customize to your needs.

{% hint style="success" %}
In case of failed connections or dark screen consult our [troubleshooting guide](/unreal-engine#troubleshooting-and-faq).
{% endhint %}
{% endtab %}

{% tab title="Unity" %}
To **connect your Unity Editor** or **game client** to your cloud deployment, input:

* **Deployment** **URL** pointing to the server's IP, usually in `NetworkManager` component.
* **External port** mapping to the [server's internal listen port](/learn/orchestration/application-and-versions#port-mapping), usually in a Transport component.

{% hint style="success" %}
In case of connection timeout or other issues consult our [troubleshooting guide](/unity#troubleshooting-and-faq-4).
{% endhint %}
{% endtab %}
{% endtabs %}

{% hint style="info" %}
We do not ask players to confirm the match, as we aim to provide shortest possible time to gameplay, high match fill rate, and minimize queue dodging and match cancellations.
{% endhint %}

Players should **save their assignment ID persistently between game restarts**, so that in case of game client crash they can retrieve the connection details and attempt reconnecting.

{% hint style="success" %}
See [Matchmaking](/learn/matchmaking) and our SDKs with automatic reconnect function and more.
{% endhint %}

### Backfill Match

Optionally, some games may have special matchmaking needs, such as:

* allow new players to join games in progress (friends or "randoms"),
* replace players who abandon (leavers) after server starts to avoid restarting match,
* allow spectators to join and observe tournament or friends’ matches (e-sports),
* centralize players in larger servers to provide more social interactions (MMOs).

Backfill is a **server-owned ticket representing players currently connected to the server.** This ensures newly added players will respect your matchmaking rules when matched with current players.

{% hint style="warning" %}
[In-Depth Look](/learn/matchmaking/matchmaker-in-depth#backfill-match) to replace Seat/Match sessions. Matchmaker only supports Default session.
{% endhint %}

<figure><img src="/files/Mjs71y4xaPuLjPP2eBMD" alt=""><figcaption><p>Backfill Scenarios Visualized</p></figcaption></figure>

{% hint style="success" %}
**Backfills ignore** `player_count`  **rule, and always match exactly one group**. `backfill_group_size`  controls team capacity with round-robin strategy, filling teams evenly in a controlled manner.
{% endhint %}

**The steps to complete a successful backfill are:**

1. Server creates one Backfill per team missing players, using values from:
   * Real `assignment`  data retrieved from [Deployments](/learn/orchestration/deployments#injected-environment-variables) (deployment).
   * Currently connected players' `tickets`:
     * from [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#injected-variables) (matchmaker), previous backfills' `assigned_ticket` response, or mock data manipulated to match specific players,
     * replace `backfill_group_size`  values with possible group sizes [up to available capacity](#user-content-fn-4)[^4],
2. Game clients create new tickets (memberships) and include `backfill_group_size`  values:
   * `"1"`  if the player is matchmaking alone.
   * [`"2"`  if the player is a part of a matchmaking group with 2x members total](#user-content-fn-5)[^5].
   * `"new"`  if players enabled starting new games in addition to joining in-progress games.
3. Game clients proceed to [In-Depth Look](/learn/matchmaking/matchmaker-in-depth#find-match) and pair players with the matching backfill.
4. If the backfilled group didn't completely fill the team, the server may repeat this process with the newly backfilled players' tickets, to add more players and reach desired team sizes.

{% hint style="info" %}
To create a Backfill-only profile, set `min_team_size`  to 999,999 and disable ticket + ticket matches.
{% endhint %}

<details>

<summary>🥛 Backfill Example (Backfill Showcase)</summary>

```json
{
  "profile": "backfill-example",
  "attributes": {
    "assignment": {
      "request_id": "cd28e6c66554",
      "fqdn": "cd28e6c66554.pr.edgegap.net",
      "public_ip": "192.168.2.14",
      "ports": {
        "game": {
          "internal": 7777,
          "external": 56890,
          "link": "cd28e6c66554.pr.edgegap.net:56890",
          "protocol": "UDP"
        },
        "web": {
          "internal": 22,
          "external": 57440,
          "link": "cd28e6c66554.pr.edgegap.net:57440",
          "protocol": "TCP"
        },
        "server": {
          "internal": 80,
          "external": 50110,
          "link": "cd28e6c66554.pr.edgegap.net:50110",
          "protocol": "TCP"
        }
      },
      "location": {
        "city": "Montreal",
        "country": "Canada",
        "continent": "North America",
        "administrative_division": "Quebec",
        "timezone": "America/Toronto"
      }
    }
  },
  "tickets": {
    "c3d057h5h6f7j889fk43": {
      "player_ip": "174.25.48.238",
      "attributes": {
        "beacons": {
          "New York": 12.2,
          "Los Angeles": 45.3,
          "Paris": 78.3
        },
        "backfill_group_size": [
          "2",
          "1"
        ]
      },
      "group_id": "192bb97e-7fd6-4d86-8ce4-61c53c9fef16",
      "id": "c3d057h5h6f7j889fk43",
      "created_at": "2024-08-20T13:38:05.251393+00:00"
    },
    "cqg0bg9583s738h9dkf6": {
      "player_ip": "217.34.85.142",
      "attributes": {
        "beacons": {
          "New York": 21.0,
          "Los Angeles": 30.2,
          "Paris": 101.1
        },
        "backfill_group_size": [
          "2",
          "1"
        ]
      },
      "group_id": "aea7df3c-d391-4ea3-a3ec-dded422fe7c8",
      "id": "cqg0bg9583s738h9dkf6",
      "created_at": "2024-08-20T13:38:05.251393+00:00"
    }
  },
  "assigned_ticket": null
}
```

</details>

<details>

<summary>🥛 Backfill Assignment Example (Backfill Showcase)</summary>

```json
{
  "profile": "backfill-example",
  "attributes": {
    "assignment": {
      "request_id": "cd28e6c66554",
      "fqdn": "cd28e6c66554.pr.edgegap.net",
      "public_ip": "192.168.2.14",
      "ports": {
        "game": {
          "internal": 7777,
          "external": 56890,
          "link": "cd28e6c66554.pr.edgegap.net:56890",
          "protocol": "UDP"
        },
        "web": {
          "internal": 22,
          "external": 57440,
          "link": "cd28e6c66554.pr.edgegap.net:57440",
          "protocol": "TCP"
        },
        "server": {
          "internal": 80,
          "external": 50110,
          "link": "cd28e6c66554.pr.edgegap.net:50110",
          "protocol": "TCP"
        }
      },
      "location": {
        "city": "Montreal",
        "country": "Canada",
        "continent": "North America",
        "administrative_division": "Quebec",
        "timezone": "America/Toronto"
      }
    }
  },
  "tickets": {
    "c3d057h5h6f7j889fk43": {
      "player_ip": "174.25.48.238",
      "attributes": {
        "beacons": {
          "New York": 12.2,
          "Los Angeles": 45.3,
          "Paris": 78.3
        },
        "backfill_group_size": [
          "2",
          "1"
        ]
      },
      "group_id": "192bb97e-7fd6-4d86-8ce4-61c53c9fef16",
      "id": "c3d057h5h6f7j889fk43",
      "created_at": "2024-08-20T13:38:05.251393+00:00"
    },
    "cqg0bg9583s738h9dkf6": {
      "player_ip": "217.34.85.142",
      "attributes": {
        "beacons": {
          "New York": 21.0,
          "Los Angeles": 30.2,
          "Paris": 101.1
        },
        "backfill_group_size": [
          "2",
          "1"
        ]
      },
      "group_id": "aea7df3c-d391-4ea3-a3ec-dded422fe7c8",
      "id": "cqg0bg9583s738h9dkf6",
      "created_at": "2024-08-20T13:38:05.251393+00:00"
    }
  },
  "assigned_ticket": {
    "profile": "backfill-example",
    "player_ip": "244.13.201.244",
    "attributes": {
      "beacons": {
        "New York": 30.2,
        "Los Angeles": 10.5,
        "Paris": 123.9
      },
      "backfill_group_size": [
        "new",
        "1"
      ]
    },
    "id": "cqg0bg550h7uujd77khg",
    "group_id": "e0cf41c0-f88f-456e-a032-03b1d6821a9a",
    "created_at": "2024-08-20T13:38:08.251393+00:00",
    "status": "HOST_ASSIGNED"
  }
}
```

</details>

{% hint style="info" %}
See [Mirror Seat Management](https://docs.edgegap.com/docs/sample-projects/mirror-on-edgegap#bonus-seat-sessions-management) and [FishNet Seat Management](https://docs.edgegap.com/docs/sample-projects/fishnet-on-edgegap#bonus-seat-sessions-management) for **player connection monitoring**.
{% endhint %}

Once game server initialization concludes, **your server should**:

* **Start abandonment timer for each new player.** We recommend indicating loading progress to connected players with a loading scene/level - either a full fledged 3D scene, a lobby-like social UI, or a loading screen with a progress bar.
* **Keep track of new player connections or existing players leaving over time**:
  1. New players must announce ticket ID to the server for authentication and to map their connection to matchmaker [#injected-variables](#injected-variables "mention") or `assigned_ticket` (if backfilled).
  2. Create new Backfills for unused player capacity (leavers) throughout server lifespan.
  3. Renew expired Backfills, which are deleted after `ticket_expiration_period`.
* **Clean up (delete) any leftover Backfills** once the [/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped](https://docs.edgegap.com/learn/matchmaking/pages/0UXQAhtFuL0FkdoUmYTh#id-5.-deployment-stopped "mention"):
  * Unity - [`OnApplicationQuit`](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/MonoBehaviour.OnApplicationQuit.html) callback or custom game end callback,
  * Unreal Engine - [`OnWorldDestroyed`](https://forums.unrealengine.com/t/call-function-before-quit-game/344954/2) , [`PreExit`](https://forums.unrealengine.com/t/event-on-close/298087/2) , or a custom game end callback.

{% hint style="info" %}
Use [GetEnvironmentVariable in C#](https://learn.microsoft.com/en-us/dotnet/api/system.environment.getenvironmentvariable?view=net-8.0) or [GetEnvironmentVariable in C++](https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Core/GenericPlatform/FGenericPlatformMisc/GetEnvironmentVariable) to get variable values.
{% endhint %}

Any profile can be used for Backfill as long as valid server assignment and at least one ticket is provided. See [Matchmaking](/learn/matchmaking#backfill-showcase) for a minimal example.

## ⚙️ Configuration

Matchmaker API is generated from a JSON configuration specified when you create a new (or quick-restart) Matchmaker. You may specify any number of profiles with varying rules and expansions:

{% hint style="success" %}
See [Matchmaking](/learn/matchmaking) for our SDKs and detailed example scenarios.
{% endhint %}

<details>

<summary>🍀 Simple Example (Minimal Recommended Configuration)</summary>

<pre class="language-json"><code class="lang-json">{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "profiles": {
    "simple-example": {
      "ticket_expiration_period": "5m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m",
      "application": {
        "name": "<a data-footnote-ref href="#user-content-fn-6">my-game-server</a>",
        "version": "<a data-footnote-ref href="#user-content-fn-7">2024.01.30-16.23.00-UTC</a>"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 1,
              "min_team_size": 2,
              "max_team_size": 2
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 100,
              "max_latency": 200
            }
          }
        },
        "expansions": {}
      }
    }
  }
}
</code></pre>

</details>

<details>

<summary>🏁 Advanced Example (Complete Example Configuration)</summary>

```json
{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "allowed_cors_origins": [
    "https://*.my-game-server.com"
  ],
  "profiles": {
    "advanced-example": {
      "ticket_expiration_period": "5m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m"
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 1,
              "min_team_size": 4,
              "max_team_size": 4
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 125,
              "max_latency": 125
            }
          },
          "elo_rating": {
            "type": "number_difference",
            "attributes": {
              "max_difference": 50
            }
          },
          "selected_game_mode": {
            "type": "string_equality"
          },
          "selected_map": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "backfill_group_size": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {
          "30": {
            "elo_rating": {
              "max_difference": 150
            },
            "beacons": {
              "difference": 125,
              "max_latency": 250
            }
          },
          "60": {
            "elo_rating": {
              "max_difference": 200
            }
          },
          "180": {
            "match_size": {
              "team_count": 1,
              "min_team_size": 1,
              "max_team_size": 4
            },
            "beacons": {
              "difference": 99999,
              "max_latency": 99999
            }
          }
        }
      }
    }
  }
}
```

</details>

<details>

<summary><span data-gb-custom-inline data-tag="emoji" data-code="1f95b">🥛</span> Backfill Configuration Example</summary>

```json
{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "profiles": {
    "backfill-example": {
      "ticket_expiration_period": "30s",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m",
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 1,
              "min_team_size": 4,
              "max_team_size": 4
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 100,
              "max_latency": 200
            }
          },
          "backfill_group_size": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {}
      }
    }
  }
}
```

</details>

<details>

<summary>⚔️ Competitive Game Example</summary>

```json
{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "profiles": {
    "casual-example": {
      "ticket_expiration_period": "5m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m",
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 2,
              "min_team_size": 5,
              "max_team_size": 5
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 125,
              "max_latency": 150
            }
          },
          "selected_maps": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "backfill_group_size": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {
          "30": {
            "beacons": {
              "difference": 125,
              "max_latency": 250
            }
          },
          "180": {
            "beacons": {
              "difference": 99999,
              "max_latency": 99999
            }
          }
        }
      }
    },
    "competitive-example": {
      "ticket_expiration_period": "5m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m"
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 2,
              "min_team_size": 5,
              "max_team_size": 5
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 125,
              "max_latency": 150
            }
          },
          "versus_ranks": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {
          "120": {
            "beacons": {
              "difference": 125,
              "max_latency": 250
            }
          }
        }
      }
    },
    "challenger-example": {
      "ticket_expiration_period": "5m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m"
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 2,
              "min_team_size": 5,
              "max_team_size": 5
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 125,
              "max_latency": 150
            }
          },
          "elo_rating": {
            "type": "number_difference",
            "attributes": {
              "max_difference": 50
            }
          }
        },
        "expansions": {
          "120": {
            "beacons": {
              "difference": 125,
              "max_latency": 250
            }
          }
        }
      }
    }
  }
}
```

</details>

<details>

<summary>🤝 Cooperative Game Example</summary>

```json
{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "profiles": {
    "cooperative-example": {
      "ticket_expiration_period": "3m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m",
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "type": "player_count",
            "attributes": {
              "team_count": 1,
              "min_team_size": 4,
              "max_team_size": 4
            }
          },
          "beacons": {
            "type": "latencies",
            "attributes": {
              "difference": 125,
              "max_latency": 150
            }
          },
          "selected_difficulty": {
            "type": "string_equality"
          },
          "selected_map": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "player_level": {
            "type": "number_difference",
            "attributes": {
              "max_difference": 10
            }
          },
          "backfill_group_size": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "moderation_flags": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {
          "30": {
            "beacons": {
              "difference": 125,
              "max_latency": 250
            },
            "player_level": {
              "max_difference": 20
            }
          },
          "60": {
            "match_size": {
              "team_count": 1,
              "min_team_size": 2,
              "max_team_size": 4
            }
          },
          "150": {
            "match_size": {
              "team_count": 1,
              "min_team_size": 1,
              "max_team_size": 4
            }
          }
        }
      }
    }
  }
}
```

</details>

<details>

<summary>🎈 Social Game Example</summary>

```json
{
  "version": "3.2.2",
  "inspect": true,
  "max_deployment_retry_count": 3,
  "profiles": {
    "social-example": {
      "ticket_expiration_period": "3m",
      "ticket_removal_period": "1m",
      "group_inactivity_removal_period": "5m",
      "application": {
        "name": "my-game-server",
        "version": "2024.01.30-16.23.00-UTC"
      },
      "rules": {
        "initial": {
          "match_size": {
            "attributes": {
              "team_count": 1,
              "min_team_size": 50,
              "max_team_size": 50
            },
            "type": "player_count"
          },
          "beacons": {
            "attributes": {
              "difference": 125,
              "max_latency": 150
            },
            "type": "latencies"
          },
          "selected_mode": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "backfill_group_size": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          },
          "moderation_flags": {
            "type": "intersection",
            "attributes": {
              "overlap": 1
            }
          }
        },
        "expansions": {
          "15": {
            "beacons": {
              "difference": 125,
              "max_latency": 250
            },
            "match_size": {
              "team_count": 1,
              "min_team_size": 20,
              "max_team_size": 50
            }
          },
          "30": {
            "match_size": {
              "team_count": 1,
              "min_team_size": 10,
              "max_team_size": 50
            }
          },
          "150": {
            "match_size": {
              "team_count": 1,
              "min_team_size": 1,
              "max_team_size": 50
            }
          }
        }
      }
    }
  }
}
```

</details>

{% hint style="warning" %}
Editing a running matchmaker will **trigger a quick reload**, deleting all tickets and causing short downtime.
{% endhint %}

<details>

<summary><code>The application configuration is not valid for profile XYZ.</code></summary>

* We couldn’t find your [Apps and Versions](/learn/orchestration/application-and-versions), please verify `application`  values.

</details>

<details>

<summary><code>Docker image for '2024.01.30-16.23.00-UTC' is not cached.</code></summary>

[**🌟 Upgrade to Pay as You Go tier**](https://app.edgegap.com/user-settings?tab=memberships) **to unlock** [**instant deployments with Caching**](https://docs.edgegap.com/learn/matchmaking/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-start-a-deployment)**.**

* 4GB+ uncached images may take longer to deploy, resulting in [/pages/0UXQAhtFuL0FkdoUmYTh#id-4.-deployment-error](https://docs.edgegap.com/learn/matchmaking/pages/0UXQAhtFuL0FkdoUmYTh#id-4.-deployment-error "mention"). Consider optimizing your server image size ([Unreal Engine](/unreal-engine#optimize-server-build-size) / [Unity](/unity#optimize-server-build-size)).
* You may proceed anyway, though we recommend testing your deployment time.

</details>

### Profiles (Queues) <a href="#matchmaking-profiles" id="matchmaking-profiles"></a>

Profiles represent entirely separated matchmaking queues, sharing the same matchmaker version. You can **configure any number of profiles for each matchmaker.** Splitting up your player base in multiple profiles may result in longer queue times for your players.

Each matchmaker profile uses an [App Version](/learn/orchestration/application-and-versions) as a template to start new deployments (servers).

{% hint style="success" %}
Some game modes may require more vCPU / RAM, especially if they support a higher number of players. Each **matchmaker may include multiple profiles**, each linked to an app version with adjusted resources.
{% endhint %}

### Rules <a href="#matchmaking-rules" id="matchmaking-rules"></a>

Every player and group joins matchmaking queue and finds matches using  `initial` rules at first.

Each entry in profile at path `.rules.initial` represents a rule, where:

* **key** is a string value to name the rule however you prefer; e.g. `match_size` , and
* **value** is an object defining the type and attributes of the rule, adhering to our standard ruleset.

{% hint style="info" %}
All rules have to be met simultaneously to initiate host assignment and start or find a deployment.
{% endhint %}

**Operators (Rule Type)**

**`player_count`** is a special rule defining how many players need to match to initiate assignment.

{% hint style="warning" %}
Rule `player_count`  **is required and may only be defined once** in your initial configuration rules.
{% endhint %}

Matchmaker always strives to maximize match fill rate, up to specified `max_team_size` :

1. if max team size is reached the match is made immediately,
2. otherwise, players wait in queue to fill match until [expansion](#rule-expansion) (or expiration) is about to elapse,
3. shortly before [expanding](#rule-expansion) (or expiring), if partial match is possible (≥ min and < max team size), this match will be made with all players in same expansion stage (assuming other rules pass).

{% hint style="success" %}
For cooperative, free-for-all, or asymmetric team size game modes, set your `"team_count": 1` .
{% endhint %}

Team count may be configured to compose multiple balanced teams for competitive games:

* **group attributes are calculated as average/overlap** of the group's **player attributes,**
* **team attributes are calculated as average/overlap** of the team's **group attributes.**

Assuming a fixed team size of 4 players:

<figure><img src="/files/FyWJQ0vqMvfovcHnMlpd" alt=""><figcaption><p>Example Match Scenarios</p></figcaption></figure>

{% hint style="info" %}
**Groups match in teams without overfilling,** only if a team has sufficient capacity to fit the whole group.
{% endhint %}

**`string_equality`** matches players with the exact same string value.

<details>

<summary>Rule Example: <code>selected_game_mode</code></summary>

`selected_game_mode`  rule will match players case sensitively:

:white\_check\_mark: Alice + Bob + Dave may match,

:x: Alice + Erin, or Charlie + Frank will never match.

| "Free For All" | "Capture The Flag" | "capture the flag" |
| -------------- | ------------------ | ------------------ |
| Alice          | Erin               | Frank              |
| Bob            | Charlie            |                    |
| Dave           |                    |                    |

</details>

**`number_difference`** matches players within the absolute numerical difference from each other.

<details>

<summary>Rule Example: <code>elo_rating</code></summary>

`elo_rating`  rule above with `"max_difference": 50` initially:

:white\_check\_mark: Alice + Bob may match, or Bob + Charlie may match,

:x: Alice + Bob + Charlie will never match.

<figure><img src="/files/kQhTtBMS0hnm2lcz3ze8" alt=""><figcaption></figcaption></figure>

</details>

**`latencies`** is a special rule optimizing the ping of player matches:

* reduce client-server latency by removing regions with high latency (above threshold),
* improve match fairness by grouping players with similar latency (below difference).

<details>

<summary>Rule Example: <code>beacons</code></summary>

`beacons` rule configured with `"difference": 100, "max_latency": 200`  will match:

:white\_check\_mark: Alice and Bob may match:

* Tokyo is discarded (>200 ms),
* latency for Chicago within 100 ms absolute difference.

<table><thead><tr><th width="180">Beacon City</th><th width="80">Match</th><th width="132">abs(A - B) [ms]</th><th width="164">Alice [ms]</th><th width="164">Bob [ms]</th></tr></thead><tbody><tr><td>Chicago</td><td><span data-gb-custom-inline data-tag="emoji" data-code="2705">✅</span></td><td>75.0</td><td>12.3</td><td>87.3</td></tr><tr><td>Los Angeles</td><td><span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td><a data-footnote-ref href="#user-content-fn-8">113.2</a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td>145.6</td><td>32.4</td></tr><tr><td><del>Tokyo</del></td><td>n/a</td><td>n/a</td><td><a data-footnote-ref href="#user-content-fn-9"><del>233.2</del></a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td><a data-footnote-ref href="#user-content-fn-9"><del>253.2</del></a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td></tr></tbody></table>

:x: Alice and Charlie will never match:

* no beacons have < 200 ms latency for both players,
* Alice lives in North America - Illinois,
* Charlie lives in Asia - Japan.

<table><thead><tr><th width="180">Beacon City</th><th width="80">Match</th><th width="132">abs(A - B) [ms]</th><th width="164">Alice [ms]</th><th width="164">Charlie [ms]</th></tr></thead><tbody><tr><td><del>Chicago</del></td><td>n/a</td><td>n/a</td><td>12.3</td><td><a data-footnote-ref href="#user-content-fn-9"><del>215.6</del></a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td></tr><tr><td><del>Los Angeles</del></td><td>n/a</td><td>n/a</td><td>145.6</td><td><a data-footnote-ref href="#user-content-fn-9"><del>238.3</del></a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td></tr><tr><td><del>Tokyo</del></td><td>n/a</td><td>n/a</td><td><a data-footnote-ref href="#user-content-fn-9"><del>233.2</del></a> <span data-gb-custom-inline data-tag="emoji" data-code="274c">❌</span></td><td>24.2</td></tr></tbody></table>

</details>

{% hint style="warning" %}
Rule `latencies`  is **optional and may only be defined once in your initial configuration** rules.
{% endhint %}

Some players with high ping to all beacons due to ISP[^10] issues or slow connection (e.g. wireless/mobile) may cause lags and degrade game experience for others. To mitigate this issue:

* Gradually expand allowed latency maximum and difference (see [Advanced Example Config](/learn/matchmaking#advanced-example)),
  * players with high ping may have to wait longer than usual to find a match.
* Alternatively, allow players to override measurement with manual region selection, only sending fake ping values for the player-selected regions only (e.g. 25ms for fast match),
  * this may negatively impact player experience of the players' teammates and opponents.

{% hint style="info" %}
**High beacon ping doesn’t always result in high server ping**. Deployments are available in more locations than beacons. Beacons are orchestrated in real time to prioritize global coverage and reliability.
{% endhint %}

{% hint style="success" %}
See [Matchmaking](/learn/matchmaking) for **automated ping measurement using our SDKs**.
{% endhint %}

{% hint style="danger" %}
Beacons are automatically rescaled in real time - adding/removing/replacing existing beacons. Your clients and backend should account for this and **reload list of beacons before each matchmaking round**.
{% endhint %}

**`intersection`** matches players with one or more overlapping string values, case sensitively.

<details>

<summary>Rule Example: <code>selected_map</code></summary>

`selected_map` rule above with `"overlap": 1`  will match:

:white\_check\_mark: Alice + Bob + Charlie may match, or Alice + Bob + Dave may match,

:x: Alice + Bob + Charlie + Dave will never match.

<figure><img src="/files/gGU8HUpw6Tk3Gt1y7DXC" alt=""><figcaption></figcaption></figure>

</details>

#### Rule Expansion

Optionally, **`expansions`**  modify a rule’s attributes after a period of time spent in queue to relax  limitations and expand the pool of players which can be matched, **resulting in faster matches**.

<details>

<summary>Example Scenario: Expansions</summary>

[Initially, we require 1 team composed of exactly 4 players (possibly split in groups)](/learn/matchmaking#advanced-example) with:

* maximum of 125 ms latency against the same (any one) beacon,
* latency difference of 125 ms or less between lowest/highest value for the same beacon,
* skill rating difference of 50 points or less between lowest and highest ranking player,
* the exact same (case sensitive) selected game mode,
* at least one matching map selection (case sensitive) amongst players,
* at least one matching [backfill group size](#backfill-match) value amongst players.

In the example above, we **expand the search by modifying attributes** after:

<table data-view="cards"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td>30 seconds:</td><td><ul><li>4 players</li><li><strong>150 skill rating range</strong></li><li><strong>max 250 ms latency</strong></li></ul></td></tr><tr><td>60 seconds:</td><td><ul><li>4 players</li><li><strong>200 skill rating range</strong></li><li>max 250ms latency</li></ul></td></tr><tr><td>3 minutes (180s):</td><td><ul><li><strong>1-4 players</strong></li><li>200 skill rating range</li><li><strong>any latency</strong></li></ul></td></tr></tbody></table>

</details>

{% hint style="info" %}
Expansions of any rule’s attribute will **overwrite previous values** of that attribute.
{% endhint %}

{% hint style="success" %}
[**Learn about common pitfalls of matchmaking**](https://edgegap.com/blog/how-session-fill-rate-affects-your-multiplayer-hosting-costs)**, and** [**optimize your match fill rate with our guide**](https://edgegap.com/blog/how-to-optimize-session-fill-rate-in-your-matchmaker)**.**
{% endhint %}

## 📌 Injected Variables

Your server might need to know details about it’s players. Player attributes, resolved match values, and other values are injected to your deployment alongside the usual [Apps and Versions](/learn/orchestration/application-and-versions#injected-variables).

Preview unformatted **🏁 Advanced Example Variables:**

```
MM_MATCH_PROFILE=advanced-example
MM_EXPANSION=initial
MM_TICKET_IDS=["cusfn10msflc73beiik0","cusfn18msflc73beiil0"]
MM_TICKET_cusfn10msflc73beiik0={"id":"cusfn10msflc73beiik0","created_at":"2025-02-21T22:17:42.3886970Z","player_ip":"174.93.233.25","group_id":"b2080c27-19c9-4fb0-8fe7-4bf1e5d285d1","team_id":"cusfn1gmsflc73beiim0","attributes":{"beacons":{"Chicago":12.3,"LosAngeles":145.6,"Tokyo":233.2},"elo_rating":1337,"selected_game_mode":"quickplay","selected_map":["DustII","Airport","BankVault"],"backfill_group_size":["new","1"]}}
MM_TICKET_cusfn18msflc73beiil0={"id":"cusfn18msflc73beiil0","created_at":"2025-02-21T22:17:42.2548390Z","player_ip":"174.93.233.23","group_id":"015d4dc8-6c79-4b5c-bbc6-f309b9787c8f","team_id":"cusfn1gmsflc73beiim0","attributes":{"beacons":{"Chicago":87.3,"LosAngeles":32.4,"Tokyo":253.2},"elo_rating":1339,"selected_game_mode":"quickplay","selected_map":["Island","Airport"],"backfill_group_size":["new","1"]}}
MM_GROUPS={"b2080c27-19c9-4fb0-8fe7-4bf1e5d285d1":["cusfn10msflc73beiik0"],"015d4dc8-6c79-4b5c-bbc6-f309b9787c8f":["cusfn18msflc73beiil0"]}
MM_TEAMS={"cusfn1gmsflc73beiim0":["b2080c27-19c9-4fb0-8fe7-4bf1e5d285d1","015d4dc8-6c79-4b5c-bbc6-f309b9787c8f"]}
MM_MATCH_ID=advanced-example_initial-2025-02-21T22:17:43.3886970Z
MM_INTERSECTION={"selected_map":["Airport"],"backfill_group_size":["new","1"]}
MM_EQUALITY={"selected_game_mode":"quickplay"}
```

{% hint style="info" %}
Environment variables are **stored as stringified JSONs**, parse them using our SDK or a custom method.
{% endhint %}

{% hint style="success" %}
**Servers can map player connections to groups and attributes** after player sends their ticket ID to server.
{% endhint %}

## 🧵 Player Tracing

If your players experience any issues, tracing their path to server logs can be helpful. Each Matchmaker **deployment** **will be tagged with assigned player ticket IDs** so you can easily [Deployments](/learn/orchestration/deployments#filter-deployments) and find [Deployments](/learn/orchestration/deployments#container-logs) to help you troubleshoot.

{% hint style="success" %}
**Display ticket IDs and deployment IDs in client match history UI** to trace players when troubleshooting.
{% endhint %}

{% hint style="info" %}
See [Deployments](/learn/orchestration/deployments#connection-quality) to learn about deployment troubleshooting.
{% endhint %}

## 👀 Analytics

Gain insights into your matchmaker load and performance, no code or configuration required.

🌟 [**Upgrade Matchmaker to Enterprise Tier**](https://app.edgegap.com/matchmaker-management-v2/matchmakers/list) **to unlock matchmaking metrics and insights:**

<figure><img src="/files/nLSoqe2dfDAa1CJdpyO2" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/7EEptUPmppb2XG6VRjYE" alt=""><figcaption></figcaption></figure>

## ☁️ Hosting Cluster

Matchmaker is conveniently hosted and managed 24/7 around the clock by Edgegap.

Choose a hosting option best suited for your goal:

* **Free Cluster (shared)** to test all features and explore synergies with your design,
  * shuts down after 3 hours automatically, requiring restart to continue testing.
* **Private Cluster** **(dedicated)** to ensure a stable environment for your production needs,
  * pick your region and get 24/7 support for live games to release with confidence.

#### Private Cluster Tiers

We currently offer [3 private cluster tiers](https://edgegap.com/resources/pricing#managed-infrastructure) to cater to everybody’s needs:

<table><thead><tr><th width="160">Tier</th><th align="right">Hobbyist Tier</th><th align="right">Studio Tier</th><th align="right">Enterprise Tier</th></tr></thead><tbody><tr><td>Best Suited For</td><td align="right">enthusiasts,<br>solo developers</td><td align="right">commercial releases</td><td align="right">high-traffic launches</td></tr><tr><td>Resources</td><td align="right">1 vCPU + 2GB RAM</td><td align="right">6 vCPU + 12GB RAM</td><td align="right">18 vCPU + 48GB RAM</td></tr><tr><td>Redundancy</td><td align="right">1x virtual node</td><td align="right">3x virtual nodes</td><td align="right">3x virtual nodes</td></tr><tr><td>Rate Limit (req/s)</td><td align="right">200</td><td align="right">750</td><td align="right">2,000</td></tr><tr><td>Price, hourly</td><td align="right">$0.0312</td><td align="right"> $0.146</td><td align="right">$0.548</td></tr><tr><td><strong>Price, 30 days</strong><br>(nonstop usage)</td><td align="right"><strong>$22.464</strong></td><td align="right"><strong>$105.12</strong></td><td align="right"><strong>$394.56</strong></td></tr></tbody></table>

Upgrade to a private cluster with one click. Changing Private Cluster Tiers after launch, without any player downtime, is also possible with [#rolling-updates-and-ab-tests](#rolling-updates-and-ab-tests "mention"). Managed clusters provide high-availability service hosting maintained by Edgegap with 24/7 live support for publicly released games.

Resource requirements for your instance will depend on factors:

* **number of players** - more players result in more tickets and API requests,
* **number of requests per player** - faster retries increase service load and consume resources,
* **configuration complexity** - intersection rules and expansions are particularly demanding,
* **average match duration** - shorter sessions make players rejoin matchmaking more often,
* **expiration and removal periods** - stale tickets stockpile over time and consume resources,
* **client retry fallback logic** - retrying with jittered backoff helps spread traffic burst peaks.

{% hint style="warning" %}
**Prepare for success and optimize after launch, so you don’t block your players on release day.** Use [Developer Tools](/unity/developer-tools#matchmaking-sdk) or **implement exponential jittered backoff** to recover from high load.
{% endhint %}

{% hint style="info" %}
Our clusters use cloud machines featuring AMD/Intel CPUs with clock speed 2.4 - 3.2 GHz.
{% endhint %}

### Rate Limits

To protect your cluster from exceeding it's burst capacity and crashing, we limit the number of requests per second based on our internal load tests using [Matchmaking](/learn/matchmaking#advanced-example) configuration.

<table><thead><tr><th>API endpoint</th><th width="130">Free Tier</th><th width="130">Hobbyist Tier</th><th width="130">Studio Tier</th><th width="130">Enterprise Tier</th></tr></thead><tbody><tr><td><strong>Overall Limit</strong></td><td><strong>100</strong></td><td><strong>200</strong></td><td><strong>750</strong></td><td><strong>2,000</strong></td></tr><tr><td>Create Deployment</td><td>5</td><td>10</td><td>30</td><td>30</td></tr><tr><td>List Beacons</td><td>10</td><td>20</td><td>75</td><td>200</td></tr><tr><td>Create Group<br>+ Create Ticket<br>+ Create Group Ticket</td><td>10</td><td>20</td><td>75</td><td>200</td></tr><tr><td>Read Membership<br>+ Read Group<br>+ Read Ticket</td><td>10</td><td>120</td><td>450</td><td>1,300</td></tr><tr><td>Create Backfill</td><td>5</td><td>10</td><td>37</td><td>100</td></tr></tbody></table>

Rate limits are expressed in **combined requests per second to the specified set of API endpoints**.

If you game clients do not retry requests upon receiving response `429 Too Many Requests`  **your deployments may be missing players** who stop reading their assignments due to high load.

#### Load Testing

Matchmaking and assignments require CPU and memory usage, requiring a hosting cost with each private matchmaker. See resources and prices associated with each tier on [our pricing page](https://edgegap.com/resources/pricing#matchmaker).

{% hint style="success" %}
**Always use** [**private clusters**](#private-cluster-tiers) **for stress testing.** Free matchmakers are strictly limited for dev testing only.
{% endhint %}

When designing your load test, **please consider realistic player patterns**:

| Realistic Scenario                                                                            | Unrealistic Traffic Pattern                                                              |
| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| ✅ Players join matchmaking gradually, increasing req/s over several hours.                    | ❌ All players coordinate and create their tickets in the exact same second.              |
| ✅ Players wait an increasing amount of time between their retries (e.g. 1s-5s-10s-10s).       | ❌ All players retry immediately upon receiving `429 Too Many Requests`  response.        |
| ✅ Most players will receive their assignments  within a short time (10-60s) and stop polling. | ❌ All players continue polling for a set amount of time even after receiving assignment. |
| ✅ Most players finish their game (taking time) before restarting matchmaking anew.            | ❌ All players restart matchmaking anew immediately after receiving their assignment.     |
| ✅ Peak traffic is sustained for 6-8 hours a day, after which some time zones drop off.        | ❌ Peak traffic is sustained 24 hours a day, with all players playing night and day.      |

If a matchmaker is experiencing high load:

* if CPU is throttling, matchmaking could be slowed down,
* if matchmaker runs out of memory, it will restart without losing ticket information, hoping that clients will implement exponential backoff and the burst is spread over longer period of time.

## ⏩ Rolling Updates <a href="#rolling-updates-and-ab-tests" id="rolling-updates-and-ab-tests"></a>

Keeping track of compatibility between server and client versions can get complicated. Follow our tips for reliable releases, updates, and preventing downtime or compatibility issues.

**Your Matchmaker URL and Auth token will always remain the same after restarting.**

{% hint style="danger" %}
**Create separate matchmakers for dev & production** environments to experiment safely.
{% endhint %}

#### ⚠️ **Before Going Live**

We recommend creating multiple copies of your matchmaker ahead of time: `green`, `blue` and `orange`. You can rotate which matchmaker is in use as you release updates ([blue/green strategy](https://en.wikipedia.org/wiki/Blue%E2%80%93green_deployment)).

**Choose different regions for each instance to prevent downtime** during localized outages.

<figure><img src="/files/wjMaDNeItdzwaI2PeYBs" alt=""><figcaption><p>Blue/Green DevOps Environment Example</p></figcaption></figure>

#### **🔃 Client + Server Update**

**Prerequisites:** This section assumes you’ve completed [#before-going-live](#before-going-live "mention").

In order to **release game client + server updates**, you may:

1. Prepare new server app version `v1.2.0-rc` on Edgegap:
   1. push a new image tag to your container registry `t1.2.0`,
   2. create new app version `v1.2.0-rc`,
2. Perform any dev tests by [deploying your new app version](https://app.edgegap.com/deployment-management/deployments/list) `v1.2.0-rc`:
   1. connect your game engine’s Editor to the provided URL + external port,
3. Update unused matchmaker `blue` to link to your new image tag `t1.2.0`,
   1. enable caching for new app version `v1.2.0-rc` , enabling cache for this version will ensure the image is also cached for version `v-blue`  since they reference the same tag,
   2. wait for caching indicator in version `v1.2.0-rc`  to reach :green\_circle: green,
4. Update your new game client `c2` to use the new version `v-blue` when creating tickets:
   1. update your base URL and Authorization token in game client,
5. Perform QA tests and final verifications of your new game client `c2`:
   1. if you find and resolve any issues, repeat process from the beginning,
   2. wait 3-7 days to propagate matchmaker DNS changes to ISPs globally, after the matchmaker has been stopped (quick restart doesn't require DNS updates or waiting period),
6. Release your new game client update `c2` on game distribution platforms,
7. Allow time for new game client `c2` to distribute to player devices (typically up to 3-7 days):
   1. monitor outdated game clients `c1`  using deployment [Deployments](/learn/orchestration/deployments#analytics),
8. Clean up unused resources in your Edgegap account:
   1. delete image tag `t1.0.0` to free up Container Registry capacity,
   2. delete image tag `t1.1.0` to free up Container Registry capacity,
   3. turn off your `green`  matchmaker to pause billing until your next update.

{% hint style="success" %}
**For your next update**, increase version numbers and swap `green` and `blue` keywords in the guide.
{% endhint %}

#### **⚡ Server Hotfix**

**Prerequisites:** This section assumes you’ve completed [#before-going-live](#before-going-live "mention").

To **release a server patch without requiring a game client update**, you may:

1. Prepare new server app version `v1.2.0-rc` on Edgegap:
   1. push a new image tag to your container registry `t1.2.0`,
   2. create new app version `v1.2.0-rc`,
2. Perform tests and verifications by [deploying your new app version](https://app.edgegap.com/deployment-management/deployments/list) `v1.2.0-rc`:
   1. connect your game engine’s Editor to the provided URL + external port,
   2. if you find and resolve any issues, repeat process from the beginning,
   3. enable caching for new app version `v1.2.0-rc` , enabling cache for this version will ensure the image is also cached for version `v-green`  later since they will reference the same tag,
   4. wait for caching indicator in version `v1.2.0-rc`  to reach :green\_circle: green,
3. Update version `v-green`  to link to your new image tag `t1.2.0`,
   1. new matches will automatically initiate assignment with the updated tag `t1.2.0`,
   2. monitor outdated game clients `c1`  using deployment [Deployments](/learn/orchestration/deployments#analytics),
4. Clean up of unused resources in your Edgegap account:
   1. delete image tag `t1.1.0` to free up Container Registry capacity.

## 📗 API <a href="#matchmaking-api" id="matchmaking-api"></a>

Clients and servers may call API directly or with game engine SDKs, see also [Matchmaking](/learn/matchmaking).

{% hint style="info" %}
Unity/Android - consider [using raw string interpolation](https://www.c-sharpcorner.com/article/convert-string-to-json-in-c-sharp/) to prevent code stripping of hardcoded JSONs.
{% endhint %}

{% hint style="success" %}
**Swagger Web UI**: deploying your service will generate an openAPI specification and a convenient web UI. Open the URL in your browser to view and test all API endpoints, and to review payload examples.
{% endhint %}

{% tabs fullWidth="false" %}
{% tab title="🍀 Simple Example" %}
{% file src="/files/xo7YWqkyH45ZTQp265pG" %}
{% endtab %}

{% tab title="🏁 Advanced Example" %}
{% file src="/files/g3XbWYhExzQqIgfozjJ2" %}
{% endtab %}

{% tab title="🎾 Custom Lobby" %}
{% file src="/files/puN4tMLXMjezZXEIDSEZ" %}
{% endtab %}

{% tab title="🥛 Backfill Showcase" %}
{% file src="/files/Qatu3klwXW4jHD6eNzxa" %}
{% endtab %}

{% tab title="⚔️ Competitive Games" %}
{% file src="/files/L7uSekelDnllYgaAOMP7" %}
{% endtab %}

{% tab title="🤝 Cooperative Games" %}
{% file src="/files/gXfaw2WbHjsioU19yvYq" %}
{% endtab %}

{% tab title="🎈 Social Games" %}
{% file src="/files/4f54lJkUXM2LnKxcvKsU" %}
{% endtab %}
{% endtabs %}

Import API specification to [Scalar API Web Client](https://client.scalar.com/workspace/default/request/default) or [Swagger Editor](https://editor.swagger.io/) to inspect details.

### Server to Server <a href="#server-to-server-api" id="server-to-server-api"></a>

Add enhanced or customized controls over matchmaking flow - implement a custom proxy using our [Managed Clusters](/learn/advanced-features/managed-clusters) or any cloud FaaS[^11] compute platform, to achieve any of:

* attach sensitive player attributes - such as cheater flags, skill ratings, or similar,
* provide team and match context in-game - list my teammates and opponents during loading,
* restrict specific edge cases - e.g. allow only 1 group per player at any time,
* add caching or API rate limiting - reduce number of requests and load on matchmaker,
* customize lobby-group integration - create asymmetric/role-based lobbies before matchmaking.

{% hint style="success" %}
**Include parameter `player_ip`  with member's public IP address** to ensure lowest possible player latency and take advantage of [/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-server-score-strategy-best-practice](https://docs.edgegap.com/learn/matchmaking/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-server-score-strategy-best-practice "mention").
{% endhint %}

{% hint style="info" %}
Game clients may use [ipify.org](http://ipify.org/) free service to find their public IPs. VPNs may mask public IP address.
{% endhint %}

<figure><img src="/files/Y8maqcwssXSXMPt0W5tn" alt=""><figcaption><p>Server to Server Matchmaking Activity Diagram</p></figcaption></figure>

#### Cross-Origin Resource Sharing (CORS)

For webGL games hosted on third party distribution platforms (e.g. [itch.io](http://itch.io/)), sending any requests to Matchmaker from game client may result in [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) policy violations. Most modern web browsers send a [pre-flight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) to verify that a backend service (the Matchmaker) understands and accepts communication from your game client.

Failing pre-flight check (default for security reasons) can result in [one of several possible CORS-related errors](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSMissingAllowOrigin), most commonly `CORS header 'Access-Control-Allow-Origin' missing` .

To resolve this error, add **`allowed_cors_origin`** parameter to your configuration to either:

* whitelist your exact client hosting domains:

<details>

<summary>🍀 Simple Example (Specific Domains Example)</summary>

<pre class="language-json"><code class="lang-json">{
  "version": "3.2.3",
  "allowed_cors_origins": [
    "https://dev.my-game-server.com",
    "https://prod.my-game-server.com"
  ],
  "profiles": {
      <a data-footnote-ref href="#user-content-fn-12">...</a>
  }
}
</code></pre>

</details>

* or whitelist a wildcard domain (including all subdomains):

<details>

<summary>🍀 Simple Example (Wildcard Domain Example)</summary>

<pre class="language-json"><code class="lang-json">{
  "version": "3.2.3",
  "allowed_cors_origins": ["https://*.my-game-server.com"],
  "profiles": {
      <a data-footnote-ref href="#user-content-fn-12">...</a>
  }
}
</code></pre>

</details>

{% hint style="info" %}
**No credentials are required for Matchmaker pre-flight requests**, if domains are configured correctly.
{% endhint %}

## 🚨 Troubleshooting

**Your success is our priority.** If you'd like to send custom requests, ask for missing critical features, or express any thoughts, [please reach out in our Community Discord](https://discord.gg/MmJf8fWjnt).

<details>

<summary><code>The application configuration is not valid for profile XYZ.</code></summary>

* We couldn’t find your [Apps and Versions](/learn/orchestration/application-and-versions), please verify `application`  values.

</details>

<details>

<summary><code>Docker image for '2024.01.30-16.23.00-UTC' is not cached.</code></summary>

[**🌟 Upgrade to Pay as You Go tier**](https://app.edgegap.com/user-settings?tab=memberships) **to unlock** [**instant deployments with Caching**](https://docs.edgegap.com/learn/matchmaking/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-start-a-deployment)**.**

* 4GB+ uncached images may take longer to deploy, resulting in [/pages/0UXQAhtFuL0FkdoUmYTh#id-4.-deployment-error](https://docs.edgegap.com/learn/matchmaking/pages/0UXQAhtFuL0FkdoUmYTh#id-4.-deployment-error "mention"). Consider optimizing your server image size ([Unreal Engine](/unreal-engine#optimize-server-build-size) / [Unity](/unity#optimize-server-build-size)).
* You may proceed anyway, though we recommend testing your deployment time.

</details>

<details>

<summary>Why am I getting errors when trying to create a new matchmaker?</summary>

* Please read the error, it’s possible you’ve misspelled an identifier, rule, or an operator. - Use [JSONLint](https://jsonlint.com/) to validate your JSON formatting, you may have missed a comma or a bracket. - Reach out over [our Community Discord](https://discord.gg/MmJf8fWjnt) for help, we’ll be happy to assist. 🙏

</details>

<details>

<summary>Why did my matchmaker turned off automatically after 3 hours?</summary>

* Matchmakers in Free Tier are intended for initial tests and are automatically turned off after 3 hours. To continue testing you may [restart your matchmaker](https://app.edgegap.com/matchmaker-management-v2/matchmakers/list).
* Consider upgrading to paid tier for unliminted runtime.

</details>

<details>

<summary>Why can’t I start a second deployment on my account?</summary>

* You may only run 1 concurrent deployment in Free Tier.
* Please consider upgrading to paid tier for unlimited deployments.

</details>

<details>

<summary>Why am I getting assignment/deployment at random times, disregarding <code>player_count</code>?</summary>

* You or another team member may have created tickets during a previous testing session which were not assigned. Please [restart your matchmaker](https://app.edgegap.com/matchmaker-management-v2/matchmakers/list).

</details>

<details>

<summary>My ticket is stuck in <code>SEARCHING</code> .</summary>

* Please verify you’ve created enough matching tickets adhering to your configuration.

</details>

<details>

<summary>My ticket is stuck switching between <code>MATCH_FOUND</code> and <code>TEAM_FOUND</code> repeatedly.</summary>

* Free Tier accounts are limited to 1 deployment at a time.
* Please consider upgrading or stop your current deployment to start a new one.

</details>

<details>

<summary>My ticket goes straight to <code>CANCELLED</code>.</summary>

* Your ticket reached it’s expiration. Create a new ticket or increase the expiration period in your configuration for testing purposes.

</details>

<details>

<summary>I receive <code>HTTP 404 Not Found</code> when checking on my ticket.</summary>

* Your ticket was removed either by a DELETE request, or by reaching it’s removal period (starts after ticket is expired, defined in your configuration). Recreate a new ticket or increase the expiration/removal periods in your configuration for testing purposes.

</details>

<details>

<summary>My matchmaker shows an error, what should I do?</summary>

* If this is a development or a testing instance, try restarting your matchmaker first. - Please report any issues through [our Community Discord](https://discord.gg/MmJf8fWjnt).
* In case this issue is impacting a live game, create an [urgent support request](https://edgegap.atlassian.net/servicedesk/customer/portal/3).

</details>

## 🔖 Changelog

#### Semantic Versioning

Our developer tools and managed services use official [Semantic Versioning](https://semver.org/), indicating which updates are ✅ safe (minor, patch) and which may contain ⚠️ breaking changes (major).

**Once a version is released, it will never be modified/changed**.

Your configuration file will be validated depending on matchmaker version used, make sure your rules are matching the matchmaker version’s capabilities.

{% hint style="info" %}
**The latest version of matchmaker is `3.2.2`**. All examples on this page are up to date.

Keep an eye out for [updates and announcements](/docs/release-notes). See also [#rolling-updates-and-ab-tests](#rolling-updates-and-ab-tests "mention").
{% endhint %}

{% hint style="warning" %}
**To upgrade your matchmaker version - Stop, Edit, Restart.** Quick Restart won't apply version changes.
{% endhint %}

[^1]: example value

[^2]: game clients join a Lobby to retrieve matchmaking Group ID and join the Group

[^3]: Fully Qualified Domain Name

[^4]: e.g. for 3 free slots = \["3", "2", "1"]

[^5]: replace "2" with the number of actual group members

[^6]: replace with your own application name

[^7]: replace with your own application version

[^8]: maximum difference exceeded

[^9]: maximum latency exceeded

[^10]: Internet Service Provider

[^11]: [Function as a Service](https://www.ibm.com/think/topics/faas)

[^12]: see other examples


# Server Browser

Get started with Server Browser quickly and explore example scenarios for various genres.

Server Browser is a managed service for [Deployments](/learn/orchestration/deployments#match-bound) and [Persistent](/learn/orchestration/persistence) servers:

* **help players search and join suitable servers** based on capacity, latency, or game parameters;
* **pre-warm new servers** to serve global audiences at scale and prevent frustrating queues;
* **streamline server operations** including updates, restarts, persistence, meshing, and more.

{% hint style="success" %}
Looking to match players based on strict rules, without allowing server choice? Consider [Matchmaking](/learn/matchmaking).
{% endhint %}

## ✔️ Preparation

**Testing this service is entirely free, no credit card required.**

Free Tier allows up to 3 hours of runtime on our shared test cluster, after each restart.

This tutorial assumes you have already:

* [understood Edgegap’s deployment model](https://docs.edgegap.com/learn/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-just-in-time-deployment-dedicated-servers),
* published your server application on Edgegap ([Unreal Engine](/unreal-engine), [Unity](/unity)),
* successfully connected from a game client to your server on Edgegap.

### Functions and Flow

<figure><img src="/files/i2b0y0S5ani1sv4p4brp" alt=""><figcaption><p>Server Browser: Flow and Hierarchy</p></figcaption></figure>

Server Browser offers two main features:

[#start-browsing](#start-browsing "mention") with Game Clients to:

* Discover and find suitable server instances, view slots, and reserve available capacity.
* Reserve seats in an instance slot, retrieve connection details, and connect to servers.
* Authenticate player connections in Deployments using [Federated Identity](#user-content-fn-1)[^1].
* Update instance slots' available capacity and/or metadata to modify discovery criteria.

[#automated-scaling](#automated-scaling "mention") (optional) with Scaling Policies to:

* Monitor available server instances, slots, capacity - per region and/or other criteria.
* Deploy Servers to increase capacity with prewarming or just-in-time scaling.
* Automate ops with special policies for demos, updates, testing, QA, tournaments, and more.

{% hint style="info" %}
After release, **your server browser will need to run 24/7** to ensure players across the world can join servers.
{% endhint %}

## ▶️ Start Browsing

Learn about server/player lifecycle and their responsibilities to ensure efficient server use.

### Authenticate

All requests must send an `Authorization`  HTTP header with your secret **Auth Token:**

<pre><code>Authorization: <a data-footnote-ref href="#user-content-fn-2">xxxxxxxx-e458-4592-b607-c2c28afd8b62</a>
</code></pre>

{% hint style="warning" %}
**Keep your tokens secret and safe! Edgegap staff will never ask you for your tokens.**
{% endhint %}

Server Browser automatically generates two types of tokens:

* **Server Token** - required for [Server API](#server-lifecycle) methods, can be [injected as app version variable](/learn/orchestration/application-and-versions#injected-variables).
  * Grants access to all API methods, and is handy for testing, devops, or custom orchestration.
* **Client Token** - required for [Monitor API and Seat Reservation API](#player-lifecycle) used by Game Clients.
  * We recommend storing this token in a third party secret store to make token rotation easier.

### Discover Instance

{% hint style="warning" %}
**New** [Deployments](/learn/orchestration/deployments) **must create a new Instance** when initialized to keep track of added capacity.
{% endhint %}

{% hint style="info" %}
See [#automated-scaling](#automated-scaling "mention") to learn about Scaling Policies and start deployments automatically.
{% endhint %}

**Required information** for each server instance includes:

* at least one Slot defined when initializing instance,
* server connection details - URL, IP, port information, and location.

**Optional custom metadata parameters** for player filtering, sorting, and browsing; for example:

* slot information - team capacity and team-specific metadata (e.g. team name),
* name and tags - customizable, unique, human-readable and searchable labels;
* compatibility data - server version or supported client versions;
* latency qualifiers - city and region identifiers, and assigned [Ping Beacons](/learn/orchestration/ping-beacons) details;
* game parameters - level/scene/map, game mode, difficulty, mods used;
* any other custom parameters to help players filter and find a suitable server.

{% hint style="info" %}
Metadata parameters above are just examples, you can define any number of parameters as needed.
{% endhint %}

{% hint style="success" %}
To serialize nested objects try encoding their accessor path in the key as `"object.child.property"`.
{% endhint %}

Servers may **update instance or slot metadata anytime** to modify their discoverability criteria. When updating metadata, all indexed keys must be provided with valid values (even if not modified).

**Server Instances must periodically send a keep-alive heartbeat** to verify their ongoing availability and prevent players from joining crashed or offline servers. Missing heartbeat for the configured expiration period will automatically delete instance and any pending seat reservations.

{% hint style="info" %}
See [Persistence](/learn/orchestration/persistence) for managing persistent world state and [Apps and Versions](/learn/orchestration/application-and-versions#active-caching) for faster deployments.
{% endhint %}

### Allocate Capacity

Instance and Slot capacity may be allocated in two ways, used individually or combined:

* [#auto-assigned-reservation](#auto-assigned-reservation "mention") to choose a server started with a specific scaling policy,
* [#search-and-browse](#search-and-browse "mention") to let the player define filters and browse suitable servers to pick from.

{% hint style="success" %}
We recommend getting started with [#auto-assigned-reservation](#auto-assigned-reservation "mention") as the simpler option.
{% endhint %}

#### Auto-Assigned Reservation

{% hint style="info" %}
Implement this feature if you wish to **choose server automatically**, based on regional capacity.
{% endhint %}

Players may create an auto-assigned reservation, only supplying player IDs and a scaling policy name. Server Browser will automatically find an instance with a slot offering sufficient joinable capacity and reserve seats, responding immediately with instance connection details.

If there is no suitable instance slot for this reservation, the response:

* **status code indicates whether the policy is scaling up** and more capacity will be added,
* **header `Retry-After`  indicates wait period (seconds) before retrying**, if retryable.

Once a reservation is completed, you may skip to [#connect-to-server](#connect-to-server "mention").

#### Search and Browse

{% hint style="info" %}
Implement this feature if you wish to **show users a list of servers and allow custom reservations**.
{% endhint %}

Players may list server instances and [paginate through results](#pagination) to find a server they'd like to join.

Instances and slots may be filtered and sorted with built-in parameters or [indexed metadata](#configuration):

<table><thead><tr><th width="400">Property</th><th width="140">Data Type</th><th width="105">Instance</th><th width="105">Slot</th></tr></thead><tbody><tr><td><code>request_id</code></td><td><code>string</code></td><td>✅</td><td>❌</td></tr><tr><td><code>total_joinable_seats</code>, <code>total_available_seats</code></td><td><code>int</code></td><td>✅</td><td>❌</td></tr><tr><td><code>name</code></td><td><code>string</code></td><td>❌</td><td>✅</td></tr><tr><td><code>available_seats</code>, <code>reserved_seats</code></td><td><code>int</code></td><td>❌</td><td>✅</td></tr><tr><td><code>created_at</code>, <code>updated_at</code></td><td><code>string</code></td><td>✅</td><td>✅</td></tr><tr><td><code>metadata.{index}</code> (custom)</td><td><code>string</code>, <code>int</code>, <code>float</code>, <code>bool</code></td><td>✅</td><td>✅</td></tr></tbody></table>

Available filtering operators depend on the data type of the filtered property:

<table><thead><tr><th width="125">Parameter</th><th width="135">Operators</th><th>Example Filter (based on Simple Example)</th></tr></thead><tbody><tr><td><code>string</code></td><td><p><a data-footnote-ref href="#user-content-fn-3"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-4"><code>ne</code></a> or </p><p><a data-footnote-ref href="#user-content-fn-5"><code>lt</code></a>  or <a data-footnote-ref href="#user-content-fn-6"><code>le</code></a> or </p><p><a data-footnote-ref href="#user-content-fn-7"><code>gt</code></a>  or <a data-footnote-ref href="#user-content-fn-8"><code>ge</code></a>  or<br><code>contains</code></p></td><td><pre><code>?$filter=metadata.custom_name contains 'my game'
and metadata.server_version le '1.1.0'
and metadata.server_version ge '1.0.0'
&#x26;$order=metadata.custom_name asc
</code></pre></td></tr><tr><td><code>int</code>, <code>float</code></td><td><p><a data-footnote-ref href="#user-content-fn-3"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-4"><code>ne</code></a> or </p><p><a data-footnote-ref href="#user-content-fn-5"><code>lt</code></a>  or <a data-footnote-ref href="#user-content-fn-6"><code>le</code></a> or </p><p><a data-footnote-ref href="#user-content-fn-7"><code>gt</code></a>  or <a data-footnote-ref href="#user-content-fn-8"><code>ge</code></a>  </p></td><td><pre><code>?$filter=metadata.xp_multiplier gt 1.0
&#x26;$order=metadata.xp_multiplier desc
</code></pre></td></tr><tr><td><code>bool</code></td><td><a data-footnote-ref href="#user-content-fn-3"><code>eq</code></a>  or <a data-footnote-ref href="#user-content-fn-4"><code>ne</code></a></td><td><pre><code>?$filter=metadata.allows_new_connections eq true
</code></pre></td></tr></tbody></table>

{% hint style="success" %}
Filter by regions and/or cities metadata to narrow down selection before measuring latency to servers.
{% endhint %}

{% hint style="info" %}
Learn about cursor-based [#pagination](#pagination "mention") to let users fetch more results.
{% endhint %}

#### Reserve Seats

Before joining a server, a seat reservation is required to ensure that the instance offers sufficient available capacity. Reservations can include a group of players or a solo individual.

Federated Identity: Players must provide a unique third party player ID in their reservation. Sending the same ID once they [#connect-to-server](#connect-to-server "mention") will allow the server to verify their identity.

Once a reservation is made successfully ([200 OK](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/200)) players should attempt connecting immediately. Pending **reservations expire after 30 seconds (configurable) unless confirmed** by your server.

**Reservations exceeding slot's joinable seat capacity will be automatically rejected** ([409 Conflict](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/409)). Joinable seats are any available seats which have not been reserved by other players yet.

{% hint style="info" %}
Server may forcefully change any slot's capacity, add, delete, or update any slots. **All reservations for a given slot will be removed if any pending reservations exceed new available slot capacity.**
{% endhint %}

### Connect to Server

Once a player found a suitable instance, they **retrieve required connection details from** (URL or IP, [External Port](/learn/orchestration/application-and-versions#port-mapping)). As soon as the seat reservation is made, **players may proceed to connect to your deployment's game server and pass their player ID**.

{% tabs %}
{% tab title="Unreal Engine" %}
To **connect from PIE (Editor)** during development and testing, press the tilde key `~`  and type `open {URL}:{port}` and wait for your editor to load the map.

{% hint style="success" %}
In case of failed connections or dark screen consult our [troubleshooting guide](/unreal-engine#troubleshooting-and-faq-1).
{% endhint %}
{% endtab %}

{% tab title="Unity" %}
To **connect your Unity Editor** or **game client** to your cloud deployment, input:

* **Deployment** **URL** pointing to the server's IP, usually in `NetworkManager`  component.
* **External port** mapping to the [server's internal listen port](https://docs.edgegap.com/learn/advanced-features/application-and-versions#port-mapping), usually in a Transport component.

{% hint style="success" %}
In case of connection timeout or other issues consult our [troubleshooting guide](/unity#troubleshooting-and-faq-4).
{% endhint %}
{% endtab %}
{% endtabs %}

To authenticate new connections, **your server must send a bulk reservation confirmation** request with all new players' IDs, receiving information in confirmation response:

* assignment of accepted player reservations to their preferred slot,
* assignment of expired player reservations to their preferred slot,
* a list of unknown player IDs.

Your **server may decide how to handle each group of players** and whether to allow or kick/ban expired or rejected users. Each of the **instance's slots must be updated immediately with the new available seat count** to ensure future reservations won't exceed slot capacity.

### Abandon Server

When players leave, your server must increase available seat capacity for the assigned slot.

{% hint style="success" %}
If your game design allows a reconnection period, your server may wait before updating slots.
{% endhint %}

Read about [Persistence](/learn/orchestration/persistence#recovery-objectives) to prevent frustrating persistent server rollbacks.

## 🚀 Automated Scaling

Server Browser is compatible with several different methods of autoscoring:

* **pre-warming method** - starting servers strictly with Server Browser scaling policies,
* **just-in-time method** - start through [Matchmaking](/learn/matchmaking) and [fill with Server Browser](#allocate-capacity),
* **custom autoscaler** - start through custom game backend and [fill with Server Browser](#allocate-capacity).

The following guide will focus on **pre-warming with Scaling Policies** as the primary method.

{% hint style="success" %}
Learn how to stop deployments in [Unreal Engine](/unreal-engine#stop-deployments), [Unity](/unity#stop-deployments), or [with API](/docs/api/dedicated-servers#delete-v1-self-stop-request_id-access_point_id) to manage lifecycle reliably.
{% endhint %}

### Monitor Capacity

Scaling policies continuously refresh the list of your server instances (discovered Deployments), repeating every [`monitoring_interval`](#user-content-fn-9)[^9] . Each policy requires a filter using the same [filtering syntax](#search-and-browse) as players do when searching for instances - per region, capacity, or other criteria.

Your configured [`minimum_active_instances`](#user-content-fn-10)[^10]  amount can be treated either as a:

* **fixed capacity** of deployments that you wish to keep running at all times,
* **pre-warm standby** deployment buffer to hide initialization delays.

#### Fixed Capacity

Keep a fixed amount of active servers for games with [Persistence](/learn/orchestration/persistence), particularly when such games offer players to provision [Persistence](/learn/orchestration/persistence#community-servers).

This type of policy configuration is also sometimes used for Quality Assurance, Tournaments, Closed Alphas, Publisher Demos, or other kinds of limited capacity events and operations.

{% hint style="info" %}
Scaling policy helps you automatically restart and recycle crashed servers on the fly.
{% endhint %}

#### Pre-Warm Standby

Start servers ahead of player demand if:

* you're launching a big release and expect rapid inflow of players in a short timespan,
* or server initialization requires more than 30 seconds ([not including deployment time](#user-content-fn-11)[^11]),
* or game implements meshing strategies requiring layered or circular network dependencies.

### Deploy Servers

New deployments will be started automatically when the amount of monitored server instance*s* drops below the configured minimum of active instances. All deployments are requested immediately and retried infinitely every monitoring interval after [`deployment_registration_period`](#user-content-fn-9)[^9]  elapsed.

{% hint style="warning" %}
Verify that new deployments [perform auto-discovery and create instances](#discover-instance) matching your policy filter correctly, or **your policy may loop infinitely and create large amounts of unused deployments**!&#x20;
{% endhint %}

Policies start deployments with [Private Fleets](/learn/orchestration/private-fleets) (with Overflow to Cloud) or directly to Cloud.

Available parameters include ([see API specification](/docs/api/dedicated-servers#private-fleets)):

* [**application and version**](/learn/orchestration/application-and-versions) - build version, resources, and other orchestration parameters,
* **users** - a single set of geographical coordinates for preferred [server placement](/learn/orchestration/deployments#regional-standby),
* [**private host IDs**](/learn/orchestration/private-fleets) - leave empty for cloud, or specify hosts within the desired region,
* [**tags**](/learn/orchestration/deployments#dashboard-monitoring) - tag with policy name to find deployments started with this policy later on,
* [**environment variables**](/learn/orchestration/deployments#custom-variables) - pass custom parameters and secrets to servers,
* [**webhooks**](/learn/orchestration/deployments#webhooks-and-postbacks) - notify your game backend (or matchmaker) of deployment lifecycle events,
* [**require cached locations**](/learn/orchestration/application-and-versions#active-caching) - if you prefer faster deployments only in cached locations.

### Example Policies

Test and modify any of these policies as you need. Most games will use multiple policies.

{% tabs %}
{% tab title="🍀 QA Pool" %}
A simple policy to keep one server deployed for testing at all times.

<pre class="language-json" data-title=""><code class="lang-json">{
  "name": "sb-qa-pool",
<strong>  <a data-footnote-ref href="#user-content-fn-12">"filter"</a>: "metadata.policy_name eq 'sb-qa-pool'",
</strong>  "deployment_request": {
    "private_host_ids": [],
    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["sb-qa-pool"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-qa-pool",
</strong>        "is_hidden": false
      }
    ]
  },
<strong>  "minimum_active_instances": 1
</strong>}
</code></pre>

{% endtab %}

{% tab title="🌡️ Pre-Warm" %}
Start 10x deployments prior to the release in anticipation of demand. Copy for each region.

<pre class="language-json" data-title=""><code class="lang-json">{
  "name": "sb-v1.0.0-chicago",
<strong>  <a data-footnote-ref href="#user-content-fn-16">"filter"</a>: "total_joinable_seats gt 0 and metadata.policy_name eq 'sb-v1.0.0-chicago'",
</strong>  "deployment_request": {
    "private_host_ids": [],
    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["sb-v1.0.0-chicago"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-v1.0.0-chicago",
</strong>        "is_hidden": false
      }
    ]
  },
<strong>  <a data-footnote-ref href="#user-content-fn-17">"minimum_active_instances"</a>: 10
</strong>}
</code></pre>

{% endtab %}

{% tab title="🔒 MMO" %}
Each region adds deployments as available capacity drops below a threshold.

<pre class="language-json" data-title=""><code class="lang-json">{
<strong>  "name": "sb-mmo-chicago",
</strong><strong>  <a data-footnote-ref href="#user-content-fn-18">"filter"</a>: "total_joinable_seats gt 5 and metadata.policy_name eq 'sb-mmo-chicago'",
</strong>  "deployment_request": {
<strong>    <a data-footnote-ref href="#user-content-fn-19">"private_host_ids"</a>: ["alpha-north-america-95fab093"],
</strong>    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["sb-mmo-chicago"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-mmo-chicago",
</strong>        "is_hidden": false
      }
    ],
<strong>    <a data-footnote-ref href="#user-content-fn-20">"webhook_on_terminated"</a>: {
</strong>      "url": "https://my-webhook.com"
    }
  },
<strong>  "minimum_active_instances": 3
</strong>}
</code></pre>

{% endtab %}

{% tab title="🔑 Community" %}
One policy per server owner, passing a custom password used for server authentication.

<pre class="language-json" data-title=""><code class="lang-json">{
<strong>  "name": "sb-owner-jnjnc8mid",
</strong><strong>  <a data-footnote-ref href="#user-content-fn-21">"filter"</a>: "metadata.policy_name eq 'sb-owner-jnjnc8mid'",
</strong>  "deployment_request": {
<strong>    <a data-footnote-ref href="#user-content-fn-19">"private_host_ids"</a>: ["alpha-north-america-95fab093"],
</strong>    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["community", "sb-owner-jnjnc8mid"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-owner-jnjnc8mid",
</strong>        "is_hidden": false
      },
      {
<strong>        "key": "SB_SERVER_PASSWORD",
</strong><strong>        "value": "password1234",
</strong>        "is_hidden": false
      }
    ],
<strong>    <a data-footnote-ref href="#user-content-fn-22">"webhook_on_ready"</a>: {
</strong>      "url": "https://my-webhook.com"
    },
<strong>    <a data-footnote-ref href="#user-content-fn-23">"webhook_on_error"</a>: {
</strong>      "url": "https://my-webhook.com"
    },
<strong>    <a data-footnote-ref href="#user-content-fn-20">"webhook_on_terminated"</a>: {
</strong>      "url": "https://my-webhook.com"
    }
  },
  "minimum_active_instances": 1
}
</code></pre>

{% endtab %}

{% tab title="❄️ Mesh Group" %}
One policy per group of servers. Game backend starts a primary node, which spawns replicas. Each node reads injected mesh group ID and search other nodes to network with.

<pre class="language-json" data-title=""><code class="lang-json">{
<strong>  "name": "sb-meshgroup-pqyt8sxcb",
</strong><strong>  <a data-footnote-ref href="#user-content-fn-12">"filter"</a>: "metadata.policy_name eq 'sb-meshgroup-pqyt8sxcb'",
</strong>  "deployment_request": {
    "private_host_ids": [],
    "application": <a data-footnote-ref href="#user-content-fn-13">"my-game-server"</a>,
    "version": <a data-footnote-ref href="#user-content-fn-14">"2024.01.30-16.23.00-UTC"</a>,
    "users": [
      {
        "user_type": "geo_coordinates",
        "<a data-footnote-ref href="#user-content-fn-15">user_data</a>": {
<strong>          "latitude": 41.881832,
</strong><strong>          "longitude": -87.623177
</strong>        }
      }
    ],
<strong>    "tags": ["sb-meshgroup-pqyt8sxcb"],
</strong>    "environment_variables": [
      {
<strong>        "key": "SB_SCALING_POLICY_NAME",
</strong><strong>        "value": "sb-meshgroup-pqyt8sxcb",
</strong>        "is_hidden": false
      },
      {
<strong>        "key": "SB_MESH_GROUP_ID",
</strong><strong>        "value": "pqyt8sxcb",
</strong>        "is_hidden": false
      }
    ],
<strong>    <a data-footnote-ref href="#user-content-fn-22">"webhook_on_ready"</a>: {
</strong>      "url": "https://my-webhook.com"
    },
<strong>    <a data-footnote-ref href="#user-content-fn-24">"webhook_on_terminated"</a>: {
</strong>      "url": "https://my-webhook.com"
    }
  },
<strong>  <a data-footnote-ref href="#user-content-fn-25">"minimum_active_instances"</a>: 9
</strong>}
</code></pre>

{% endtab %}
{% endtabs %}

## ⚙️ Configuration

Server Browser API is generated from a JSON configuration specified when you create a new (or quick-restart) Server Browser. You may specify server and slot expiration, and custom metadata:

{% tabs %}
{% tab title="🍀 Simple Example" %}

<pre class="language-json" data-title="sb-simple-example-v1-0-0.json"><code class="lang-json">{
	"version": "1.0.0",
	"server_instances": {
		"expiration_period": "1m",
		"<a data-footnote-ref href="#user-content-fn-26">indices</a>": {
			"policy_name": "string",
			"name": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-26">indices</a>": {}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "1m"
	}
}
</code></pre>

{% endtab %}

{% tab title="🎈 Social Games" %}

<pre class="language-json" data-title="sb-social-example-v1-0-0.json"><code class="lang-json">{
	"version": "1.0.0",
	"server_instances": {
		"expiration_period": "15s",
		"<a data-footnote-ref href="#user-content-fn-26">indices</a>": {
			"policy_name": "string",
			"name": "string",
			"third_party_id": "string",
			"level": "string",
			"mode": "string",
			"difficulty": "string",
			"seed": "string",
			"max_players": "int",
			"app_version": "string",
			"location.city": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-26">indices</a>": {
			"third_party_id": "string",
			"max_players": "int",
			"avg_latency": "int",
			"player_ids": "string"
		}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "30s"
	}
}
</code></pre>

{% endtab %}

{% tab title="🤝 Cooperative Games" %}

<pre class="language-json" data-title="sb-cooperative-example-v1-0-0.json"><code class="lang-json">{
	"version": "1.0.0",
	"server_instances": {
		"expiration_period": "15s",
		"<a data-footnote-ref href="#user-content-fn-26">indices</a>": {
			"policy_name": "string",
			"name": "string",
			"third_party_id": "string",
			"level": "string",
			"mode": "string",
			"difficulty": "string",
			"avg_rank": "int",
			"max_players": "int",
			"app_version": "string",
			"tags": "string",
			"match_id": "string",
			"location.city": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-26">indices</a>": {
			"third_party_id": "string",
			"max_players": "int",
			"player_ids": "string"
		}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "30s"
	}
}
</code></pre>

{% endtab %}

{% tab title="⚔️ Competitive Games" %}

<pre class="language-json" data-title="sb-competitive-example-v1-0-0.json"><code class="lang-json">{
	"version": "1.0.0",
	"server_instances": {
		"expiration_period": "15s",
		"<a data-footnote-ref href="#user-content-fn-26">indices</a>": {
			"policy_name": "string",
			"name": "string",
			"third_party_id": "string",
			"avg_rank": "int",
			"max_players": "int",
			"is_ranked": "bool",
			"app_version": "string",
			"cpu_frequency": "int",
			"match_id": "string",
			"location.city": "string"
		}
	},
	"server_instance_slots": {
		"<a data-footnote-ref href="#user-content-fn-26">indices</a>": {
			"third_party_id": "string",
			"max_players": "int",
			"avg_rank": "int",
			"avg_latency": "int"
		}
	},
	"seat_reservations": {
		"expiration_period": "30s"
	},
	"scaling_policies": {
		"monitoring_interval": "10s",
		"deployment_registration_period": "15s"
	}
}
</code></pre>

{% endtab %}
{% endtabs %}

{% hint style="info" %}
For best performance, avoid specifying indices for metadata not used for filtering or sorting. Non-indexed params can be still set and read with server instance or slot details API methods, see [#api](#api "mention").
{% endhint %}

## ☁️ Hosting Cluster

Server Browser is conveniently hosted and managed 24/7 around the clock by Edgegap.

Choose a hosting option best suited for your goal:

* **Free Cluster (shared)** to test all features and explore synergies with your design,
  * shuts down after 3 hours automatically, requiring restart to continue testing.
* **Private Cluster** **(dedicated)** to ensure a stable environment for your production needs,
  * pick your region and get 24/7 support for live games to release with confidence.

### Private Cluster Tiers

We currently offer [3 private cluster tiers](https://edgegap.com/resources/pricing#managed-infrastructure) to cater to everybody’s needs:

<table><thead><tr><th width="160">Tier</th><th align="right">Hobbyist Tier</th><th align="right">Studio Tier</th><th align="right">Enterprise Tier</th></tr></thead><tbody><tr><td>Best Suited For</td><td align="right">enthusiasts,<br>solo developers</td><td align="right">commercial releases</td><td align="right">high-traffic launches</td></tr><tr><td>Resources</td><td align="right">1 vCPU + 2GB RAM</td><td align="right">6 vCPU + 12GB RAM</td><td align="right">18 vCPU + 48GB RAM</td></tr><tr><td>Redundancy</td><td align="right">1x virtual node</td><td align="right">3x virtual nodes</td><td align="right">3x virtual nodes</td></tr><tr><td>Rate Limit (req/s)</td><td align="right">200</td><td align="right">750</td><td align="right">2,000</td></tr><tr><td>Price, hourly</td><td align="right">$0.0312</td><td align="right"> $0.146</td><td align="right">$0.548</td></tr><tr><td><strong>Price, 30 days</strong><br>(nonstop usage)</td><td align="right"><strong>$22.464</strong></td><td align="right"><strong>$105.12</strong></td><td align="right"><strong>$394.56</strong></td></tr></tbody></table>

Upgrade to a private cluster with one click to benefit from highly available hosting maintained by Edgegap team with 24/7 live support for publicly released games.

Resource requirements for your instance will depend on factors:

* **number of players** - more players result in more API requests,
* **number of requests per player** - faster retries increase service load and consume resources,
* **number of servers** - more servers result in more data stored and more API requests,
* **client retry fallback logic** - retrying with jittered backoff helps spread traffic burst peaks,
* **average match duration** - shorter sessions require more frequent server browser interaction.

{% hint style="info" %}
Our clusters use cloud machines featuring AMD/Intel CPUs with clock speed 2.4 - 3.2 GHz.
{% endhint %}

## 📗 API

**Consider using our SDK for** [**Unreal Engine**](/unreal-engine/developer-tools) **or** [**Unity**](/unity/server-browser) **to get started quickly with prebuilt examples.**

Game Clients and Dedicated Servers send API requests throughout their lifecycle to Server Browser.

{% hint style="info" %}
Unity/Android - consider [using raw string interpolation](https://www.c-sharpcorner.com/article/convert-string-to-json-in-c-sharp/) to prevent code stripping of hardcoded JSONs.
{% endhint %}

{% hint style="success" %}
**Swagger Web UI**: deploying your service will generate an openAPI specification and a convenient web UI. Open the URL in your browser to view and test all API endpoints, and to review payload examples.
{% endhint %}

{% file src="/files/hwdf2pmv3teYfx1dx5vH" %}

Import API specification to [Scalar API Web Client](https://client.scalar.com/workspace/default/request/default) or [Swagger Editor](https://editor.swagger.io/) to inspect details.

### Pagination

**Server Browser provides cursor pagination to fetch filtered data incrementally in a specific order.** This approach requires sending a cursor (starting point) and a page size (number of response items) whenever fetching more results, as opposed to the traditional limit-offset pagination.

{% hint style="info" %}
Combined with our proprietary database indexing system developed for game server metadata, cursor pagination provides a fast, consistent, and flexible user experience for filtering highly dynamic data.
{% endhint %}

Our goal is for users to find a suitable server on the first page. For best experience, we recommend showing cached results for previous pages, and only refreshing results when the user clicks Search.

## 🔖 Changelog

#### Semantic Versioning

Our developer tools and managed services use official [Semantic Versioning](https://semver.org/), indicating which updates are ✅ safe (minor, patch) and which may contain ⚠️ breaking changes (major).

**Once a version is released, it will never be modified/changed**.

{% hint style="info" %}
**The latest version of server browser is `1.0.0`** . Keep an eye out for [updates and announcements](/docs/release-notes).
{% endhint %}

[^1]: third party player identifiers

[^2]: example value

[^3]: equals

[^4]: not equals

[^5]: lower than

[^6]: lower than or equal

[^7]: greater than

[^8]: greater than or equal

[^9]: see Configuration

[^10]: see Example Policies

[^11]: use active caching to reduce deployment times easily

[^12]: * fixed capacity
    * assumes instances supply policy name in metadata from injected variable

[^13]: replace with your own application name

[^14]: replace with your own application version

[^15]: Chicago coordinates

[^16]: * deploys when less than 10 joinable instances found
    * assumes instances supply policy name in metadata from injected variable

[^17]: we expect at least 10 deployments in the Chicago region

[^18]: * deploys when less than 3 instances with 5 or less joinable seats are found
    * assumes instances supply policy name in metadata from injected variable

[^19]: prefer private fleet if capacity available

[^20]: notify game backend when server restarts

[^21]: * does not monitor capacity
    * assumes instances supply policy name in metadata from injected variable

[^22]: notify game backend when ready

[^23]: notify game backend when deployment fails

[^24]: notify game backend when stopped

[^25]: 3x3 grid = 9 servers per world

[^26]: indices contain your custom metadata parameters used for filtering or sorting


# Advanced Features

Here, you will learn how to get more out of your Edgegap account. Understanding some server concepts in-depth can make your development process less prone to human error, more cost effective, and help customize to your specific needs.

{% hint style="success" %}
Before you send us your questions please use the search bar in this documentation and on our [Community Discord](https://discord.gg/NgCnkHbsGp). Posting your questions, suggestions, bug reports, or otherwise participating in community support will be rewarded with experience points!
{% endhint %}


# Container Registry

Now that you know how to use our platform, you need to containerize your game server. You will need a place to store your container images to be accessible when deploying your game servers.

That is why we created the Edgegap Container Registry. It is a privately managed container repository that works much like standard public container registries, but with the added benefit of increased security, vulnerability scans and other goodies! With high availability and dashboard integration, you will spend less time learning new tools, and your servers will always be ready to go.

### Getting your credentials

To push images, you will first need to authenticate to our registry, and for that, you need credentials. Find the `Container Registry` tab in the `Service` section to request your credentials in the sidebar. From there, you can request access to our registry.

<figure><img src="/files/1TrQLEAjTvb9dXqX5V2e" alt=""><figcaption></figcaption></figure>

In the box at the bottom of the screen, you will find everything you need to connect to the registry and push your container images.

<figure><img src="/files/c0zJkjNPZYiI9snX6hJr" alt=""><figcaption></figcaption></figure>

| Field    | Description                                                                                    |
| -------- | ---------------------------------------------------------------------------------------------- |
| URL      | The URL of our container registry. You will need to use it when logging in and pushing images. |
| Project  | The name of your assigned project. You must include it in your image repository path.          |
| Username | The username to use when logging in to the registry.                                           |
| Token    | The password to use when logging in to the registry.                                           |

You can now login to the repository using your tool of choice:

#### Linux

```bash
# enter your password when prompted
docker login -u '<REPOSITORY_USERNAME>' registry.edgegap.com
```

#### cmd

```bash
# enter your password when prompted
docker login -u <REPOSITORY_USERNAME> registry.edgegap.com
```

#### Powershell

```bash
# enter your password when prompted
docker login -u '<REPOSITORY_USERNAME>' registry.edgegap.com
```

### Docker login Warning

```
WARNING! Your password will be stored unencrypted in /home/user/.docker/config.json.
```

You can configure the credentials store by following [this setup](https://docs.docker.com/engine/reference/commandline/login/#credentials-store). Remember that this warning is normal behaviour from Docker.

It's **important** that you understand that your robot username, and the token for your private repository are encoded in base64 in your machine at this path `$HOME/.docker/config.json`. Anybody who has access to this file can easily decrypt your robot account token with a base64 decoder. Ensure that the environment `docker login registry.edgegap.com` you use is safe and that the access permissions to this file are restricted.

To remove your credentials from this file, you can easily use `docker logout registry.edgegap.com` each time you push and pull your private images.

### Pushing an image to the registry

If you need help containerizing your project, see [our documentation on this topic](/docs/tools-and-integrations/container/docker) to get you started.

First, you need to add a tag to your image corresponding to the registry. Then, since you are logged in to the registry, all you need to do is push your image to the URL and project found on the page:

```bash
docker image tag <IMAGE_NAME>:<TAG> registry.edgegap.com/<PROJECT_NAME>/<IMAGE_NAME>:<TAG>
docker push registry.edgegap.com/<PROJECT_NAME>/<IMAGE_NAME>:<TAG>
```

You may push as many images as your storage permits and separate them in different repositories as you see fit. However, you should read the "App templates" section before pushing too many images!

You will then find your repositories on the same page upon refreshing.

### Tagging the same build artifact multiple times (pushing an image duplicate)

If you happen to push the same build artifact twice, if the tag value is the same no change will be made.

If you happen to push the same build artifact twice, with two different tag values, no additional storage space will be consumed and our registry will simply create the new tag pointing to the same build artifact.

### Managing your images

You may see your repositories by going to the same page after you have pushed at least one image.

By clicking on one of those repositories, you can see the images it contains, grouped by artifact. If you tagged the same image with two different tags and pushed those to the registry, you will see them as the same artifact.

<figure><img src="/files/aGrJzyR3MZJ4jaexxbme" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/QOVVNS4wPlHX5TfUDrMv" alt=""><figcaption></figcaption></figure>

The images tagged `1.0.1` and `1.0.2` in the picture above have the same artifact. The image tagged `1.0.3`, however, has a different artifact.

{% hint style="warning" %}
If you delete an artifact, you will also delete its associated tags. The confirmation dialogue will list all the tags that this action would delete to ensure you do not accidentally delete needed resources.
{% endhint %}

### Delete build artifacts via API

If the dashboard UI is too slow for you, or you need an automated continuous integration pipeline that would delete your outdated build artifacts automatically to keep your registry usage within limits, consider using our Container Registry API. Our [Container Registry API](/docs/api) allows listing all tags for a given app (image), as well as deleting individual tags. Since each image artifact (build) may have multiple tags associated at the same time (such as `v1.1.0`, `dev`, `latest`, or more), you will need to delete all tags associated with a specific artifact in order to free up space in your Edgegap Container Registry.

### Requesting more storage

When your request for access to the registry has been approved, you will be given a certain amount of storage space in the registry.

You may request additional space by clicking on the "Request more storage" button next to the space usage indicator.

<figure><img src="/files/gVz3voT1TJe9fzJSP9Yw" alt=""><figcaption></figcaption></figure>

When a staff member has approved your request, you will have more space to push your images.

### Next steps

Learn how to containerize your application with our guide: [Getting started with Docker](/docs/tools-and-integrations/container/docker).


# Managed Clusters

Learn about Managed Clusters and get started quickly with custom backend solutions.

## ✔️ Introduction

Managed Clusters make hosting self-managed game services and game backend easy and fast. You prepare the service image and we provide a high-availability, resilient cloud environment to run them:

* player authentication,
* data storage - accounts, progression, inventory, rewards, ...
* social services - chat, clans, leaderboards, tournaments, ...
* custom matchmaking - using [#advanced-matchmaker](#advanced-matchmaker "mention"), [#nakama-by-heroic-labs](#nakama-by-heroic-labs "mention"), ...
* serverless compute - managed [functions as a service](https://github.com/openfaas/faas) (alt. cloudscript, lambda), ...

{% hint style="success" %}
See [Matchmaking](/learn/matchmaking) and [Server Browser](/learn/server-browser) to get started with game client integration.
{% endhint %}

Private clusters ensure your services have **dedicated compute to serve your players 24/7**.

We currently offer [3 private cluster tiers](https://edgegap.com/resources/pricing#managed-infrastructure) to cater to everybody’s needs:

<table><thead><tr><th width="160">Tier</th><th align="right">Hobbyist Tier</th><th align="right">Studio Tier</th><th align="right">Enterprise Tier</th></tr></thead><tbody><tr><td>Best Suited For</td><td align="right">enthusiasts,<br>solo developers</td><td align="right">commercial releases</td><td align="right">high-traffic launches</td></tr><tr><td>Resources</td><td align="right">1 vCPU + 2GB RAM</td><td align="right">6 vCPU + 12GB RAM</td><td align="right">18 vCPU + 48GB RAM</td></tr><tr><td>Redundancy</td><td align="right">1x virtual node</td><td align="right">3x virtual nodes</td><td align="right">3x virtual nodes</td></tr><tr><td>Rate Limit (req/s)</td><td align="right">200</td><td align="right">750</td><td align="right">2,000</td></tr><tr><td>Price, hourly</td><td align="right">$0.0312</td><td align="right"> $0.146</td><td align="right">$0.548</td></tr><tr><td><strong>Price, 30 days</strong><br>(nonstop usage)</td><td align="right"><strong>$22.464</strong></td><td align="right"><strong>$105.12</strong></td><td align="right"><strong>$394.56</strong></td></tr></tbody></table>

{% hint style="info" %}
Our cluster machines use AMD/Intel CPUs with clock speed 2.4 - 3.2 GHz. Reach out on [Community Discord](https://discord.gg/MmJf8fWjnt) to coordinate load tests and to ensure your server has sufficient resources available.
{% endhint %}

## 🛠️ Developer Tools

If you see an opportunity for improvement, please let us know in our [Community Discord](https://discord.gg/NgCnkHbsGp).

We hope you will enjoy a smooth experience. 🚀

### Docker

To help make your server reliable, we use [Docker](https://www.docker.com/) - virtualization software to ensuring that all of your server code dependencies down to the operating system level are going to be always exactly the same, no matter how or where the server is launched.

{% hint style="info" %}
We recommend watching ["Never install locally" (video)](https://www.youtube.com/watch?v=J0NuOlA2xDc\&ab_channel=Coderized). **You DON'T need to use Dockerhub with Docker**.  Docker ≠ Dockerhub. Think of Docker as a programming engine and Dockerhub as it’s App Store.
{% endhint %}

### Kubernetes (K8s)

[Kubernetes](https://kubernetes.io/docs/concepts/overview/), also known as K8s, is an open source system for automating deployment, scaling, and management of containerized applications (Docker Images). It groups containers that make up an application into logical units for easy management and discovery.

Edgegap Managed Clusters provide a Kubernetes API for administration purposes.

### K8s Lens

With over 1 million users, [K8s Lens](https://k8slens.dev/) is the most popular Kubernetes IDE in the world. Connect to clusters, explore, gain insights, learn and take an action when needed. Lens provides all the information from your workloads and resources in real-time, always in the right context.

Edgegap Cluster Kubernetes API can be used through Lens or other Kubernetes IDEs.

### Helm Package Manager

[Helm](https://helm.sh/) is the best way to find, share, and use software built for Kubernetes. Helm helps you manage Kubernetes applications - Helm Charts help you define, install, and upgrade even the most complex Kubernetes application. Charts are easy to create, version, share, and publish - so start using Helm and stop the copy-and-paste.

[Installing Helm CLI](https://helm.sh/docs/intro/install/) provides developers with a simple interface to manage their cluster packages.

## 🚀 Getting Started

☑️ [Registered for your free Edgegap account](https://app.edgegap.com/auth/register) and upgrade to pay as you go tier to unlock Clusters.

☑️ Navigate to [Managed Clusters](https://app.edgegap.com/cluster-management/clusters/list) page.

☑️ Click on **Create Cluster** first, then input:

* **Label** for your cluster to find it later easily,
* **Cluster Size -** see [#introduction](#introduction "mention").

{% hint style="danger" %}
**We strongly recommend creating separate clusters for your dev & production environments.**
{% endhint %}

☑️ Review estimated cost and click **Create Cluster** to start your new cluster.

☑️ Once the cluster is ready, **click Kubeconfig to download your configuration and credentials** for connecting and administrating your new cluster.

☑️ [Move your kubeconfig file](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) for `kubectl` to find it.

☑️ Lens users: [import your kubeconfig file](https://docs.k8slens.dev/getting-started/add-cluster/#specify-kubeconfig-files).

☑️ Test your cluster connection with command `kubectl get nodes` :

```bash
kubectl get nodes
NAME                            STATUS   ROLES    AGE    VERSION
lke334087-533013-294dcfe70000   Ready    <none>   10m   v1.31.0
lke334087-533013-4e69edc10000   Ready    <none>   10m   v1.31.0
lke334087-533013-50bf39880000   Ready    <none>   10m   v1.31.0
```

🙌 Congratulations, you’ve completed Managed Cluster setup! You may now install your services.

## 📦 Nakama by Heroic Labs

{% hint style="success" %}
See [Managed Clusters](/learn/advanced-features/managed-clusters#getting-started) before setting up your services on Managed Clusters.
{% endhint %}

{% hint style="info" %}
Integrate Edgegap with [Nakama plugin](https://github.com/edgegap/nakama-edgegap) + [Unity plugin](https://github.com/edgegap/edgegap-server-nakama-plugin-unity)! [Reach out for other platforms/features.](https://discord.gg/NgCnkHbsGp)
{% endhint %}

Follow these steps to host your own [Nakama Game Backend](https://heroiclabs.com/docs/nakama/getting-started/) on Managed Clusters:

☑️ Install a **Certificate Manager** to support usage of HTTPS requests from game clients:

```bash
helm repo add jetstack https://charts.jetstack.io --force-update;
helm upgrade --install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --version v1.16.1 --set crds.enabled=true;
```

☑️ Lens: verify installation in Services / Network section, `cert-manager` should be running.

☑️ Write a **Cluster Issuer** file, remember to replace `<YOUR_EMAIL>` below:

<pre class="language-yaml"><code class="lang-yaml">apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
  namespace: cert-manager
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: <a data-footnote-ref href="#user-content-fn-1">&#x3C;YOUR_EMAIL></a>
    privateKeySecretRef:
      name: <a data-footnote-ref href="#user-content-fn-2">letsencrypt-staging</a>
    solvers:
    - http01:
        ingress:
          class: nginx
          
</code></pre>

{% hint style="danger" %}
Use `letsencrypt-prod` for your production cluster `privateKeySecretRef`.
{% endhint %}

☑️ Create a **Cluster Issuer** in command line:

```bash
kubectl create -f <FILE_PATH>/issuer.yaml
```

☑️ Lens: verify installation in Custom Resources section / `cert-manager.io` - cluster issuer created.

☑️ Install the **nginx Ingress** for receiving client requests and passing them to services in cluster:

```
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace
```

☑️ Lens: verify installation in Services / Network section, `nginx` should be running.

☑️ Create a **DNS record type A** in your DNS provider (e.g. [Cloudflare](https://developers.cloudflare.com/dns/get-started/)), note the URL for later. Your **external IP for the DNS record** can be found in Lens under Services / `ingress-nginx-controller` .

☑️ Verify your DNS is set up correctly by performing a lookup [using DNSchecker](https://dnschecker.org/ns-lookup.php).

☑️ Create file named `values.yaml` with contents (use your own values):

<pre class="language-yaml"><code class="lang-yaml">isProductionEnvironment: true

# The external hostname for the Nakama server - DNS record from last step
externalHostName: <a data-footnote-ref href="#user-content-fn-3">&#x3C;DNS_A_RECORD_URL></a>

nakama:
  # The version of Nakama to deploy.
  # See https://hub.docker.com/r/heroiclabs/nakama/tags for available versions.
  version: 3.26.0
  # Username and password for the Nakama console
  username: <a data-footnote-ref href="#user-content-fn-4">&#x3C;USERNAME></a>
  password: <a data-footnote-ref href="#user-content-fn-5">&#x3C;PASSWORD></a>
</code></pre>

{% hint style="danger" %}
**Replace \<VALUES> above with your own values** in the file above.
{% endhint %}

☑️ Deploy Nakama helm chart:

```bash
helm upgrade --install \
  --namespace nakama --create-namespace -f <FILE_PATH>/values.yaml \
  --version 1.0.0 <RELEASE_NAME> oci://registry-1.docker.io/edgegap/heroiclabs-nakama
```

☑️ Lens: verify installation in Workloads / Deployments section, `nakama` should be running.

✅ **Connect to your Nakama Console** with URL and credentials from `values.yaml` file.

🙌 Congratulations, you’ve completed self-hosted Nakama Game Backend setup!

### Service Updates

Follow these steps to update your service hosted in the Managed Cluster:

☑️ Update your `value.yaml` file with new files.

☑️ Update your helm chart using this command:

```bash
helm upgrade --reuse-values \
  --namespace nakama -f <FILE_PATH>/values.yaml \
  --version 1.0.0 <RELEASE_NAME> oci://registry-1.docker.io/edgegap/heroiclabs-nakama
```

☑️ Reload your changes by closing the updated pods, causing the new helm chart to be used after we automatically restart the pods.

{% hint style="warning" %}
This **update will cause a short downtime** while the pods restart.
{% endhint %}

🙌 Congratulations, you’ve completed Nakama Cluster update!

## 👷 Advanced Matchmaker

{% hint style="success" %}
See [Managed Clusters](/learn/advanced-features/managed-clusters#getting-started) before setting up your services on Managed Clusters.
{% endhint %}

Follow these steps to host your [OpenMatch](https://open-match.dev/site/) on a Managed Cluster.

☑️ Install a **Certificate Manager** to support usage of HTTPS requests from game clients:

```bash
helm repo add jetstack https://charts.jetstack.io --force-update;
helm upgrade --install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --version v1.16.1 --set crds.enabled=true;
```

☑️ Lens: verify installation in Services / Network section, `cert-manager` should be running.

☑️ Write a **Cluster Issuer** file, remember to replace `<YOUR_EMAIL>` below:

<pre class="language-yaml"><code class="lang-yaml">apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
  namespace: cert-manager
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: <a data-footnote-ref href="#user-content-fn-1">&#x3C;YOUR_EMAIL></a>
    privateKeySecretRef:
      name: <a data-footnote-ref href="#user-content-fn-2">letsencrypt-staging</a>
    solvers:
    - http01:
        ingress:
          class: nginx
          
</code></pre>

{% hint style="danger" %}
Use `letsencrypt-prod` for your production cluster `privateKeySecretRef`.
{% endhint %}

☑️ Create a **Cluster Issuer** in command line:

```bash
kubectl create -f <FILE_PATH>/issuer.yaml
```

☑️ Lens: verify installation in Custom Resources section / `cert-manager.io` - cluster issuer created.

☑️ Install the **nginx Ingress** for receiving client requests and passing them to services in cluster:

```
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace
```

☑️ Lens: verify installation in Services / Network section, `nginx` should be running.

☑️ Create a DNS record type A in your DNS provider (e.g. [Cloudflare](https://developers.cloudflare.com/dns/get-started/)), note the URL for later. Your **external IP for the DNS record** can be found in Lens under Services / `ingress-nginx-controller` .

☑️ Verify your DNS is set up correctly by performing a lookup [using DNSchecker](https://dnschecker.org/ns-lookup.php).

☑️ Create file named `values.yaml` with contents (use your own values):

```yaml
isProductionEnvironment: false

director:
  credential:
    registry: <YOUR_DIRECTOR_REGISTRY>
    username: <YOUR_DIRECTOR_REGISTRY_USERNAME>
    password: <YOUR_DIRECTOR_REGISTRY_PASSWORD>
  image: <DIRECTOR_IMAGE>
  env: {
    "KEY": "VALUE"
  }

mmf:
  credential:
    registry: <MATCHMAKER_FUNCTION_REGISTRY>
    username: <MATCHMAKER_FUNCTION_REGISTRY_USERNAME>
    password: <MATCHMAKER_FUNCTION_REGISTRY_PASSWORD>
  image: <MATCHMAKER_FUNCTION_IMAGE>
  env: {
    "KEY": "VALUE"
  }

frontend:
  credential:
    registry: <FRONTEND_REGISTRY>
    username: <FRONTEND_REGISTRY_USERNAME>
    password: <FRONTEND_REGISTRY_PASSWORD>
  externalHostName: <YOUR_CLOUDFLARE_HOST_NAME> # e.g. exemple.test.com
  image: <FRONTEND_IMAGE>
  env: {
    "KEY": "VALUE"
  }

# Global configurations that are visible to all subcharts
global:
  kubernetes:
    resources:
      requests:
        memory: 100Mi
        cpu: 100m
      limits:
        memory: 100Mi
        cpu: 100m
```

{% hint style="danger" %}
**Replace \<VALUES> above with your own values** in the file above.
{% endhint %}

☑️ Add **Edgegap repository** to your list of repositories:

```bash
helm repo add edgegap-public https://registry.edgegap.com/chartrepo/edgegap-public
```

☑️ Deploy advanced matchmaker helm chart:

```bash
helm upgrade --install \
  --namespace matchmaker --create-namespace -f <FILE_PATH>/values.yaml \
  --version 1.0.1 <RELEASE_NAME> edgegap-public/open-match-edgegap
```

🙌 Congratulations, you’ve completed Advanced Matchmaker setup!

### Service Updates

Follow these steps to update your service hosted in the Managed Cluster:

☑️ Update your `value.yaml` file with new files.

☑️ Update your helm chart using this command:

```bash
helm upgrade --reuse-values \
  --namespace matchmaker -f <FILE_PATH>/values.yaml \
  --version 1.0.1 <RELEASE_NAME> edgegap-public/open-match-edgegap
```

☑️ Reload your changes by closing the updated pods (director, mmf, frontend), causing the new helm chart to be used after we automatically restart the pods.

{% hint style="warning" %}
This **update will cause a short downtime** while the pods restart.
{% endhint %}

🙌 Congratulations, you’ve completed Advanced Matchmaker update!

### Continuous Deployment

Automate updating your services by adding this shell script to your deployment pipeline:

```bash
#!/bin/bash

RELEASE_NAME="<RELEASE_NAME>"
NAMESPACE="matchmaker"  # Change this if you changed the namespace.

helm upgrade --reuse-values -f <FILE_PATH>/value.yaml --namespace $NAMESPACE --version 1.0.1 $RELEASE_NAME edgegap-public/open-match-edgegap

echo "Installing redis-tools"
apt-get update
apt-get install -y redis-tools

DIRECTOR_DEPLOYMENT_NAME="$RELEASE_NAME-director"
MMF_DEPLOYMENT_NAME="$RELEASE_NAME-mmf"
CUSTOM_FRONTEND_DEPLOYMENT_NAME="$RELEASE_NAME-custom-frontend"
REDIS_HOST="$RELEASE_NAME-redis-master"

declare -A replicas

# For each deployment (director, mmf, custom-frontend) stop the pods
for deployment in $DIRECTOR_DEPLOYMENT_NAME $MMF_DEPLOYMENT_NAME $CUSTOM_FRONTEND_DEPLOYMENT_NAME
do
  echo "Stopping pods for deployment: $deployment"
  replicas[$deployment]=$(kubectl get deployment $deployment -o=jsonpath='{.spec.replicas}' --namespace $NAMESPACE)
  kubectl scale deployment/$deployment --replicas=0 --namespace $NAMESPACE
done

# Wait until the pods are terminated
for deployment in $DIRECTOR_DEPLOYMENT_NAME $MMF_DEPLOYMENT_NAME $CUSTOM_FRONTEND_DEPLOYMENT_NAME
do

  echo "Waiting for pods to be terminated for deployment: $deployment"
  kubectl wait --for=delete pod -l app=$deployment --timeout=60s --namespace $NAMESPACE

  # Check if the wait command was successful. If not, exit the script
  if [ $? -ne 0 ]; then
    echo "Failed to wait for pods to be terminated for deployment: $deployment"
    exit 1
  fi
done

# Clean up redis database
echo "Cleaning up redis database"
redis-cli -h $REDIS_HOST flushall

# For each deployment (director, mmf, custom-frontend) rescale the pods to their original count
for deployment in $DIRECTOR_DEPLOYMENT_NAME $MMF_DEPLOYMENT_NAME $CUSTOM_FRONTEND_DEPLOYMENT_NAME
do
  echo "Rescaling pods to ${replicas[$deployment]} for deployment: $deployment"
  kubectl scale deployment/$deployment --replicas=${replicas[$deployment]} --namespace $NAMESPACE
done
```

{% hint style="warning" %}
This **update will cause a short downtime** while the pods restart.
{% endhint %}

### Letsencrypt Certificate Validation (C#)

For some clients, the recommended Letsencrypt certificate validation may fail with error:

```bash
Curl error 60: Cert verify failed. Certificate has expired. UnityTls error code: 7
```

{% hint style="success" %}
Updating your operating system may resolve issues with outdated root certificate authority.
{% endhint %}

As a last resort, game clients may implement a custom certificate handler function:

````csharp
```csharp
public class CustomCertificateHandler : CertificateHandler
{
  private readonly string EXPECTED_CERT = "-----BEGIN CERTIFICATE-----<key>-----END CERTIFICATE-----\r\n";
  protected override bool ValidateCertificate(byte[] certificateData)
  {
    X509Certificate2 certificate = new X509Certificate2(certificateData);
    X509Certificate2 expectedCert = new X509Certificate2(Encoding.ASCII.GetBytes(EXPECTED_CERT));

    using (SHA256 sha256 = SHA256.Create())
    {
      Debug.Log("certificate.Thumbprint: " + certificate.Thumbprint);
      Debug.Log("expectedCert.Thumbprint: " + expectedCert.Thumbprint);

      return certificate.Thumbprint == expectedCert.Thumbprint;
    }
  }
}
```
````

Usage:

```csharp
UnityWebRequest request = UnityWebRequest.Get(...);
request.certificateHandler = new BypassCertificateHandler();
request.SendWebRequest();
request.certificateHandler.Dispose();
```

We recommend storing the `EXPECTED_CERT` value in your own file storage, and retrieving it on runtime, so you can update it without releasing a game client update.

## 🟢 Operations and Observability

### Cluster Tier Changes

Prepare for success and optimize after launch, so you don’t block your players on release day.

{% hint style="warning" %}
&#x20;Changing cluster size requires stopping your cluster. See [blue/green deployment](https://circleci.com/blog/canary-vs-blue-green-downtime/) for zero downtime updates.
{% endhint %}

### Support and Future Updates

**Your success is our priority.** If you'd like to send custom requests, ask for missing critical features , or express any thoughts, [please reach out in our Community Discord](https://discord.gg/MmJf8fWjnt).

[^1]: replace with your email

[^2]: use letsencrypt-prod for production

[^3]: DNS record from last step

[^4]: choose your own admin username

[^5]: choose a secure password for admin


# China Gateway

Please [contact us](mailto:china@edgegap.com) to enable deployments in China, as your application requires regulatory, country-specific compliance in this market.


# Relays (P2P)

{% hint style="warning" %}
This section of the documentation is a work in progress and is subject to change.

Don't hesitate to ask questions or make requests via our [Discord](https://discord.com/invite/NgCnkHbsGp) channel. We are here to assist you!
{% endhint %}

The Edgegap Distributed Relays benefit from our distributed cloud network, which includes hundreds of locations. This ensures that your players are always playing on the best available relay, thanks to our patented real-time telemetry decision technology.

Implementing relays can indeed be a complex task, which is why we have developed a solution that takes care of the technical aspects so you don't have to. Our solution simplifies the process of setting up and managing relays, allowing you to focus on integrating them seamlessly into your game or application without the need for extensive technical expertise.

***

### Why Do I Need a Relay in a P2P Game

A Relay server can be a valuable tool for P2P game developers. In P2P games, players connect directly to each other without the need for a central server. However, this approach can present some challenges, such as:

#### **Firewall restrictions**

Firewalls can block P2P connections between devices, making it difficult for players to connect to each other.

#### **Strict NAT**

A Strict NAT can have a significant impact on a player's ability to participate in peer-to-peer (P2P) games. P2P games rely on direct connections between players' devices to facilitate communication and gameplay, but a Strict NAT can limit or even block these connections. This can result in increased latency, dropped connections, and difficulty joining or hosting games.

<figure><img src="/files/xRo5XWU81uGTkdyALmM9" alt=""><figcaption></figcaption></figure>

A Relay server can help overcome these challenges by acting as an intermediary between players. When a player wants to connect to another player, their connection is first routed through the Relay server. This allows the Relay server to handle NAT traversal and firewall restrictions, making it easier for players to connect to each other.

Relay servers can also help improve the overall performance of P2P games by reducing the load on player devices. By handling some of the network traffic between players, the Relay server can help reduce latency and improve the overall gameplay experience.

In summary, a Relay server can be a valuable tool for P2P game developers, helping to overcome NAT traversal and firewall restrictions, and improving the overall performance of the game.

***

### Server vs Client in P2P

In a peer-to-peer (P2P) setup for a multiplayer game, one player must act as the game server since there is no dedicated server to manage the game for all players. This player, referred to as the "server," is responsible for managing the game world and synchronizing all information between players. The remaining players (Client" alt="">

will connect to the server directly.

***

### Getting Started

#### **Implement Some Sort of Lobby/Matchmaker**

You will need to implement some form of lobby or matchmaker to create relay sessions for your players. Don't worry, initially you won't need a fully functional matchmaker, but rather something that can automate your API requests without exposing your token in the game client. Consider using [Managed Clusters](/learn/advanced-features/managed-clusters#nakama-by-heroic-labs) on Edgegap clusters.

#### **Integration with the Edgegap API**

Once you have completed the previous step, you will need to primarily perform two straightforward actions using the Edgegap API: creating relay sessions and deleting relay sessions. Your lobby/matchmaker should be responsible for implementing these actions, which will authorize or unauthorize your players from accessing a relay. [More information in this section](/learn/distributed-relay/relay-edgegap-api).

#### **Integration with Existing Transport Layer**

After authorizing your players on a relay, you will still need to implement the communication between your game client and the relay, which is known as the transport layer.

To make your life easier, we are working to do many implementations for different netcode, language and framework. If you want to integrate one of our actual implementation, simply follow the tutorial corresponding your stack.

* [Unity with Mirror Netcode](/docs/sample-projects/unity-netcodes/mirror-on-edgegap)
* Unreal Networking - Coming soon
* [Unity Netcode for GameObjects](/docs/sample-projects/unity-netcodes/unity-netcode-on-edgegap)
* [Fishnet Networking](/docs/sample-projects/unity-netcodes/fishnet-on-edgegap)

#### **(Alternatively) Implementing Your Own Transport Layer**

If you are interested in implementing your own transport layer for a specific netcode, you can find detailed information and guidance by following this guide:

[Relay Implementation Manual](https://github.com/edgegap/distributed-relay-examples#relay-implementation-manual)

If you have any question on the implementation or want to know the roadmap on our current integration feel free to reach us!


# Game Integration

### Why do I need a Matchmaker and/or Lobby?

A Lobby Service ([Managed Clusters](/learn/advanced-features/managed-clusters#nakama-by-heroic-labs)) system plays a crucial role in online multiplayer games for various technical reasons.

**Player management**

A lobby or matchmaker system helps manage players by grouping them into appropriate matches based on various criteria such as skill level, geographical location, or game preferences. This ensures a balanced and enjoyable gaming experience for all players involved.

**Backend**

You do not want your player to communicate directly with your backend and you want to offer a centralized point for them to access. Imagine you have an API token that start a VM in a certain provider, you do not want the Token hardcoded in the Game Client, accessible to any players!

**Security**

Using a centralized lobby or matchmaker system helps maintain control over the game's security. By filtering and validating player connections, a matchmaker can prevent unauthorized access, protect user data, and mitigate potential cheating or hacking attempts.

**Game session management**

A lobby or matchmaker system manages game sessions by creating, updating, and terminating them as needed. This ensures that players can join or leave games seamlessly, and the game instances can be efficiently managed by the system.

<figure><img src="/files/cF4cYV713b6j8SFKeVZ5" alt=""><figcaption></figcaption></figure>

### You Don't Need One Right Away! (Development mode)

During the development phase, you might not need a fully-fledged lobby or matchmaker system for several technical reasons:

**Simplified testing environment**

In the initial stages of development, you might focus on implementing and testing the core game mechanics and networking features. By avoiding the complexity of a lobby or matchmaker system, you can more easily test and debug your game with a smaller group of players, often within your development team.

**Faster iteration**

Without a lobby or matchmaker, you can make quick changes to your game code and test new features or fixes without worrying about the impact on the matchmaking process. This allows for a more agile development approach, enabling your team to iterate and refine the game mechanics more rapidly.

**Resource allocation**

Developing a full-scale lobby or matchmaker system requires considerable time and resources. By delaying the implementation of these components, you can allocate your resources more efficiently, focusing on the core gameplay and networking features first.

**Scalability concerns**

In the early stages of development, you likely won't have a large number of players to manage. As a result, a basic system for connecting players or even manual connections for testing purposes may be sufficient. However, as your game grows and attracts more players, a lobby or matchmaker system will become increasingly necessary to manage player connections and ensure a smooth gameplay experience.

It's crucial to design your game architecture to accommodate a lobby or matchmaker system in the future. This forward-thinking approach will make it easier to integrate these components when you're ready to test your game with a larger group of players, ensuring a smoother transition and a more polished final product.

### Interacting with Edgegap’s API

Edgegap offer a simple API to interact with the Relay Sessions to authorize your players to connect to the closest relay for low latency.

<figure><img src="/files/tFUiWfK8hKIkrtnyfQFt" alt=""><figcaption></figcaption></figure>

To interact with the API, you'll need to provide an `Authorization Header Token` in your requests.

{% hint style="info" %}
You can acquire a token by registering for an account and generating a relay profile. [**Register here**](https://app.edgegap.com/auth/register)

Please note that you need a token exclusively designated for the relays. If you already have a standard API token, it will not work.
{% endhint %}

This is accessible from the sidebar on the dashboard.

<figure><img src="/files/8X57s8b75Edbakrvb8j6" alt=""><figcaption></figcaption></figure>

Once you have the token, include it in the request headers for all API calls. Don't forget to prefix your API key with `token`.

{% hint style="success" %}
If you are not familiar with working with an API, you can refer to [this section](#interacting-with-api-curl-or-postman) to assist you in getting started.
{% endhint %}

### Creating a Relay Session

To use Distributed Relay, you need to create a relay session. This is done by sending a `POST` request to the `/v1/relays/sessions` endpoint with a JSON payload. Sessions can be created manually, with a matchmaker, with a lobby, or with your own custom service.

This will dynamically select the best available relay for your players in real-time and create authorization for accessing the relay. However, we recommend pre-filtering your matchmaking to group players within the same region to avoid extreme distances between them.

{% hint style="info" %}
This step will not directly connect your players to the relay; you will need to handle that in a later stage of the process. This step only provides you with a relay to establish a connection with.
{% endhint %}

Example Request:

```bash
POST - /v1/relays/sessions
```

The payload should contain an array of user objects, where each object contains the IP address of a user. You can also include a `webhook_url`, which is the URL that will receive notifications related to the session.

Example Payload:

```json
{
  "users": [
    {
      "ip": "1.1.1.1"
    },
    {
      "ip": "2.2.2.2"
    }
  ],
  "webhook_url": "https://webhook.example.com/notify"
}
```

{% hint style="info" %}
Where `"users"` is an array of user objects containing the `"ip"` of each user in the session, and `"webhook_url"` is an optional URL to receive session updates.

After sending the `POST` request, you will receive a JSON response containing the `session_id`, which is needed to retrieve session information later.
{% endhint %}

Example Response:

```json
{
  "session_id": "3960c873aafd-S",
  "authorization_token": null,
  "status": "Initializing",
  "ready": false,
  "linked": false,
  "error": null,
  "session_users": [],
  "relay": null,
  "webhook_url": "https://webhook.example.com/notify"
}
```

### Getting Information about the Relay Session

To retrieve session information, send a `GET` request to the `/v1/relays/sessions/{session_id}` endpoint with the `session_id` obtained from the `POST` request.

Example Request:

```bash
GET - /v1/relays/sessions/3960c873aafd-S
```

The response will contain information about the session, including the session status, users information, and relay information.

The expected response will contain session information like this:

```json
{
  "session_id": "3960c873aafd-S",
  "authorization_token": 1031196689,
  "status": "Linked",
  "ready": true,
  "linked": true,
  "error": null,
  "session_users": [
    {
      "ip_address": "2.2.2.2",
      "latitude": 48.8602294921875,
      "longitude": 2.34106993675232,
      "authorization_token": 3499933322
    },
    {
      "ip_address": "1.1.1.1",
      "latitude": -37.7036018371582,
      "longitude": 145.180633544922,
      "authorization_token": 4261594560
    }
  ],
  "relay": {
    "ip": "178.79.131.238",
    "host": "cc84b011777b.pr.edgegap.net",
    "ports": {
      "server": {
        "port": 31527,
        "protocol": "UDP",
        "link": "cc84b011777b.pr.edgegap.net:31527"
      },
      "client": {
        "port": 32089,
        "protocol": "UDP",
        "link": "cc84b011777b.pr.edgegap.net:32089"
      }
    }
  },
  "webhook_url": "https://webhook.example.com/notify"
}
```

Getting the Authorization to the closest relay can take a small amount of time, so when the field `ready` is `true` you can extract JSON data to use it.

[More details here](/docs/api)

{% hint style="success" %}
Alternatively, you can use the \`webhook\_url\` parameter when creating a relay session to be notified when the session has either succeeded in being assigned to a relay or has failed to do so.
{% endhint %}

### Terminating a Relay Session

When you decide to end your connection to the relay, you can easily terminate a relay session. You need to send a `DELETE` request to the following endpoint:

```bash
DELETE - /v1/relays/sessions/{session_id}
```

{% hint style="info" %}
Replace `{session_id}` with the actual session ID you want to terminate.
{% endhint %}

A response with status 204 no content means the session has been successfully deleted. The players will lose access to the relay and you will no longer be charged for that session.

{% hint style="warning" %}
It's important to terminate sessions properly in order to avoid leaving any unused resources allocated, which can impact performance and incur unnecessary costs.
{% endhint %}

***

### C# Example

This is a C# that your Lobby or Matchmaker could do to create a Relay Session and extract the Data that you would need to return to your game client.

{% hint style="info" %}
This example uses C# version > 7.0
{% endhint %}

```csharp
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace LobbyNamespace
{
    public class EdgegapAPI
    {
        private readonly HttpClient _httpClient;

        public EdgegapAPI(string apiToken)
        {
            _httpClient = new HttpClient();
            _httpClient.DefaultRequestHeaders.Add("Authorization", $"Token {apiToken}");
        }

        public async Task<RelaySessionAuthorization> CreateRelaySession(List<string> ips)
        {
            var jsonBody = new
            {
                users = ips.ConvertAll(ip => new { ip })
            };

            var postResponse = await _httpClient.PostAsJsonAsync("https://api.edgegap.com/v1/relays/sessions", jsonBody);
            var postJsonData = await postResponse.Content.ReadFromJsonAsync<Dictionary<string, object>>();

            if (!postJsonData.ContainsKey("session_id"))
            {
                throw new Exception("Failed to create relay session");
            }

            var sessionId = postJsonData["session_id"].ToString();

            var retries = 5;
            while (retries > 0)
            {
                var getResponse = await _httpClient.GetAsync($"https://api.edgegap.com/v1/relays/sessions/{sessionId}");
                var getJsonData = await getResponse.Content.ReadFromJsonAsync<Dictionary<string, object>>();
                var ready = (JsonElement)getJsonData["ready"];

                if (ready.ValueKind == JsonValueKind.True)
                {
                    var usersData = (JsonElement)getJsonData["session_users"];
                    var userAuthorizationTokens = new Dictionary<string, UInt32>();
                    foreach (var user in usersData.EnumerateArray())
                    {
                        var ipAddress = user.GetProperty("ip_address").GetString();
                        var userAuthorizationToken = user.GetProperty("authorization_token").GetUInt32();
                        userAuthorizationTokens[ipAddress] = userAuthorizationToken;
                    }

                    var relayData = (JsonElement)getJsonData["relay"];
                    var relayAddress = relayData.GetProperty("host").GetString();
                    var relayServerPort = relayData.GetProperty("ports").GetProperty("server").GetProperty("port").GetInt32();
                    var relayClientPort = relayData.GetProperty("ports").GetProperty("client").GetProperty("port").GetInt32();

                    return new RelaySessionAuthorization
                    {
                        SessionAuthorizationToken = sessionId,
                        UserAuthorizationTokens = userAuthorizationTokens,
                        RelayAddress = relayAddress,
                        RelayServerPort = relayServerPort,
                        RelayClientPort = relayClientPort
                    };
                }

                retries--;
                await Task.Delay(2000); // wait for 2 second before retrying
            }

            throw new Exception("Failed to get a ready relay session");
        }
    }

    public class RelaySessionAuthorization
    {
        public string? SessionAuthorizationToken { get; set; }
        public Dictionary<string, UInt32>? UserAuthorizationTokens { get; set; }
        public string? RelayAddress { get; set; }
        public int RelayServerPort { get; set; }
        public int RelayClientPort { get; set; }
    }
}
```

You will need to return this information to the corresponding Player.

The Player acting as the Server will need the `RelayServerPort` and all the other will need the `RelayClientPort`

```csharp
using System.Text.Json;
using LobbyNamespace;

class Program
{
    static async Task Main(string[] args)
    {
        // Replace YOUR_API_TOKEN with your actual API token
        var apiToken = "YOUR_API_TOKEN";

        // 1.1.1.1 is your player acting as a server
        // 2.2.2.2 is your player acting as a client
        var ips = new List<string> { "1.1.1.1", "2.2.2.2" };

        var edgegapAPI = new EdgegapAPI(apiToken);
        var relaySessionAuthorization = await edgegapAPI.CreateRelaySession(ips);

        // Generate JSON body for server (1.1.1.1)
        var serverJsonBody = new
        {
            relay_address = relaySessionAuthorization.RelayAddress,
            port = relaySessionAuthorization.RelayServerPort,
            session_authorization_token = relaySessionAuthorization.SessionAuthorizationToken,
            user_authorization_token = relaySessionAuthorization.UserAuthorizationTokens["1.1.1.1"]
        };
        var serverJson = JsonSerializer.Serialize(serverJsonBody);
        Console.WriteLine($"JSON body for server (1.1.1.1):\n{serverJson}");

        // Generate JSON body for client (2.2.2.2)
        var clientJsonBody = new
        {
            relay_address = relaySessionAuthorization.RelayAddress,
            port = relaySessionAuthorization.RelayClientPort,
            session_authorization_token = relaySessionAuthorization.SessionAuthorizationToken,
            user_authorization_token = relaySessionAuthorization.UserAuthorizationTokens["2.2.2.2"]
        };
        var clientJson = JsonSerializer.Serialize(clientJsonBody);
        Console.WriteLine($"JSON body for client (2.2.2.2):\n{clientJson}");
    }
}
```

### Interacting with API (cURL or POSTMAN)

While developing your game, you'll need to interact with the API to create, manage, and delete relay sessions. You can use tools like cURL or POSTMAN to send HTTP requests to the API.

cURL is a command-line tool that allows you to make HTTP requests and interact with APIs directly from the terminal. POSTMAN is a popular graphical user interface (GUI) application that simplifies API testing by providing a user-friendly interface to create, send, and analyze HTTP requests.

Here's an example of how to create a relay session using cURL:

```bash
curl -X POST -H "Content-Type: application/json" -H "Authorization: Token API_TOKEN" -d '{
  "users": [
    {
      "ip": "1.1.1.1"
    },
    {
      "ip": "2.2.2.2"
    }
  ],
  "webhook_url": "https://webhook.example.com/notify"
}' "https://api.edgegap.com/v1/relays/sessions"
```

{% hint style="info" %}
Remember to replace `API_TOKEN` with your actual relay API Token.
{% endhint %}

The API will return you a response that contain data also. You will need to parse this response and extract the data you need.


# Transport Samples

After managing your players with either a matchmaker or lobbies then creating a relay session for them using the Edgegap API, you will need to connect your players to the relay once it is ready. Edgegap offers a specific transport to do just that, and this guide will show you how to implement it in your project. The transport is available [here on our GitHub](https://github.com/edgegap/distributed-relay-examples/tree/main).

With Unity, there currently is a version of the transport for the following netcodes:

* Mirror;
* Fishnet;
* Unity Netcode for GameObjects (NGO).

### Adding the transport

After downloading the right transport for your netcode, you need to include it in your project. First, add it to your project files in the following location:

* **Mirror** : add the `Edgegap` folder under `Assets/Mirror/Transports`;
* **Fishnet** : add the `Edgegap KcpTransport` folder under `Assets/Fishnet/Plugins`;
* **NGO** : add the `EdgegapRelay` and `kcp2k` folders under `Assets/Edgegap`.

Then, add the `EdgegapKcpTransport` script to your `NetworkManager` gameObject, make sure to drag that new component in the `Transport` property of the gameObject as well.

{% hint style="info" %}
If the option is present, make sure to disable `Relay GUI` in the `EdgegapKcpTransport` component of the `NetworkManager`. Otherwise, it will interfere when attempting to connect to the relay.

This option is namely present in the Mirror and NGO versions of the transport.
{% endhint %}

### Connecting to the relay

After matchmaking and creating the relay session, use the Edgegap API to determine when the relay is ready to accept connections. Once it is, use the data provided in the API response to set the values of the transport. You will need the following values in the appropriate fields:

* the relay's `IP` address is used as the `transport's relay address`;
* the `session authorization token` is used as the `transport's session ID`;
* the `user's authorization token` is used as the `transport's user ID`.

#### Server/Host Connection

* the relay's `server port` value is used as the `transport's relay port` .

#### Client Connection

* the relay's `client port` value is used as the `transport's relay port` .

{% hint style="info" %}
Each player will have their own unique authorization token, but the session token will remain the same for each player in the session.
{% endhint %}

### Using Mirror

```cs
// `data` is the deserialized API response converted to JSON
// `_EdgegapTransport` is the EdgegapKcpTransport

// Convert uint? to uint
uint sessionAuthorizationToken = data.authorization_token ?? 0;

//TODO find which session user matches the player, `i` being its place in the list
uint userAuthorizationToken = data.session_users?[i].authorization_token ?? 0;

_EdgegapTransport.ChangeValue(
    data.relay.ip,
    data.relay.ports.client.port,
    data.relay.ports.server.port,
    data.session_id,
    sessionAuthorizationToken,
    userAuthorizationToken
);

// then `NetworkManager.Singleton.StartHost();` if host player
// OR `NetworkManager.Singleton.StartClient();` if client 
```

### Using Fishnet

```cs
// `data` is the deserialized API response converted to JSON
// `_transport` is the EdgegapKcpTransport

// Convert uint? to uint
uint sessionAuthorizationToken = data.authorization_token ?? 0;

//TODO find which session user matches the player, `i` being its place in the list
uint userAuthorizationToken = data.session_users?[i].authorization_token ?? 0;

Relay relay = data.relay;
string address = relay.ip;
ushort serverPort = relay.ports.server.port;
ushort clientPort = relay.ports.client.port;

var relayData = new EdgegapRelayData(
    address,
    serverPort,
    clientPort,
    userAuthorizationToken,
    sessionAuthorizationToken
);
_transport.SetEdgegapRelayData(relayData);

// then `_transport.StartConnection(true);` if host
// OR `_transport.StartConnection(false);` if client
```

### Using NGO

```cs
// `data` is the deserialized API response converted to JSON
// `_EdgegapTransport` is the EdgegapKcpTransport

// Convert uint? to uint
uint sessionAuthorizationToken = data.authorization_token ?? 0;

//TODO find which session user matches the player, `i` being its place in the list
uint userAuthorizationToken = data.session_users?[i].authorization_token ?? 0;

_EdgegapTransport.relayAddress = data.relay.ip;
_EdgegapTransport.relayGameClientPort = data.relay.ports.client.port;
_EdgegapTransport.relayGameServerPort = data.relay.ports.server.port;
_EdgegapTransport.sessionId = sessionAuthorizationToken;
_EdgegapTransport.userId = userAuthorizationToken;

// then `NetworkManager.Singleton.StartHost();` if host player
// OR `NetworkManager.Singleton.StartClient();` if client 
```

### Using ApiResponse

```cs
public class ApiResponse
{
    public string session_id { get; set; }
    public uint? authorization_token { get; set; }
    public string status { get; set; }
    public bool ready { get; set; }
    public bool linked { get; set; }
    public object? error { get; set; }
    public List<SessionUser>? session_users { get; set; }
    public Relay relay { get; set; }
    public object? webhook_url { get; set; }
}
```

Once the values are properly set, use the transport to connect each player to the relay. After a short moment, you will be able to play the game!

### Sample projects

The following projects are simple examples that use the Edgegap relay transport.

For them to work properly, open a command prompt terminal and download the project via the `git clone [URL]` command. Open the project folder in the editor via the Unity Hub, then change the `RelayProfileToken` value in the `HelloWorldManager` component of the `NetworkManager` gameObject to your own relay profile token.

* [Mirror sample](https://github.com/edgegap/unity-mirror-relay-sample)
* [Fishnet sample](https://github.com/edgegap/unity-fishnet-relay-sample)
* [NGO sample](https://github.com/edgegap/unity-ngo-relay-sample)

{% hint style="info" %}
Use your custom [Relay Profile token](https://app.edgegap.com/relay-management/dashboard) in `HelloWorldManager.cs` or `EdgegapRelayService.cs`.
{% endhint %}


# API Reference

Get access to management functions for your Edgegap resources using HTTP requests.

<a href="https://raw.githubusercontent.com/edgegap/openapi-specification/refs/heads/main/edgegap-v2-openapi.yaml" class="button primary" data-icon="square-down">Download openapi \[v2]</a> <a href="http://raw.githubusercontent.com/edgegap/openapi-specification/refs/heads/main/edgegap-v1-openapi.yaml" class="button secondary" data-icon="square-down">Download openapi \[v1]</a>&#x20;

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

### Authentication Token

Generate (and view) your secret tokens for Edgegap API in [Dashboard - User Settings / Tokens](https://app.edgegap.com/user-settings?tab=tokens).

Add your secret token with each API request as an HTTP header (include the word `token`):

`Authorization: token xxxxxxxx-e458-4592-b607-c2c28afd8b62`

{% hint style="danger" %}
**Do not integrate Edgegap API endpoints in game client, as your API token provides unlimited access to your account. See** [Integration](/docs/api/integration) **for secure client-facing API endpoints and functions.**
{% endhint %}

{% hint style="success" %}
In case your secret tokens are compromised, delete them, and update organization member passwords.
{% endhint %}

### Response Pagination

In case a response would contain too many items, we return a subset of data with pagination info:

<pre class="language-json"><code class="lang-json">{
  "total_count": 100,
  "data": ["foo-0", "[...]", "bar-9"],
  "<a data-footnote-ref href="#user-content-fn-1">pagination</a>": {
    "number": 1,
    "next_page_number": 2,
    "previous_page_number": null
    "paginator": {
      "num_pages": 10
    },
    "has_next": true,
    "has_previous": false
  }
}
</code></pre>

For paginated response, use parameters `page` and `limit`  to retrieve more results:

* second page: `https://api.edgegap.com/v1/apps?`**`page=2`**
* more results: `https://api.edgegap.com/v1/apps?`**`limit=20`**
* combined: `https://api.edgegap.com/v1/apps?`**`page=2&limit=20`**

### Rate Limiting

To ensure platform stability and prevent surprise invoices, we rate limit your organization's API usage:

* [Deployments](/learn/orchestration/deployments) at 40 requests per second,
* [Deployments](/learn/orchestration/deployments#context-and-status) at 20 requests per second.

{% hint style="info" %}
[Contact us](mailto:info@edgegap.com) to **plan releases, run load tests, estimate launch traffic, and prepare for success.**
{% endhint %}

[^1]: contains pagination information


# Dedicated Servers

## ⚡ Deployments

## \[v2] Deploy

> \[Rate Limit: 40 req/s] Create a new deployment - containerized instance of an application version.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v2 API","version":"20026.06.01"},"tags":[{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com/v2","description":"v1"}],"security":[{"apiKey1":[]}],"components":{"securitySchemes":{"apiKey1":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"DeploymentV2":{"required":["application","users","version"],"properties":{"application":{"type":"string","description":"Name of the application that will be deployed"},"version":{"type":"string","description":"Name of the version within the application that will be deployed"},"resources":{"type":"object","properties":{"cpu_units":{"type":"integer","description":"Amount of vCPU units assigned to deployment. (1024 units = 1 vCPU).","minimum":100},"memory_mib":{"type":"integer","description":"Amount of memory MB assigned to deployment, at most double your vCPU units. (1024 MB = 1 GB).","minimum":100}},"required":["cpu_units","memory_mib"],"description":"Overrides app version resource specification if supplied."},"require_cached_locations":{"type":"boolean","default":false,"description":"Deploy faster by limiting placement to locations with cached image.","nullable":true},"users":{"type":"array","description":"List of users. These users will be used to select the locations for the deployment","minItems":1,"items":{"type":"object","properties":{"user_type":{"type":"string","description":"Type of user (e.g., ip_address or geo_coordinates)"},"user_data":{"type":"object","description":"Dynamic user data depending on user_type","properties":{}}},"required":["user_data","user_type"]}},"environment_variables":{"type":"array","description":"List of environment variables to inject into the deployment","items":{"type":"object","properties":{"key":{"type":"string","description":"Environment variable key"},"value":{"type":"string","description":"Environment variable value"},"is_hidden":{"type":"boolean","description":"An hidden environment variable is not shown in the UI"}},"required":["is_hidden","key","value"]}},"tags":{"type":"array","description":"List of tags to associate with the deployment","items":{"type":"string"}},"webhook_on_ready":{"description":"Webhook to call when the deployment is ready","allOf":[{"$ref":"#/components/schemas/BasicWebhook"}]},"webhook_on_error":{"description":"Webhook to call when the deployment is in error","allOf":[{"$ref":"#/components/schemas/BasicWebhook"}]},"webhook_on_terminated":{"description":"Webhook to call when the deployment is terminated","allOf":[{"$ref":"#/components/schemas/BasicWebhook"}]}},"type":"object"},"BasicWebhook":{"required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL"}},"type":"object"}}},"paths":{"/deployments":{"post":{"summary":"[v2] Deploy","deprecated":false,"description":"[Rate Limit: 40 req/s] Create a new deployment - containerized instance of an application version.","operationId":"v2-deployment-create","tags":["Deployments","Dedicated Servers"],"parameters":[{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentV2"}}}},"responses":{"202":{"description":"Deployment Request Accepted","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"request_id":{"type":"string","description":"ID of your deployment, also referred to as request ID."}},"required":["request_id"]}}},"headers":{}},"400":{"description":"Client Side Request Error","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"Error message"},"details":{"type":"object","description":"Additional error details","properties":{}}},"required":["message"]}}},"headers":{}},"401":{"description":"Unauthorized - Missing Token","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"Error message"},"details":{"type":"object","description":"Additional error details","properties":{}}},"required":["message"]}}},"headers":{}},"422":{"description":"Couldn't Allocate Server","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"request_id":{"type":"string","description":"Request ID. Unique identifier of the deployment request"},"message":{"type":"string","description":"Error message"}},"required":["request_id","message"]}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"Error message"},"details":{"type":"object","description":"Additional error details","properties":{}}},"required":["message"]}}},"headers":{}}}}}}}
```

## Get Deployment

> \[Rate Limit: 20 req/s] Get the specified deployment status and information.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"V1DeploymentStatus":{"type":"object","properties":{"request_id":{"type":"string","description":"ID of your deployment, also referred to as request ID."},"fqdn":{"type":"string","description":"FQDN (URL) to connect to the deployment."},"public_ip":{"type":"string","description":"Public IP address of the host."},"app_name":{"type":"string","description":"Application which has been deployed."},"app_version":{"type":"string","description":"App version which has been deployed."},"current_status":{"type":"string","description":"Indicates whether deployment is Ready, in Error, or otherwise."},"running":{"type":"boolean","description":"True if deployment is Ready for connections.","default":true},"start_time":{"type":"string","description":"Timestamp of deployment becoming Ready.","format":"date-time"},"elapsed_time":{"type":"integer","description":"Seconds since deployment becoming Ready."},"max_duration":{"type":"integer","description":"Maximum allowed uptime minutes of deployments before automatically stopped.","minimum":-1,"maximum":1440},"removal_time":{"type":"string","description":"Timestamp of deployment stopped.","format":"date-time"},"last_status":{"type":"string","description":"Previous status of deployment."},"error":{"type":"boolean","description":"Indicates an error with the deployment.","default":false},"error_detail":{"type":"string","description":"Details of the error."},"ports":{"type":"object","properties":{"gameport":{"$ref":"#/components/schemas/DeploymentPort"}},"description":"Port mapping exposing server listeners to internet."},"location":{"$ref":"#/components/schemas/LocationData","description":"Geographic location of the deployment."},"tags":{"type":"array","items":{"type":"string"},"description":"List of tags to mark your deployments for easy filtering."},"command":{"type":"string","description":"Overrides container command for the deployment, use with caution."},"arguments":{"type":"string","description":"Overrides container arguments for the deployment, use with caution."}},"required":["app_name","app_version","current_status","elapsed_time","error","fqdn","max_duration","public_ip","request_id","running","start_time"],"nullable":true},"DeploymentPort":{"type":"object","properties":{"name":{"type":"string","description":"Custom identifier."},"link":{"type":"string","description":"URL to connect, containing FQDN and external port."},"internal":{"type":"integer","description":"Internal listener port where deployment expects connections.","minimum":0,"maximum":59999},"external":{"type":"integer","minimum":0,"maximum":59999,"description":"Randomly assigned port to connect to the deployment."},"protocol":{"type":"string","description":"Selected networking protocol."},"tls_upgrade":{"type":"boolean","description":"TLS proxy, only available with ports WS and HTTP.","default":false}},"required":["name","link","internal","external","protocol","tls_upgrade"]},"LocationData":{"required":["administrative_division","city","continent","country","latitude","longitude","timezone"],"properties":{"city":{"type":"string","description":"City of the deployment."},"country":{"type":"string","description":"Country of the deployment."},"continent":{"type":"string","description":"Continent of the deployment."},"administrative_division":{"type":"string","description":"Administrative division of the deployment."},"timezone":{"type":"string","description":"Timezone of the deployment."},"latitude":{"type":"number","description":"Latitude of the deployment"},"longitude":{"type":"number","description":"Longitude of the deployment."}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/status/{deployment_id}":{"get":{"summary":"Get Deployment","deprecated":false,"description":"[Rate Limit: 20 req/s] Get the specified deployment status and information.","operationId":"deployment-get","tags":["Deployments","Dedicated Servers"],"parameters":[{"name":"deployment_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1DeploymentStatus"}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"required":["message"]}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## List Deployments

> \[Rate Limit: 10 req/s] List all deployments, optionally filtered and sorted.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"DeploymentPort":{"type":"object","properties":{"name":{"type":"string","description":"Custom identifier."},"link":{"type":"string","description":"URL to connect, containing FQDN and external port."},"internal":{"type":"integer","description":"Internal listener port where deployment expects connections.","minimum":0,"maximum":59999},"external":{"type":"integer","minimum":0,"maximum":59999,"description":"Randomly assigned port to connect to the deployment."},"protocol":{"type":"string","description":"Selected networking protocol."},"tls_upgrade":{"type":"boolean","description":"TLS proxy, only available with ports WS and HTTP.","default":false}},"required":["name","link","internal","external","protocol","tls_upgrade"]},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/deployments":{"get":{"summary":"List Deployments","deprecated":false,"description":"[Rate Limit: 10 req/s] List all deployments, optionally filtered and sorted.","operationId":"deployment-list","tags":["Deployments","Dedicated Servers"],"parameters":[{"name":"query","in":"query","description":"Query to filter the deployments. [See filtering guide.](https://docs.edgegap.com/learn/orchestration/deployments#filter-deployments) For client integrations, [consider Server Browser instead](https://docs.edgegap.com/learn/server-browser).","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"limit","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","description":"List of active deployments","items":{"type":"object","properties":{"request_id":{"type":"string","description":"ID of your deployment, also referred to as request ID."},"fqdn":{"type":"string","description":"FQDN (URL) to connect to the deployment."},"ready":{"type":"boolean","description":"True if deployment is Ready for connections."},"start_time":{"type":"string","description":"Timestamp of deployment becoming Ready."},"public_ip":{"type":"string","description":"Public IP address of the host."},"ports":{"type":"object","properties":{"gameport":{"$ref":"#/components/schemas/DeploymentPort"}},"description":"Port mapping exposing server listeners to internet."},"tags":{"type":"array","description":"List of tags to mark your deployments for easy filtering.","items":{"type":"string"}}},"required":["fqdn","public_ip","ready","request_id","start_time"]}},"message":{"type":"array","description":"Additional information related to your query.","items":{"type":"string"}},"total_count":{"type":"integer","minimum":0},"pagination":{"type":"object","properties":{"number":{"type":"integer","minimum":0},"next_page_number":{"type":"integer","minimum":1,"nullable":true},"previous_page_number":{"type":"integer","minimum":1,"nullable":true},"paginator":{"type":"object","properties":{"num_pages":{"type":"integer","minimum":1}},"required":["num_pages"]},"has_next":{"type":"boolean","default":true},"has_previous":{"type":"boolean","default":false}},"required":["number","next_page_number","previous_page_number","paginator","has_next","has_previous"]}},"required":["total_count","pagination"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Stop Deployment

> Request to stop a deployment from your custom backend, triggering a graceful shutdown (SIGTERM). Returns 200 or 202 if deployment status is not READY and not ERROR, and stops deployment once either of these statuses is reached - \[see Deployment Lifecycle]\(<https://docs.edgegap.com/learn/advanced-features/deployments#deployment-lifecycle>).

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"V1DeploymentStatus":{"type":"object","properties":{"request_id":{"type":"string","description":"ID of your deployment, also referred to as request ID."},"fqdn":{"type":"string","description":"FQDN (URL) to connect to the deployment."},"public_ip":{"type":"string","description":"Public IP address of the host."},"app_name":{"type":"string","description":"Application which has been deployed."},"app_version":{"type":"string","description":"App version which has been deployed."},"current_status":{"type":"string","description":"Indicates whether deployment is Ready, in Error, or otherwise."},"running":{"type":"boolean","description":"True if deployment is Ready for connections.","default":true},"start_time":{"type":"string","description":"Timestamp of deployment becoming Ready.","format":"date-time"},"elapsed_time":{"type":"integer","description":"Seconds since deployment becoming Ready."},"max_duration":{"type":"integer","description":"Maximum allowed uptime minutes of deployments before automatically stopped.","minimum":-1,"maximum":1440},"removal_time":{"type":"string","description":"Timestamp of deployment stopped.","format":"date-time"},"last_status":{"type":"string","description":"Previous status of deployment."},"error":{"type":"boolean","description":"Indicates an error with the deployment.","default":false},"error_detail":{"type":"string","description":"Details of the error."},"ports":{"type":"object","properties":{"gameport":{"$ref":"#/components/schemas/DeploymentPort"}},"description":"Port mapping exposing server listeners to internet."},"location":{"$ref":"#/components/schemas/LocationData","description":"Geographic location of the deployment."},"tags":{"type":"array","items":{"type":"string"},"description":"List of tags to mark your deployments for easy filtering."},"command":{"type":"string","description":"Overrides container command for the deployment, use with caution."},"arguments":{"type":"string","description":"Overrides container arguments for the deployment, use with caution."}},"required":["app_name","app_version","current_status","elapsed_time","error","fqdn","max_duration","public_ip","request_id","running","start_time"],"nullable":true},"DeploymentPort":{"type":"object","properties":{"name":{"type":"string","description":"Custom identifier."},"link":{"type":"string","description":"URL to connect, containing FQDN and external port."},"internal":{"type":"integer","description":"Internal listener port where deployment expects connections.","minimum":0,"maximum":59999},"external":{"type":"integer","minimum":0,"maximum":59999,"description":"Randomly assigned port to connect to the deployment."},"protocol":{"type":"string","description":"Selected networking protocol."},"tls_upgrade":{"type":"boolean","description":"TLS proxy, only available with ports WS and HTTP.","default":false}},"required":["name","link","internal","external","protocol","tls_upgrade"]},"LocationData":{"required":["administrative_division","city","continent","country","latitude","longitude","timezone"],"properties":{"city":{"type":"string","description":"City of the deployment."},"country":{"type":"string","description":"Country of the deployment."},"continent":{"type":"string","description":"Continent of the deployment."},"administrative_division":{"type":"string","description":"Administrative division of the deployment."},"timezone":{"type":"string","description":"Timezone of the deployment."},"latitude":{"type":"number","description":"Latitude of the deployment"},"longitude":{"type":"number","description":"Longitude of the deployment."}},"type":"object"},"DeleteDeploymentResponse":{"required":["message"],"properties":{"message":{"type":"string","description":"Additional information regarding deployment termination."}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/stop/{deployment_id}":{"delete":{"summary":"Stop Deployment","deprecated":false,"description":"Request to stop a deployment from your custom backend, triggering a graceful shutdown (SIGTERM). Returns 200 or 202 if deployment status is not READY and not ERROR, and stops deployment once either of these statuses is reached - [see Deployment Lifecycle](https://docs.edgegap.com/learn/advanced-features/deployments#deployment-lifecycle).","operationId":"deployment-delete","tags":["Deployments","Dedicated Servers"],"parameters":[{"name":"deployment_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"container_log_storage","in":"query","description":"Enable or disable previously configured log storage. Alternatively, provide endpoint storage ID to override or set storage to upload logs.","required":false,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Additional information regarding deployment termination."},"deployment_summary":{"$ref":"#/components/schemas/V1DeploymentStatus"}},"required":["message"]}}},"headers":{}},"202":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteDeploymentResponse"}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"410":{"description":"Instance Already terminated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Stop Deployments

> Delete multiple deployments using custom filtering. Caution is advised.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/deployments/bulk-stop":{"post":{"summary":"Stop Deployments","deprecated":false,"description":"Delete multiple deployments using custom filtering. Caution is advised.","operationId":"deployment-bulk-delete","tags":["Deployments","Dedicated Servers"],"parameters":[{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"filters":{"type":"array","description":"Identify which deployments to stop.","items":{"type":"object","properties":{"field":{"type":"string","description":"Deployment field used for filtering.","enum":["request_id","deployment_tags"]},"values":{"type":"array","description":"Values to compare against.","items":{"type":"string"}},"filter_type":{"type":"string","description":"Filter operator.","enum":["any","all","not"]}},"required":["field","filter_type","values"]}}},"required":["filters"]}}},"required":true},"responses":{"202":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","properties":{"processable":{"type":"array","description":"Deployments which will be stopped.","items":{"type":"object","properties":{"request_id":{"type":"string","description":"ID of your deployment, also referred to as request ID."}},"required":["request_id"]}}},"required":["processable"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Self-Stop Deployment

> Delete a deployment from the inside of a container, available as injected variable \`ARBITRIUM\_DELETE\_URL\`.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"DeleteDeploymentResponse":{"required":["message"],"properties":{"message":{"type":"string","description":"Additional information regarding deployment termination."}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/self/stop/{deployment_id}/{stop_key}":{"delete":{"summary":"Self-Stop Deployment","deprecated":false,"description":"Delete a deployment from the inside of a container, available as injected variable `ARBITRIUM_DELETE_URL`.","operationId":"deployment-self-delete","tags":["Deployments","Dedicated Servers"],"parameters":[{"name":"deployment_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"stop_key","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"container_log_storage","in":"query","description":"Enable or disable previously configured log storage. Alternatively, provide endpoint storage ID to override or set storage to upload logs.","required":false,"schema":{"type":"string"}},{"name":"authorization","in":"header","description":"Auto Generated token. This token is injected in your deployment and can be found via the environment variable named ARBITRIUM_DELETE_TOKEN","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{}}},"headers":{}},"202":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteDeploymentResponse"}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Get Deployment Logs

> Retrieve the logs of your container, if Endpoint Storage was configured before stopping the deployment.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/deployment/{deployment_id}/container-logs":{"get":{"summary":"Get Deployment Logs","deprecated":false,"description":"Retrieve the logs of your container, if Endpoint Storage was configured before stopping the deployment.","operationId":"deployment-logs-get","tags":["Deployments","Dedicated Servers"],"parameters":[{"name":"deployment_id","in":"path","description":"ID of your deployment, also referred to as request ID.","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"logs":{"type":"string","description":"Plaintext deployment success logs."},"crash_logs":{"type":"string","description":"Plaintext deployment crash logs.","nullable":true},"crash_data":{"type":"object","properties":{"exit_code":{"type":"integer","description":"Deployment process exit code."},"message":{"type":"string","description":"Deployment process exit message."},"restart_count":{"type":"integer","description":"Restart counter of your deployment's main process (if allowed).","minimum":0}},"description":"Additional information about deployment's main process error..","nullable":true},"encoding":{"type":"string","description":"Encoding used for logs."},"logs_link":{"type":"string","description":"Link to retrieve logs from your configured Endpoint Storage.","nullable":true}}}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Get Deployment Metrics

> Get metrics for the specified deployment in a given time range.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"MetricsModel":{"properties":{"labels":{"type":"array","items":{"type":"string"}},"datasets":{"type":"array","items":{"type":"integer"}},"timestamps":{"type":"array","items":{"type":"string","format":"date"}}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/metrics/deployment/{deployment_id}":{"get":{"summary":"Get Deployment Metrics","deprecated":false,"description":"Get metrics for the specified deployment in a given time range.","operationId":"deployment-metrics-get","tags":["Deployments","Dedicated Servers"],"parameters":[{"name":"deployment_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"start_time","in":"query","description":"Start of reporting time range, defaults to deployment start time.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"end_time","in":"query","description":"End of reporting time range, default to deployment stopped time. Must be greater than start time.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"steps","in":"query","description":"Size of reporting time step between metric values, aggregated using average (mean).","required":false,"schema":{"type":"string"}},{"name":"raw","in":"query","description":"Optionally enable raw output.","required":false,"schema":{"type":"boolean","default":false}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"object","properties":{"receive_total":{"$ref":"#/components/schemas/MetricsModel"},"transmit_total":{"$ref":"#/components/schemas/MetricsModel"},"disk_read_total":{"$ref":"#/components/schemas/MetricsModel"},"disk_write_total":{"$ref":"#/components/schemas/MetricsModel"}}},"cpu":{"$ref":"#/components/schemas/MetricsModel"},"cpu_throttle_rate":{"$ref":"#/components/schemas/MetricsModel"},"cpu_throttle_total":{"$ref":"#/components/schemas/MetricsModel"},"mem":{"$ref":"#/components/schemas/MetricsModel"},"network":{"type":"object","properties":{"receive":{"$ref":"#/components/schemas/MetricsModel"},"transmit":{"$ref":"#/components/schemas/MetricsModel"}}}}}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"422":{"description":"Unprocessable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## 🗺️ Private Fleets

## Deploy to Fleet

> \<strong>\[Rate Limit: 40/seconds]\</strong> Initiate a new private fleet deployment. A deployment is a containerized server instance of an application version running on the Edgegap platform.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v2 API","version":"20026.06.01"},"tags":[{"name":"Deployments"},{"name":"Dedicated Servers"},{"name":"Private Fleets"}],"servers":[{"url":"https://api.edgegap.com/v2","description":"v1"}],"security":[{"apiKey1":[]}],"components":{"securitySchemes":{"apiKey1":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"BasicWebhook":{"required":["url"],"properties":{"url":{"type":"string","description":"Webhook URL"}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"Error message"},"details":{"type":"object","description":"Additional error details","properties":{}}},"type":"object"},"ParameterError422":{"required":["message","request_id"],"properties":{"request_id":{"type":"string","description":"Request ID. Unique identifier of the deployment request"},"message":{"type":"string","description":"Error message"}},"type":"object"}}},"paths":{"/private-fleets/deployments":{"post":{"summary":"Deploy to Fleet","deprecated":false,"description":"<strong>[Rate Limit: 40/seconds]</strong> Initiate a new private fleet deployment. A deployment is a containerized server instance of an application version running on the Edgegap platform.","operationId":"private-fleets-deployment-create","tags":["Deployments","Dedicated Servers","Private Fleets"],"parameters":[{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"private_host_ids":{"type":"array","items":{"type":"string"},"description":"Preferred and prioritized Private Host IDs to try before overflowing to cloud.","minItems":1,"uniqueItems":true},"application":{"type":"string","description":"Name of the application that will be deployed"},"version":{"type":"string","description":"Name of the version within the application that will be deployed"},"resources":{"type":"object","properties":{"cpu_units":{"type":"integer","description":"Amount of vCPU units assigned to deployment. (1024 units = 1 vCPU).","minimum":100},"memory_mib":{"type":"integer","description":"Amount of memory MB assigned to deployment, at most double your vCPU units. (1024 MB = 1 GB).","minimum":100}},"required":["cpu_units","memory_mib"],"description":"Overrides app version resource specification if supplied."},"require_cached_locations":{"type":"boolean","default":false,"description":"Deploy faster by limiting placement to locations with cached image.","nullable":true},"users":{"type":"array","description":"List of users. These users will be used to select the locations for the deployment","minItems":1,"items":{"type":"object","properties":{"user_type":{"type":"string","description":"Type of user (e.g., ip_address or geo_coordinates)"},"user_data":{"type":"object","description":"Dynamic user data depending on user_type","properties":{}}},"required":["user_data","user_type"]}},"environment_variables":{"type":"array","description":"List of environment variables to inject into the deployment","items":{"type":"object","properties":{"key":{"type":"string","description":"Environment variable key"},"value":{"type":"string","description":"Environment variable value"},"is_hidden":{"type":"boolean","description":"An hidden environment variable is not shown in the UI"}},"required":["is_hidden","key","value"]}},"tags":{"type":"array","description":"List of tags to associate with the deployment","items":{"type":"string"}},"webhook_on_ready":{"description":"Webhook to call when the deployment is ready","allOf":[{"$ref":"#/components/schemas/BasicWebhook"}]},"webhook_on_error":{"description":"Webhook to call when the deployment is in error","allOf":[{"$ref":"#/components/schemas/BasicWebhook"}]},"webhook_on_terminated":{"description":"Webhook to call when the deployment is terminated","allOf":[{"$ref":"#/components/schemas/BasicWebhook"}]}},"required":["private_host_ids","application","version","users"]}}}},"responses":{"202":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"request_id":{"type":"string","description":"ID of your deployment, also referred to as request ID."}},"required":["request_id"]}}},"headers":{}},"400":{"description":"Invalid Application or Application Version. No deployment has been created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized. No deployment has been created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"422":{"description":"Could not allocate an Edge Server for the deployment. A deployment has been created. Deployment is in error state. You can delete it manually or it will be deleted automatically by our system after some some times","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParameterError422"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## List Private Fleet Hosts

> \<strong>\[Rate Limit: 10/seconds]\</strong> List all hosts in a private fleet.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v2 API","version":"20026.06.01"},"tags":[{"name":"Dedicated Servers"},{"name":"Private Fleets"}],"servers":[{"url":"https://api.edgegap.com/v2","description":"v1"}],"security":[{"apiKey1":[]}],"components":{"securitySchemes":{"apiKey1":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}}},"paths":{"/private-fleets/{fleet-name}/hosts":{"get":{"summary":"List Private Fleet Hosts","deprecated":false,"description":"<strong>[Rate Limit: 10/seconds]</strong> List all hosts in a private fleet.","operationId":"private-fleet-hosts-list","tags":["Dedicated Servers","Private Fleets"],"parameters":[{"name":"fleet-name","in":"path","description":"Name of your private fleet.","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"host_id":{"type":"string","description":"Unique private fleet host identifier, used to deploy on this host."},"status":{"type":"string","description":"Lifecycle stage of the host, only ACTIVE hosts accept deployments.","enum":["PENDING","ACTIVE"]},"ip_address":{"type":"string","description":"Public IP address of the host.","nullable":true},"latitude":{"type":"number","description":"Approximate geographic latitude of the host.","nullable":true},"longitude":{"type":"number","description":"Approximate geographic longitude of the host.","nullable":true},"city":{"type":"string","description":"City in which the host is located.","nullable":true},"country":{"type":"string","description":"Country in which the host is located.","nullable":true},"continent":{"type":"string","description":"Continent in which the host is located.","nullable":true},"allocated_cpu_units":{"type":"integer","description":"Total virtual CPU units currently in use for deployments, beacons, and orchestration.","nullable":true},"allocated_memory_mb":{"type":"integer","description":"Total memory in MB currently in use for deployments, beacons, and orchestration."},"allocatable_cpu_units":{"type":"integer","description":"Total available virtual CPU units for deployments.","nullable":true},"allocatable_memory_mb":{"type":"integer","description":"Total available memory in MB for deployments.","nullable":true},"beacon":{"type":"object","properties":{"tcp_port":{"type":"integer","description":"External beacon TCP port used to perform TCP ping."},"udp_port":{"type":"integer","description":"External beacon UDP port used to perform UDP ping."},"ip_address":{"type":"string","description":"Public IP address used to perform ping measurement."}},"required":["tcp_port","udp_port","ip_address"],"description":"Assigned beacon information, if enabled for this fleet.","nullable":true},"created_at":{"type":"string","description":"Timestamp of creation in UTC, ISO 8601 format."},"updated_at":{"type":"string","description":"Timestamp of last update in UTC, ISO 8601 format."},"centroid":{"type":"object","properties":{"label":{"type":"string","description":"Human readable label."},"latitude":{"type":"number","description":"Approximate geographic latitude of the centroid."},"longitude":{"type":"number","description":"Approximate geographic longitude of the centroid."},"radius_km":{"type":"number","description":"Approximate geographic radius used to pick primary and fallback locations."},"desired_host_count":{"type":"integer","description":"Requested amount of hosts to be started under this centroid."}},"required":["label","latitude","longitude","radius_km","desired_host_count"],"description":"Centroid information used to orchestrate placement of the host."},"delete_schedule":{"type":"object","properties":{"uuid":{"type":"string","description":"Unique identifier of deletion schedule."},"scheduled_at":{"type":"string","description":"Timestamp of scheduled deletion in UTC, ISO 8601 format."}},"description":"If defined, specified when the host will be deleted.","required":["uuid","scheduled_at"],"nullable":true},"fleet_host_specifications":{"type":"object","properties":{"cpu_units":{"type":"integer","description":"Host specification vCPU units selected during fleet creation."},"memory_mb":{"type":"integer","description":"Host specification memory MB selected during fleet creation."},"base_clock_speed_mhz":{"type":"integer","description":"Host specification base CPU clock frequency selected during fleet creation."}},"required":["cpu_units","memory_mb","base_clock_speed_mhz"]}},"description":"List of private fleet hosts.","required":["host_id","status","allocated_cpu_units","allocated_memory_mb","created_at","updated_at","centroid","fleet_host_specifications"]}},"total_count":{"type":"integer","minimum":0},"pagination":{"type":"object","properties":{"number":{"type":"integer","minimum":1},"next_page_number":{"type":"integer","minimum":1,"nullable":true},"previous_page_number":{"type":"integer","minimum":1,"nullable":true},"paginator":{"type":"object","properties":{"num_pages":{"type":"integer","minimum":1}},"required":["num_pages"]},"has_next":{"type":"boolean","default":true},"has_previous":{"type":"boolean","default":false}},"required":["number","next_page_number","previous_page_number","paginator","has_next","has_previous"]}},"required":["data","total_count","pagination"]}}},"headers":{}},"400":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"Error message"},"details":{"type":"object","description":"Additional error details","properties":{}}},"required":["message"]}}},"headers":{}},"401":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"Error message"},"details":{"type":"object","description":"Additional error details","properties":{}}},"required":["message"]}}},"headers":{}},"500":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"Error message"},"details":{"type":"object","description":"Additional error details","properties":{}}},"required":["message"]}}},"headers":{}}}}}}}
```

## &#x20;🔖 Tags

## Create Tag

> Create a tag for the specified deployment. Does not modify injected variables in a running deployment.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Tags"},{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"DeploymentTag":{"required":["name"],"properties":{"name":{"type":"string","description":"Name of the tag.","maxLength":40,"minLength":1}},"type":"object"},"DeploymentTagResponse":{"properties":{"name":{"type":"string","description":"Name of the tag.","maxLength":40,"minLength":1},"create_time":{"type":"string","description":"Date of creation.","format":"date-time"},"last_updated":{"type":"string","description":"Date of last update.","format":"date-time"}},"type":"object","required":["name"]},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/deployments/{deployment_id}/tags":{"post":{"summary":"Create Tag","deprecated":false,"description":"Create a tag for the specified deployment. Does not modify injected variables in a running deployment.","operationId":"deployment-tag-create","tags":["Tags","Deployments","Dedicated Servers"],"parameters":[{"name":"deployment_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentTag"}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentTagResponse"}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Get Tag

> Get tag from the specified deployment.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Tags"},{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"DeploymentTagResponse":{"properties":{"name":{"type":"string","description":"Name of the tag.","maxLength":40,"minLength":1},"create_time":{"type":"string","description":"Date of creation.","format":"date-time"},"last_updated":{"type":"string","description":"Date of last update.","format":"date-time"}},"type":"object","required":["name"]},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/deployments/{deployment_id}/tags/{tag_name}":{"get":{"summary":"Get Tag","deprecated":false,"description":"Get tag from the specified deployment.","operationId":"deployment-tag-get","tags":["Tags","Deployments","Dedicated Servers"],"parameters":[{"name":"deployment_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"tag_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentTagResponse"}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## List Tags

> List tags from the specified deployment.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Tags"},{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"DeploymentTagResponse":{"properties":{"name":{"type":"string","description":"Name of the tag.","maxLength":40,"minLength":1},"create_time":{"type":"string","description":"Date of creation.","format":"date-time"},"last_updated":{"type":"string","description":"Date of last update.","format":"date-time"}},"type":"object","required":["name"]},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/deployments/{deployment_id}/tags":{"get":{"summary":"List Tags","deprecated":false,"description":"List tags from the specified deployment.","operationId":"deployment-tag-list","tags":["Tags","Deployments","Dedicated Servers"],"parameters":[{"name":"deployment_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"limit","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"$ref":"#/components/schemas/DeploymentTagResponse"}},"total_count":{"type":"integer","minimum":0},"pagination":{"type":"object","properties":{"number":{"type":"integer","minimum":0},"next_page_number":{"type":"integer","minimum":1,"nullable":true},"previous_page_number":{"type":"integer","minimum":1,"nullable":true},"paginator":{"type":"object","properties":{"num_pages":{"type":"integer","minimum":1}},"required":["num_pages"]},"has_next":{"type":"boolean","default":true},"has_previous":{"type":"boolean","default":false}},"required":["number","next_page_number","previous_page_number","paginator","has_next","has_previous"]}},"required":["total_count","pagination"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Update Tag

> Update a tag for the specified deployment. Does not modify injected variables.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Tags"},{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"DeploymentTag":{"required":["name"],"properties":{"name":{"type":"string","description":"Name of the tag.","maxLength":40,"minLength":1}},"type":"object"},"DeploymentTagResponse":{"properties":{"name":{"type":"string","description":"Name of the tag.","maxLength":40,"minLength":1},"create_time":{"type":"string","description":"Date of creation.","format":"date-time"},"last_updated":{"type":"string","description":"Date of last update.","format":"date-time"}},"type":"object","required":["name"]},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/deployments/{deployment_id}/tags/{tag_name}":{"patch":{"summary":"Update Tag","deprecated":false,"description":"Update a tag for the specified deployment. Does not modify injected variables.","operationId":"deployment-tag-update","tags":["Tags","Deployments","Dedicated Servers"],"parameters":[{"name":"deployment_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"tag_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentTag"}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentTagResponse"}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Delete Tag

> Delete a tag for a deployment. Does not modify injected variables.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Tags"},{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/deployments/{deployment_id}/tags/{tag_name}":{"delete":{"summary":"Delete Tag","deprecated":false,"description":"Delete a tag for a deployment. Does not modify injected variables.","operationId":"deployment-tag-delete","tags":["Tags","Deployments","Dedicated Servers"],"parameters":[{"name":"deployment_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"tag_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```


# Versioning

## 📦 Apps and Versions

## Create App

> Create an app to group versions and/or split environments.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Applications"},{"name":"Versioning"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/app":{"post":{"summary":"Create App","deprecated":false,"description":"Create an app to group versions and/or split environments.","operationId":"application-create","tags":["Applications","Versioning"],"parameters":[{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Custom identifier.","minLength":3,"maxLength":64},"is_active":{"type":"boolean","description":"Enable or disable deployments.","default":true},"image":{"type":"string","description":"Image base64 string."}},"required":["image","is_active","name"]}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Custom name.","minLength":3,"maxLength":64},"is_active":{"type":"boolean","description":"Enable or disable deployments.","default":true},"image":{"type":"string","description":"Image base64 string."},"create_time":{"type":"string","description":"Date of creation."},"last_updated":{"type":"string","description":"Date of last update."}},"required":["name","is_active","image","create_time","last_updated"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Get App

> Get app details.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Applications"},{"name":"Versioning"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Application":{"required":["create_time","is_active","last_updated","name","image"],"properties":{"name":{"type":"string","description":"Custom name.","minLength":3,"maxLength":64},"is_active":{"type":"boolean","description":"Enable or disable deployments.","default":true},"image":{"type":"string","description":"Image base64 string."},"create_time":{"type":"string","description":"Date of creation."},"last_updated":{"type":"string","description":"Date of last update."}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/app/{app_name}":{"get":{"summary":"Get App","deprecated":false,"description":"Get app details.","operationId":"application-get","tags":["Applications","Versioning"],"parameters":[{"name":"app_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Application"}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## List Apps

> List apps under your organization.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Applications"},{"name":"Versioning"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Application":{"required":["create_time","is_active","last_updated","name","image"],"properties":{"name":{"type":"string","description":"Custom name.","minLength":3,"maxLength":64},"is_active":{"type":"boolean","description":"Enable or disable deployments.","default":true},"image":{"type":"string","description":"Image base64 string."},"create_time":{"type":"string","description":"Date of creation."},"last_updated":{"type":"string","description":"Date of last update."}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/apps":{"get":{"summary":"List Apps","deprecated":false,"description":"List apps under your organization.","operationId":"application-list","tags":["Applications","Versioning"],"parameters":[{"name":"page","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"limit","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"applications":{"type":"array","items":{"$ref":"#/components/schemas/Application"}},"total_count":{"type":"integer","minimum":0},"pagination":{"type":"object","properties":{"number":{"type":"integer","minimum":0},"next_page_number":{"type":"integer","minimum":1,"nullable":true},"previous_page_number":{"type":"integer","minimum":1,"nullable":true},"paginator":{"type":"object","properties":{"num_pages":{"type":"integer","minimum":1}},"required":["num_pages"]},"has_next":{"type":"boolean","default":true},"has_previous":{"type":"boolean","default":false}},"required":["number","next_page_number","previous_page_number","paginator","has_next","has_previous"]}},"required":["total_count","pagination"]}}},"headers":{}},"400":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{}}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"403":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"required":["message"]}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Update App

> Modify existing app.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Applications"},{"name":"Versioning"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Application":{"required":["create_time","is_active","last_updated","name","image"],"properties":{"name":{"type":"string","description":"Custom name.","minLength":3,"maxLength":64},"is_active":{"type":"boolean","description":"Enable or disable deployments.","default":true},"image":{"type":"string","description":"Image base64 string."},"create_time":{"type":"string","description":"Date of creation."},"last_updated":{"type":"string","description":"Date of last update."}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/app/{app_name}":{"patch":{"summary":"Update App","deprecated":false,"description":"Modify existing app.","operationId":"application-update","tags":["Applications","Versioning"],"parameters":[{"name":"app_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","required":false,"schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Application name","minLength":3,"maxLength":64},"is_active":{"type":"boolean","description":"If the application can be deployed","default":true},"image":{"type":"string","description":"Image base64 string"}}}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Application"}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Delete App

> Delete an app and its versions.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Applications"},{"name":"Versioning"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/app/{app_name}":{"delete":{"summary":"Delete App","deprecated":false,"description":"Delete an app and its versions.","operationId":"application-delete","tags":["Applications","Versioning"],"parameters":[{"name":"app_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Create App Version

> Create an app version.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Applications"},{"name":"Versioning"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"AppVersionPort":{"required":["port","protocol"],"properties":{"port":{"type":"integer","description":"Internal listener port where deployment expects connections.","minimum":0,"maximum":59999},"protocol":{"type":"string","description":"Selected networking protocol."},"to_check":{"type":"boolean","description":"Enable or disable deployment port readiness check.","default":true},"tls_upgrade":{"type":"boolean","description":"Enable or disable TLS proxy, only available with ports WS and HTTP.","default":false},"name":{"type":"string","description":"Custom port identifier."}},"type":"object"},"InjectedEnvVariable":{"required":["key","value"],"properties":{"key":{"type":"string","description":"Custom key used to access the injected variable within the deployment. Automatically sanitized."},"value":{"type":"string","description":"Custom value to inject in your deployment, up to 4 KB."},"is_hidden":{"type":"boolean","description":"Enable encryption at rest to prevent reading the value with API.","default":false}},"type":"object"},"AppVersion":{"required":["docker_image","docker_repository","docker_tag","name","req_cpu","req_memory"],"properties":{"name":{"type":"string","description":"Custom version identifier, often a timestamp or a build ID.","minLength":1,"maxLength":64},"create_time":{"type":"string","description":"Date of creation.","format":"date-time"},"last_updated":{"type":"string","description":"Date of last update.","format":"date-time"},"is_active":{"type":"boolean","description":"Enable or disable deployments.","default":true},"req_cpu":{"type":"integer","description":"Amount of vCPU units assigned to deployments. (1024 units = 1 vCPU).","minimum":100},"req_memory":{"type":"integer","description":"Amount of memory MB assigned to deployments, at most double your vCPU units. (1024 MB = 1 GB).","minimum":100},"req_video":{"type":"integer","description":"Amount of GPU units assigned to deployments. (1024 = 1 GPU)","minimum":0},"docker_repository":{"type":"string","description":"Docker registry URL, ‘docker_repository’ is named incorrectly."},"private_username":{"type":"string","description":"Docker Registry username. Not your Edgegap credentials."},"private_token":{"type":"string","description":"Docker Registry token/password. Not your Edgegap credentials."},"docker_image":{"type":"string","description":"Namespaced docker image ID."},"docker_tag":{"type":"string","description":"Docker tag ID, typically timestamp or a build ID. Avoid using `latest` or overwriting tags."},"verify_image":{"type":"boolean","description":"Enable to verify we can pull your docker image using provided parameters and credentials.","default":false},"force_cache":{"type":"boolean","description":"Enable faster deployments with global image preloading. Recommended for live games."},"caching_percent":{"type":"number","description":"Current global caching percentage on scale 0-1. Values above 0.8 (80%) are considered a good coverage level."},"time_to_deploy":{"type":"integer","description":"Image downloads exceeding this period (seconds) will timeout and be marked as Error.","minimum":15,"default":10},"max_duration":{"type":"integer","description":"Maximum allowed uptime minutes of deployments before automatically stopped.","default":1440,"minimum":-1,"maximum":1440},"restart_policy":{"type":"string","description":"Define policy for handling server process exit codes. Set to `never` to automatically stop deployments when your server process exits.","enum":["always","never","on_failure"],"default":"always"},"termination_grace_period_seconds":{"type":"integer","description":"Termination grace period in seconds after sending the SIGTERM signal to your deployment. Allows graceful shutdown and post-processing before the container stops.","minimum":5,"maximum":3600},"will_deploy_in_mainland_china":{"type":"boolean","description":"Enable China Gateway. Disable to deploy worldwide except China.","default":false},"dns_config":{"type":"string","description":"DNS configuration identifier for custom deployment domains."},"ports":{"type":"array","items":{"$ref":"#/components/schemas/AppVersionPort"},"description":"Port mapping exposing server listeners to internet."},"envs":{"type":"array","description":"Inject variables to pass parameters to your game server.","items":{"$ref":"#/components/schemas/InjectedEnvVariable"}},"endpoint_storage":{"type":"string","description":"Endpoint storage identifier to upload deployment logs, overrides app version storage if specified."}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/app/{app_name}/version":{"post":{"summary":"Create App Version","deprecated":false,"description":"Create an app version.","operationId":"app-version-create","tags":["Applications","Versioning"],"parameters":[{"name":"app_name","in":"path","description":"","required":true,"schema":{"type":"string","minLength":3,"maxLength":64}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Custom version identifier, often a timestamp or a build ID.","minLength":1,"maxLength":64},"is_active":{"type":"boolean","description":"Enable or disable deployments.","default":true},"req_cpu":{"type":"integer","description":"Amount of vCPU units assigned to deployments. (1024 units = 1 vCPU).","minimum":100},"req_memory":{"type":"integer","description":"Amount of memory MB assigned to deployments, at most double your vCPU units. (1024 MB = 1 GB).","minimum":100},"req_video":{"type":"integer","description":"Amount of GPU units assigned to deployments. (1024 = 1 GPU)","minimum":0},"docker_repository":{"type":"string","description":"Docker registry URL, ‘docker_repository’ is named incorrectly."},"private_username":{"type":"string","description":"Docker Registry username. Not your Edgegap credentials."},"private_token":{"type":"string","description":"Docker Registry token/password. Not your Edgegap credentials."},"docker_image":{"type":"string","description":"Namespaced docker image ID."},"docker_tag":{"type":"string","description":"Docker tag ID, typically timestamp or a build ID. Avoid using `latest` or overwriting tags."},"verify_image":{"type":"boolean","description":"Enable to verify we can pull your docker image using provided parameters and credentials.","default":false},"force_cache":{"type":"boolean","description":"Enable faster deployments with global image preloading. Recommended for live games."},"time_to_deploy":{"type":"integer","description":"Image downloads exceeding this period (seconds) will timeout and be marked as Error.","minimum":15,"default":10},"max_duration":{"type":"integer","description":"Maximum allowed uptime minutes of deployments before automatically stopped.","default":1440,"minimum":-1,"maximum":1440},"restart_policy":{"type":"string","enum":["always","never","on_failure"],"default":"always","description":"Define policy for handling server process exit codes. Set to `never` to automatically stop deployments when your server process exits."},"termination_grace_period_seconds":{"type":"integer","description":"Termination grace period in seconds after sending the SIGTERM signal to your deployment. Allows graceful shutdown and post-processing before the container stops.","minimum":5,"maximum":3600},"will_deploy_in_mainland_china":{"type":"boolean","description":"Enable China Gateway. Disable to deploy worldwide except China.","default":false},"dns_config":{"type":"string","description":"DNS configuration identifier for custom deployment domains."},"ports":{"type":"array","items":{"$ref":"#/components/schemas/AppVersionPort"},"description":"Port mapping exposing server listeners to internet."},"envs":{"type":"array","description":"Inject variables to pass parameters to your game server.","items":{"$ref":"#/components/schemas/InjectedEnvVariable"}},"endpoint_storage":{"type":"string","description":"Endpoint storage identifier to upload deployment logs, overrides app version storage if specified."}},"required":["docker_image","docker_repository","docker_tag","name","req_cpu","req_memory"]}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"If the creation happened correctly","default":true},"version":{"$ref":"#/components/schemas/AppVersion"}},"required":["success","version"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"424":{"description":"Failed Dependency","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Get App Version

> Get version details.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Applications"},{"name":"Versioning"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"AppVersion":{"required":["docker_image","docker_repository","docker_tag","name","req_cpu","req_memory"],"properties":{"name":{"type":"string","description":"Custom version identifier, often a timestamp or a build ID.","minLength":1,"maxLength":64},"create_time":{"type":"string","description":"Date of creation.","format":"date-time"},"last_updated":{"type":"string","description":"Date of last update.","format":"date-time"},"is_active":{"type":"boolean","description":"Enable or disable deployments.","default":true},"req_cpu":{"type":"integer","description":"Amount of vCPU units assigned to deployments. (1024 units = 1 vCPU).","minimum":100},"req_memory":{"type":"integer","description":"Amount of memory MB assigned to deployments, at most double your vCPU units. (1024 MB = 1 GB).","minimum":100},"req_video":{"type":"integer","description":"Amount of GPU units assigned to deployments. (1024 = 1 GPU)","minimum":0},"docker_repository":{"type":"string","description":"Docker registry URL, ‘docker_repository’ is named incorrectly."},"private_username":{"type":"string","description":"Docker Registry username. Not your Edgegap credentials."},"private_token":{"type":"string","description":"Docker Registry token/password. Not your Edgegap credentials."},"docker_image":{"type":"string","description":"Namespaced docker image ID."},"docker_tag":{"type":"string","description":"Docker tag ID, typically timestamp or a build ID. Avoid using `latest` or overwriting tags."},"verify_image":{"type":"boolean","description":"Enable to verify we can pull your docker image using provided parameters and credentials.","default":false},"force_cache":{"type":"boolean","description":"Enable faster deployments with global image preloading. Recommended for live games."},"caching_percent":{"type":"number","description":"Current global caching percentage on scale 0-1. Values above 0.8 (80%) are considered a good coverage level."},"time_to_deploy":{"type":"integer","description":"Image downloads exceeding this period (seconds) will timeout and be marked as Error.","minimum":15,"default":10},"max_duration":{"type":"integer","description":"Maximum allowed uptime minutes of deployments before automatically stopped.","default":1440,"minimum":-1,"maximum":1440},"restart_policy":{"type":"string","description":"Define policy for handling server process exit codes. Set to `never` to automatically stop deployments when your server process exits.","enum":["always","never","on_failure"],"default":"always"},"termination_grace_period_seconds":{"type":"integer","description":"Termination grace period in seconds after sending the SIGTERM signal to your deployment. Allows graceful shutdown and post-processing before the container stops.","minimum":5,"maximum":3600},"will_deploy_in_mainland_china":{"type":"boolean","description":"Enable China Gateway. Disable to deploy worldwide except China.","default":false},"dns_config":{"type":"string","description":"DNS configuration identifier for custom deployment domains."},"ports":{"type":"array","items":{"$ref":"#/components/schemas/AppVersionPort"},"description":"Port mapping exposing server listeners to internet."},"envs":{"type":"array","description":"Inject variables to pass parameters to your game server.","items":{"$ref":"#/components/schemas/InjectedEnvVariable"}},"endpoint_storage":{"type":"string","description":"Endpoint storage identifier to upload deployment logs, overrides app version storage if specified."}},"type":"object"},"AppVersionPort":{"required":["port","protocol"],"properties":{"port":{"type":"integer","description":"Internal listener port where deployment expects connections.","minimum":0,"maximum":59999},"protocol":{"type":"string","description":"Selected networking protocol."},"to_check":{"type":"boolean","description":"Enable or disable deployment port readiness check.","default":true},"tls_upgrade":{"type":"boolean","description":"Enable or disable TLS proxy, only available with ports WS and HTTP.","default":false},"name":{"type":"string","description":"Custom port identifier."}},"type":"object"},"InjectedEnvVariable":{"required":["key","value"],"properties":{"key":{"type":"string","description":"Custom key used to access the injected variable within the deployment. Automatically sanitized."},"value":{"type":"string","description":"Custom value to inject in your deployment, up to 4 KB."},"is_hidden":{"type":"boolean","description":"Enable encryption at rest to prevent reading the value with API.","default":false}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/app/{app_name}/version/{version_name}":{"get":{"summary":"Get App Version","deprecated":false,"description":"Get version details.","operationId":"app-version-get","tags":["Applications","Versioning"],"parameters":[{"name":"app_name","in":"path","description":"The name of the application","required":true,"schema":{"type":"string"}},{"name":"version_name","in":"path","description":"The name of the application version","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppVersion"}}},"headers":{}},"400":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"required":["message"]}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## List App Versions

> List versions under app.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Applications"},{"name":"Versioning"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"AppVersion":{"required":["docker_image","docker_repository","docker_tag","name","req_cpu","req_memory"],"properties":{"name":{"type":"string","description":"Custom version identifier, often a timestamp or a build ID.","minLength":1,"maxLength":64},"create_time":{"type":"string","description":"Date of creation.","format":"date-time"},"last_updated":{"type":"string","description":"Date of last update.","format":"date-time"},"is_active":{"type":"boolean","description":"Enable or disable deployments.","default":true},"req_cpu":{"type":"integer","description":"Amount of vCPU units assigned to deployments. (1024 units = 1 vCPU).","minimum":100},"req_memory":{"type":"integer","description":"Amount of memory MB assigned to deployments, at most double your vCPU units. (1024 MB = 1 GB).","minimum":100},"req_video":{"type":"integer","description":"Amount of GPU units assigned to deployments. (1024 = 1 GPU)","minimum":0},"docker_repository":{"type":"string","description":"Docker registry URL, ‘docker_repository’ is named incorrectly."},"private_username":{"type":"string","description":"Docker Registry username. Not your Edgegap credentials."},"private_token":{"type":"string","description":"Docker Registry token/password. Not your Edgegap credentials."},"docker_image":{"type":"string","description":"Namespaced docker image ID."},"docker_tag":{"type":"string","description":"Docker tag ID, typically timestamp or a build ID. Avoid using `latest` or overwriting tags."},"verify_image":{"type":"boolean","description":"Enable to verify we can pull your docker image using provided parameters and credentials.","default":false},"force_cache":{"type":"boolean","description":"Enable faster deployments with global image preloading. Recommended for live games."},"caching_percent":{"type":"number","description":"Current global caching percentage on scale 0-1. Values above 0.8 (80%) are considered a good coverage level."},"time_to_deploy":{"type":"integer","description":"Image downloads exceeding this period (seconds) will timeout and be marked as Error.","minimum":15,"default":10},"max_duration":{"type":"integer","description":"Maximum allowed uptime minutes of deployments before automatically stopped.","default":1440,"minimum":-1,"maximum":1440},"restart_policy":{"type":"string","description":"Define policy for handling server process exit codes. Set to `never` to automatically stop deployments when your server process exits.","enum":["always","never","on_failure"],"default":"always"},"termination_grace_period_seconds":{"type":"integer","description":"Termination grace period in seconds after sending the SIGTERM signal to your deployment. Allows graceful shutdown and post-processing before the container stops.","minimum":5,"maximum":3600},"will_deploy_in_mainland_china":{"type":"boolean","description":"Enable China Gateway. Disable to deploy worldwide except China.","default":false},"dns_config":{"type":"string","description":"DNS configuration identifier for custom deployment domains."},"ports":{"type":"array","items":{"$ref":"#/components/schemas/AppVersionPort"},"description":"Port mapping exposing server listeners to internet."},"envs":{"type":"array","description":"Inject variables to pass parameters to your game server.","items":{"$ref":"#/components/schemas/InjectedEnvVariable"}},"endpoint_storage":{"type":"string","description":"Endpoint storage identifier to upload deployment logs, overrides app version storage if specified."}},"type":"object"},"AppVersionPort":{"required":["port","protocol"],"properties":{"port":{"type":"integer","description":"Internal listener port where deployment expects connections.","minimum":0,"maximum":59999},"protocol":{"type":"string","description":"Selected networking protocol."},"to_check":{"type":"boolean","description":"Enable or disable deployment port readiness check.","default":true},"tls_upgrade":{"type":"boolean","description":"Enable or disable TLS proxy, only available with ports WS and HTTP.","default":false},"name":{"type":"string","description":"Custom port identifier."}},"type":"object"},"InjectedEnvVariable":{"required":["key","value"],"properties":{"key":{"type":"string","description":"Custom key used to access the injected variable within the deployment. Automatically sanitized."},"value":{"type":"string","description":"Custom value to inject in your deployment, up to 4 KB."},"is_hidden":{"type":"boolean","description":"Enable encryption at rest to prevent reading the value with API.","default":false}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/app/{app_name}/versions":{"get":{"summary":"List App Versions","deprecated":false,"description":"List versions under app.","operationId":"app-version-list","tags":["Applications","Versioning"],"parameters":[{"name":"app_name","in":"path","description":"The name of the application","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"versions":{"type":"array","items":{"$ref":"#/components/schemas/AppVersion"}},"total_count":{"type":"integer","minimum":0},"pagination":{"type":"object","properties":{"number":{"type":"integer","minimum":0},"next_page_number":{"type":"integer","minimum":1,"nullable":true},"previous_page_number":{"type":"integer","minimum":1,"nullable":true},"paginator":{"type":"object","properties":{"num_pages":{"type":"integer","minimum":1}},"required":["num_pages"]},"has_next":{"type":"boolean","default":true},"has_previous":{"type":"boolean","default":false}},"required":["number","next_page_number","previous_page_number","paginator","has_next","has_previous"]}},"required":["total_count","pagination"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Update App Version

> Modify existing app version.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Applications"},{"name":"Versioning"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"AppVersionPort":{"required":["port","protocol"],"properties":{"port":{"type":"integer","description":"Internal listener port where deployment expects connections.","minimum":0,"maximum":59999},"protocol":{"type":"string","description":"Selected networking protocol."},"to_check":{"type":"boolean","description":"Enable or disable deployment port readiness check.","default":true},"tls_upgrade":{"type":"boolean","description":"Enable or disable TLS proxy, only available with ports WS and HTTP.","default":false},"name":{"type":"string","description":"Custom port identifier."}},"type":"object"},"InjectedEnvVariable":{"required":["key","value"],"properties":{"key":{"type":"string","description":"Custom key used to access the injected variable within the deployment. Automatically sanitized."},"value":{"type":"string","description":"Custom value to inject in your deployment, up to 4 KB."},"is_hidden":{"type":"boolean","description":"Enable encryption at rest to prevent reading the value with API.","default":false}},"type":"object"},"AppVersion":{"required":["docker_image","docker_repository","docker_tag","name","req_cpu","req_memory"],"properties":{"name":{"type":"string","description":"Custom version identifier, often a timestamp or a build ID.","minLength":1,"maxLength":64},"create_time":{"type":"string","description":"Date of creation.","format":"date-time"},"last_updated":{"type":"string","description":"Date of last update.","format":"date-time"},"is_active":{"type":"boolean","description":"Enable or disable deployments.","default":true},"req_cpu":{"type":"integer","description":"Amount of vCPU units assigned to deployments. (1024 units = 1 vCPU).","minimum":100},"req_memory":{"type":"integer","description":"Amount of memory MB assigned to deployments, at most double your vCPU units. (1024 MB = 1 GB).","minimum":100},"req_video":{"type":"integer","description":"Amount of GPU units assigned to deployments. (1024 = 1 GPU)","minimum":0},"docker_repository":{"type":"string","description":"Docker registry URL, ‘docker_repository’ is named incorrectly."},"private_username":{"type":"string","description":"Docker Registry username. Not your Edgegap credentials."},"private_token":{"type":"string","description":"Docker Registry token/password. Not your Edgegap credentials."},"docker_image":{"type":"string","description":"Namespaced docker image ID."},"docker_tag":{"type":"string","description":"Docker tag ID, typically timestamp or a build ID. Avoid using `latest` or overwriting tags."},"verify_image":{"type":"boolean","description":"Enable to verify we can pull your docker image using provided parameters and credentials.","default":false},"force_cache":{"type":"boolean","description":"Enable faster deployments with global image preloading. Recommended for live games."},"caching_percent":{"type":"number","description":"Current global caching percentage on scale 0-1. Values above 0.8 (80%) are considered a good coverage level."},"time_to_deploy":{"type":"integer","description":"Image downloads exceeding this period (seconds) will timeout and be marked as Error.","minimum":15,"default":10},"max_duration":{"type":"integer","description":"Maximum allowed uptime minutes of deployments before automatically stopped.","default":1440,"minimum":-1,"maximum":1440},"restart_policy":{"type":"string","description":"Define policy for handling server process exit codes. Set to `never` to automatically stop deployments when your server process exits.","enum":["always","never","on_failure"],"default":"always"},"termination_grace_period_seconds":{"type":"integer","description":"Termination grace period in seconds after sending the SIGTERM signal to your deployment. Allows graceful shutdown and post-processing before the container stops.","minimum":5,"maximum":3600},"will_deploy_in_mainland_china":{"type":"boolean","description":"Enable China Gateway. Disable to deploy worldwide except China.","default":false},"dns_config":{"type":"string","description":"DNS configuration identifier for custom deployment domains."},"ports":{"type":"array","items":{"$ref":"#/components/schemas/AppVersionPort"},"description":"Port mapping exposing server listeners to internet."},"envs":{"type":"array","description":"Inject variables to pass parameters to your game server.","items":{"$ref":"#/components/schemas/InjectedEnvVariable"}},"endpoint_storage":{"type":"string","description":"Endpoint storage identifier to upload deployment logs, overrides app version storage if specified."}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/app/{app_name}/version/{version_name}":{"patch":{"summary":"Update App Version","deprecated":false,"description":"Modify existing app version.","operationId":"app-version-update","tags":["Applications","Versioning"],"parameters":[{"name":"app_name","in":"path","description":"The name of the application","required":true,"schema":{"type":"string"}},{"name":"version_name","in":"path","description":"The name of the application version","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Custom version identifier, often a timestamp or a build ID.","minLength":1,"maxLength":64},"is_active":{"type":"boolean","description":"Enable or disable deployments.","default":true},"docker_repository":{"type":"string","description":"Docker registry URL, ‘docker_repository’ is named incorrectly."},"private_username":{"type":"string","description":"Docker Registry username. Not your Edgegap credentials."},"private_token":{"type":"string","description":"Docker Registry token/password. Not your Edgegap credentials."},"docker_image":{"type":"string","description":"Namespaced docker image ID."},"docker_tag":{"type":"string","description":"Docker tag ID, typically timestamp or a build ID. Avoid using `latest` or overwriting tags."},"verify_image":{"type":"boolean","description":"Enable to verify we can pull your docker image using provided parameters and credentials.","default":false},"force_cache":{"type":"boolean","description":"Enable faster deployments with global image preloading. Recommended for live games."},"time_to_deploy":{"type":"integer","description":"Image downloads exceeding this period (seconds) will timeout and be marked as Error.","minimum":15,"default":10},"max_duration":{"type":"integer","description":"Maximum allowed uptime minutes of deployments before automatically stopped.","default":1440,"minimum":-1,"maximum":1440},"restart_policy":{"type":"string","description":"Define policy for handling server process exit codes. Set to `never` to automatically stop deployments when your server process exits.","enum":["always","never","on_failure"],"default":"always"},"termination_grace_period_seconds":{"type":"integer","description":"Termination grace period in seconds after sending the SIGTERM signal to your deployment. Allows graceful shutdown and post-processing before the container stops.","minimum":5,"maximum":3600},"will_deploy_in_mainland_china":{"type":"boolean","description":"Enable China Gateway. Disable to deploy worldwide except China.","default":false},"dns_config":{"type":"string","description":"DNS configuration identifier for custom deployment domains."},"ports":{"type":"array","items":{"$ref":"#/components/schemas/AppVersionPort"},"description":"Port mapping exposing server listeners to internet."},"envs":{"type":"array","description":"Inject variables to pass parameters to your game server.","items":{"$ref":"#/components/schemas/InjectedEnvVariable"}},"endpoint_storage":{"type":"string","description":"Endpoint storage identifier to upload deployment logs, overrides app version storage if specified."}}}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"version":{"$ref":"#/components/schemas/AppVersion"}},"required":["success"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"424":{"description":"Failed Dependency","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Delete App Version

> Delete an app version.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Applications"},{"name":"Versioning"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/app/{app_name}/version/{version_name}":{"delete":{"summary":"Delete App Version","deprecated":false,"description":"Delete an app version.","operationId":"app-version-delete","tags":["Applications","Versioning"],"parameters":[{"name":"app_name","in":"path","description":"The name of the application","required":true,"schema":{"type":"string"}},{"name":"version_name","in":"path","description":"The name of the application version","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"required":["success"]}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## 💾 Container Registry

## List Image Tags

> List all tags of a specific docker image.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Versioning"},{"name":"Container Registry"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/container-registry/images/{image_name}/tags":{"get":{"summary":"List Image Tags","deprecated":false,"description":"List all tags of a specific docker image.","operationId":"registry-image-tag-list","tags":["Container Registry","Versioning"],"parameters":[{"name":"image_name","in":"path","description":"The name of the image","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"limit","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"tag":{"type":"string","description":"Docker tag ID."},"last_push_at":{"type":"string","description":"Date of last push.","format":"date-time"},"artifact":{"description":"Build artifact linked to the tag.","allOf":[{"type":"object","properties":{"artifact_deleted":{"type":"boolean","description":"Indicates if the associated artifact has been deleted.","default":false},"remaining_tags":{"type":"array","description":"Complete list of tags associated with the artifact.","items":{"type":"string"}},"image_hash":{"type":"string","description":"Unique hash ID of the artifact."},"size_mb":{"type":"number","description":"Size of the artifact in MB."}},"required":["artifact_deleted","image_hash","remaining_tags","size_mb"]}]}},"required":["artifact","last_push_at","tag"]}}}}}},"headers":{}},"400":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"required":["message"]}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Delete Image Tag

> Delete a single tag and the associated image in the registry.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Versioning"},{"name":"Container Registry"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/container-registry/images/{image_name}/tags/{tag_name}":{"delete":{"summary":"Delete Image Tag","deprecated":false,"description":"Delete a single tag and the associated image in the registry.","operationId":"registry-image-tag-delete","tags":["Container Registry","Versioning"],"parameters":[{"name":"image_name","in":"path","description":"Docker image name.","required":true,"schema":{"type":"string"}},{"name":"tag_name","in":"path","description":"Docker tag ID.","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"artifact":{"type":"object","properties":{"artifact_deleted":{"type":"boolean","description":"Indicates if the associated artifact has been deleted.","default":true},"size_mb":{"type":"number","description":"Size of the artifact in MB."},"remaining_tags":{"type":"array","description":"Complete list of tags associated with the artifact.","items":{"type":"string"}},"image_hash":{"type":"string","description":"Unique hash ID of the artifact."}},"description":"Build artifact linked to the tag."},"deleted_tag":{"type":"string","description":"Deleted docker tag ID."}}}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Delete Image Tags

> Delete multiple tags of a registry image. Returns 207 Multi-Status with result for each tag separately. Caution is advised.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Versioning"},{"name":"Container Registry"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}}},"paths":{"/v1/container-registry/images/{image_name}/tags/bulk-delete":{"post":{"summary":"Delete Image Tags","deprecated":false,"description":"Delete multiple tags of a registry image. Returns 207 Multi-Status with result for each tag separately. Caution is advised.","operationId":"registry-image-tag-bulk-delete","tags":["Container Registry","Versioning"],"parameters":[{"name":"image_name","in":"path","description":"The name of the image","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"},"description":"Tags to delete."}},"required":["tags"]}}},"required":true},"responses":{"207":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"tag":{"type":"string","description":"Tag from your request."},"status":{"type":"integer","enum":[204,404,500],"description":"HTTP status code indicating operation result."},"message":{"type":"string","description":"Additional details."}}},"description":"Each tag receives a separate result."},"summary":{"type":"object","properties":{"total":{"type":"integer","minimum":1,"description":"Total amount of processed tags."},"success":{"type":"integer","minimum":0,"description":"Amount of successfully deleted tags."},"failed":{"type":"integer","minimum":0,"description":"Amount of tags which could not be deleted."}},"required":["total","success","failed"]}},"required":["results","summary"]}}},"headers":{}},"400":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"required":["message"]}}}},"401":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"required":["message"]}}}},"404":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"required":["message"]}}}},"500":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"required":["message"]}}}}}}}}}
```


# Integration

## ⭐ Matchmaking

Each matchmaker manages their own private API, separate from your organization's Edgegap API.

{% tabs fullWidth="false" %}
{% tab title="🍀 Simple Example" %}
{% file src="/files/xo7YWqkyH45ZTQp265pG" %}
{% endtab %}

{% tab title="🏁 Advanced Example" %}
{% file src="/files/g3XbWYhExzQqIgfozjJ2" %}
{% endtab %}

{% tab title="🎾 Custom Lobby" %}
{% file src="/files/puN4tMLXMjezZXEIDSEZ" %}
{% endtab %}

{% tab title="🥛 Backfill Showcase" %}
{% file src="/files/Qatu3klwXW4jHD6eNzxa" %}
{% endtab %}

{% tab title="⚔️ Competitive Games" %}
{% file src="/files/L7uSekelDnllYgaAOMP7" %}
{% endtab %}

{% tab title="🤝 Cooperative Games" %}
{% file src="/files/gXfaw2WbHjsioU19yvYq" %}
{% endtab %}

{% tab title="🎈 Social Games" %}
{% file src="/files/4f54lJkUXM2LnKxcvKsU" %}
{% endtab %}
{% endtabs %}

Import API specification to [Scalar API Web Client](https://client.scalar.com/workspace/default/request/default) or [Swagger Editor](https://editor.swagger.io/) to inspect details.

{% hint style="success" %}
**Swagger Web UI**: deploying your service will generate an openAPI specification and a convenient web UI. Open the URL in your browser to view and test all API endpoints, and to review payload examples.
{% endhint %}

## 🧭 Server Browser

Each matchmaker manages their own private API, separate from your organization's Edgegap API.

{% file src="/files/WTScSvL6u1oZ1GwV36gH" %}

{% hint style="success" %}
**Swagger Web UI**: deploying your service will generate an openAPI specification and a convenient web UI. Open the URL in your browser to view and test all API endpoints, and to review payload examples.
{% endhint %}

## 🗺️ Locations

## List Cloud Locations

> List all locations available for cloud deployment. Optionally specify an app version to remove locations with insufficient capacity to deploy at this time.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Deployments"},{"name":"Integration"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"LocationData":{"required":["administrative_division","city","continent","country","latitude","longitude","timezone"],"properties":{"city":{"type":"string","description":"City of the deployment."},"country":{"type":"string","description":"Country of the deployment."},"continent":{"type":"string","description":"Continent of the deployment."},"administrative_division":{"type":"string","description":"Administrative division of the deployment."},"timezone":{"type":"string","description":"Timezone of the deployment."},"latitude":{"type":"number","description":"Latitude of the deployment"},"longitude":{"type":"number","description":"Longitude of the deployment."}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/locations":{"get":{"summary":"List Cloud Locations","deprecated":false,"description":"List all locations available for cloud deployment. Optionally specify an app version to remove locations with insufficient capacity to deploy at this time.","operationId":"location-list","tags":["Deployments","Integration"],"parameters":[{"name":"app","in":"query","description":"Custom identifier.","required":false,"schema":{"type":"string"}},{"name":"version","in":"query","description":"Custom version identifier, often a timestamp or a build ID.","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"limit","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"locations":{"type":"array","items":{"$ref":"#/components/schemas/LocationData"},"description":"List of locations available for deployment at this time."},"messages":{"type":"array","description":"Additional information regarding locations.","items":{"type":"string"}},"total_count":{"type":"integer","minimum":0},"pagination":{"type":"object","properties":{"number":{"type":"integer","minimum":0},"next_page_number":{"type":"integer","minimum":1,"nullable":true},"previous_page_number":{"type":"integer","minimum":1,"nullable":true},"paginator":{"type":"object","properties":{"num_pages":{"type":"integer","minimum":1}},"required":["num_pages"]},"has_next":{"type":"boolean","default":true},"has_previous":{"type":"boolean","default":false}},"required":["number","next_page_number","previous_page_number","paginator","has_next","has_previous"]}},"required":["total_count","pagination"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## 🗼 Beacons

## List Ping Beacons

> List all active ping beacons.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"LocationData":{"required":["administrative_division","city","continent","country","latitude","longitude","timezone"],"properties":{"city":{"type":"string","description":"City of the deployment."},"country":{"type":"string","description":"Country of the deployment."},"continent":{"type":"string","description":"Continent of the deployment."},"administrative_division":{"type":"string","description":"Administrative division of the deployment."},"timezone":{"type":"string","description":"Timezone of the deployment."},"latitude":{"type":"number","description":"Latitude of the deployment"},"longitude":{"type":"number","description":"Longitude of the deployment."}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/locations/beacons":{"get":{"summary":"List Ping Beacons","deprecated":false,"description":"List all active ping beacons.","operationId":"beacon-list","tags":["Integration"],"parameters":[{"name":"page","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"limit","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"locations":{"type":"array","description":"List of all active ping beacons.","items":{"type":"object","properties":{"host":{"type":"string","description":"Public IP address of the ping beacon."},"fqdn":{"type":"string","description":"FQDN (URL) to ping the beacon."},"udp_port":{"type":"integer","description":"UDP port to ping from client.","minimum":0,"maximum":59999},"tcp_port":{"type":"integer","description":"TCP port to ping from client.","minimum":0,"maximum":59999},"location":{"$ref":"#/components/schemas/LocationData"}},"required":["host","location"]}},"count":{"type":"integer","description":"Count of returned active ping beacons."}},"required":["count"]}}},"headers":{}},"400":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"required":["message"]}}},"headers":{}},"401":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"required":["message"]}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## 🗒️ Log Storage

## Create a New Endpoint Storage

> Create an endpoint storage to store your container logs at the end of a deployment.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoint":{"post":{"summary":"Create a New Endpoint Storage","deprecated":false,"description":"Create an endpoint storage to store your container logs at the end of a deployment.","operationId":"endpoint-create","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Name of the Endpoint Storage"},"access_key_id":{"type":"string","description":"Your access key ID to connect to your S3 Bucket."},"endpoint":{"type":"string","description":"Full URL to your S3 Bucket with https or http. It's recommended to not include your bucket name as a subdomain"},"bucket":{"type":"string","description":"Your secret access key to connect to your S3 Bucket. Will be encrypted."},"output_format":{"type":"string","description":"The format how the logs will be exported to the bucket.","default":"TEXT","enum":["TEXT","NDJSON"]},"secret_access_key":{"type":"string","description":"Your secret access key to connect to your S3 Bucket. Will be encrypted."}},"required":["access_key_id","bucket","endpoint","name","secret_access_key"]}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Name of the Endpoint Storage"},"access_key_id":{"type":"string","description":"Your access key ID to connect to your S3 Bucket."},"endpoint":{"type":"string","description":"Full URL to your S3 Bucket with https or http. It's recommended to not include your bucket name as a subdomain"},"bucket":{"type":"string","description":"Your secret access key to connect to your S3 Bucket. Will be encrypted."},"output_format":{"type":"string","description":"The format how the logs will be exported to the bucket.","default":"TEXT","enum":["TEXT","NDJSON"]},"create_time":{"type":"string","description":"UTC time of endpoint creation"},"last_updated":{"type":"string","description":"UTC time of endpoint last update"}},"required":["access_key_id","bucket","endpoint","name"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Get an Endpoint Storage

> Retrieve an endpoint storage. The \`\`\`secret\_access\_key\`\`\` won't be displayed.

````json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"EndpointStorage":{"required":["access_key_id","bucket","endpoint","name"],"properties":{"name":{"type":"string","description":"Name of the Endpoint Storage"},"access_key_id":{"type":"string","description":"Your access key ID to connect to your S3 Bucket."},"endpoint":{"type":"string","description":"Full URL to your S3 Bucket with https or http. It's recommended to not include your bucket name as a subdomain"},"bucket":{"type":"string","description":"Your secret access key to connect to your S3 Bucket. Will be encrypted."},"output_format":{"type":"string","description":"The format how the logs will be exported to the bucket.","default":"TEXT","enum":["TEXT","NDJSON"]},"create_time":{"type":"string","description":"UTC time of endpoint storage creation"},"last_updated":{"type":"string","description":"UTC time of endpoint storage last update"}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoint/{endpoint_name}":{"get":{"summary":"Get an Endpoint Storage","deprecated":false,"description":"Retrieve an endpoint storage. The ```secret_access_key``` won't be displayed.","operationId":"endpoint-get","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"endpoint_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EndpointStorage"}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
````

## List All Endpoint Storage

> List all endpoint storage.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"EndpointStorage":{"required":["access_key_id","bucket","endpoint","name"],"properties":{"name":{"type":"string","description":"Name of the Endpoint Storage"},"access_key_id":{"type":"string","description":"Your access key ID to connect to your S3 Bucket."},"endpoint":{"type":"string","description":"Full URL to your S3 Bucket with https or http. It's recommended to not include your bucket name as a subdomain"},"bucket":{"type":"string","description":"Your secret access key to connect to your S3 Bucket. Will be encrypted."},"output_format":{"type":"string","description":"The format how the logs will be exported to the bucket.","default":"TEXT","enum":["TEXT","NDJSON"]},"create_time":{"type":"string","description":"UTC time of endpoint storage creation"},"last_updated":{"type":"string","description":"UTC time of endpoint storage last update"}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoints":{"get":{"summary":"List All Endpoint Storage","deprecated":false,"description":"List all endpoint storage.","operationId":"endpoints-list","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"page","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"limit","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"X-Fields","in":"header","description":"An optional fields mask","required":false,"schema":{"type":"string","format":"mask"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"endpoints":{"type":"array","items":{"$ref":"#/components/schemas/EndpointStorage"}},"pagination":{}}}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Update an Endpoint Storage

> Update an Endpoint Storage with new specifications.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoint/{endpoint_name}":{"patch":{"summary":"Update an Endpoint Storage","deprecated":false,"description":"Update an Endpoint Storage with new specifications.","operationId":"endpoint-update","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"endpoint_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Name of the Endpoint Storage"},"access_key_id":{"type":"string","description":"Your access key ID to connect to your S3 Bucket."},"secret_access_key":{"type":"string","description":"Your secret access key to connect to your S3 Bucket. Will be\n            encrypted."},"endpoint":{"type":"string","description":"Full URL to your S3 Bucket with https or http. It's recommended to not include your bucket name as a subdomain"},"bucket":{"type":"string","description":"Your secret access key to connect to your S3 Bucket. Will be encrypted."},"output_format":{"type":"string","description":"The format how the logs will be exported to the bucket.","default":"TEXT","enum":["TEXT","NDJSON"]}}}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Name of the Endpoint Storage"},"access_key_id":{"type":"string","description":"Your access key ID to connect to your S3 Bucket."},"endpoint":{"type":"string","description":"Full URL to your S3 Bucket with https or http. It's recommended to not include your bucket name as a subdomain"},"bucket":{"type":"string","description":"Your secret access key to connect to your S3 Bucket. Will be encrypted."},"output_format":{"type":"string","description":"The format how the logs will be exported to the bucket.","default":"TEXT","enum":["TEXT","NDJSON"]},"create_time":{"type":"string","description":"UTC time of endpoint storage creation"},"last_updated":{"type":"string","description":"UTC time of endpoint storage last update"}}}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Delete an Endpoint Storage

> Delete an endpoint storage. All the application versions linked to it won't be able to store logs anymore.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoint/{endpoint_name}":{"delete":{"summary":"Delete an Endpoint Storage","deprecated":false,"description":"Delete an endpoint storage. All the application versions linked to it won't be able to store logs anymore.","operationId":"endpoint-delete","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"endpoint_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Create a New Pull Profile

> Create a pull profile. Pull profile will upload data from an endpoint storage to a deployment container on boot. You must link the application version to the pull profile first.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoint/{endpoint_name}/pull-profile":{"post":{"summary":"Create a New Pull Profile","deprecated":false,"description":"Create a pull profile. Pull profile will upload data from an endpoint storage to a deployment container on boot. You must link the application version to the pull profile first.","operationId":"pull-profile-create","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"endpoint_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Name of the pull profile"},"source":{"type":"string","description":"Source in the S3 bucket to fetch from"},"source_type":{"type":"string","description":"If the source is a File or a Directory","enum":["File","Folder"]},"destination":{"type":"string","description":"Destination path where your source will be uploaded in your container. Make sure to avoid protected destinations, such as `/etc/`, as this will prevent the files from being copied to your deployment, and will make your deployment fail. Make sure a normal user can write to the destination folder."}},"required":["destination","name","source","source_type"]}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Name of the pull profile"},"source":{"type":"string","description":"Source in the S3 bucket to fetch from"},"source_type":{"type":"string","description":"If the source is a File or a Directory","enum":["File","Folder"]},"destination":{"type":"string","description":"Destination path where your source will be uploaded in your container. Make sure to avoid protected destinations, such as `/etc/`, as this will prevent the files from being copied to your deployment, and will make your deployment fail. Make sure a normal user can write to the destination folder."},"create_time":{"type":"string","description":"UTC time of pull profile creation"},"last_updated":{"type":"string","description":"UTC time of pull profile last update"}},"required":["destination","name","source","source_type"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Get a Pull Profile

> Retrieve a pull profile and its specifications.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"PullProfile":{"required":["destination","name","source","source_type"],"properties":{"name":{"type":"string","description":"Name of the pull profile"},"source":{"type":"string","description":"Source in the S3 bucket to fetch from"},"source_type":{"type":"string","description":"If the source is a File or a Directory","enum":["File","Folder"]},"destination":{"type":"string","description":"Destination path where your source will be uploaded in your container. Make sure to avoid protected destinations, such as `/etc/`, as this will prevent the files from being copied to your deployment, and will make your deployment fail. Make sure a normal user can write to the destination folder."},"create_time":{"type":"string","description":"UTC time of pull profile creation"},"last_updated":{"type":"string","description":"UTC time of pull profile last update"}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoint/{endpoint_name}/pull-profile/{pull_profile_name}":{"get":{"summary":"Get a Pull Profile","deprecated":false,"description":"Retrieve a pull profile and its specifications.","operationId":"pull-profile-get","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"endpoint_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"pull_profile_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PullProfile"}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## List All Pull Profile of an Endpoint Storage

> List all pull profiles of an endpoint storage.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"PullProfile":{"required":["destination","name","source","source_type"],"properties":{"name":{"type":"string","description":"Name of the pull profile"},"source":{"type":"string","description":"Source in the S3 bucket to fetch from"},"source_type":{"type":"string","description":"If the source is a File or a Directory","enum":["File","Folder"]},"destination":{"type":"string","description":"Destination path where your source will be uploaded in your container. Make sure to avoid protected destinations, such as `/etc/`, as this will prevent the files from being copied to your deployment, and will make your deployment fail. Make sure a normal user can write to the destination folder."},"create_time":{"type":"string","description":"UTC time of pull profile creation"},"last_updated":{"type":"string","description":"UTC time of pull profile last update"}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoint/{endpoint_name}/pull-profiles":{"get":{"summary":"List All Pull Profile of an Endpoint Storage","deprecated":false,"description":"List all pull profiles of an endpoint storage.","operationId":"pull-profile-list","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"endpoint_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"limit","in":"query","description":"","schema":{"type":"integer","minimum":1}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"pull_profiles":{"type":"array","items":{"$ref":"#/components/schemas/PullProfile"}},"total_count":{"type":"integer","minimum":0},"pagination":{"type":"object","properties":{"number":{"type":"integer","minimum":0},"next_page_number":{"type":"integer","minimum":1,"nullable":true},"previous_page_number":{"type":"integer","minimum":1,"nullable":true},"paginator":{"type":"object","properties":{"num_pages":{"type":"integer","minimum":1}},"required":["num_pages"]},"has_next":{"type":"boolean","default":true},"has_previous":{"type":"boolean","default":false}},"required":["number","next_page_number","previous_page_number","paginator","has_next","has_previous"]}},"required":["total_count","pagination"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Link a Pull Profile to an Application Version

> Link a pull profile to an app version. Without a link, the pull profile by itself will do nothing.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoint/{endpoint_name}/pull-profile/{pull_profile_name}/app/{app_name}/version/{version_name}":{"put":{"summary":"Link a Pull Profile to an Application Version","deprecated":false,"description":"Link a pull profile to an app version. Without a link, the pull profile by itself will do nothing.","operationId":"pull-profile-link-app-version","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"endpoint_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"pull_profile_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"app_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"version_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"app":{"type":"string","description":"Name of the linked app of the linked version"},"app_version":{"type":"string","description":"Name of the linked app version."},"endpoint":{"type":"string","description":"Name of the endpoint storage"},"pull_profile":{"type":"string","description":"Name of the pull profile the app version is linked to."},"create_time":{"type":"string","description":"UTC time of link creation"},"last_updated":{"type":"string","description":"UTC time of link last update"}},"required":["app","app_version","endpoint","pull_profile"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Update a Pull Profile

> Update a pull profile with new specifications.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoint/{endpoint_name}/pull-profile/{pull_profile_name}":{"patch":{"summary":"Update a Pull Profile","deprecated":false,"description":"Update a pull profile with new specifications.","operationId":"pull-profile-update","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"endpoint_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"pull_profile_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Name of the pull profile"},"source":{"type":"string","description":"Source in the S3 bucket to fetch from"},"source_type":{"type":"string","description":"If the source is a File or a Directory","enum":["File","Folder"]},"destination":{"type":"string","description":"Destination path where your source will be uploaded in your container. Make sure to avoid protected destinations, such as `/etc/`, as this will prevent the files from being copied to your deployment, and will make your deployment fail. Make sure a normal user can write to the destination folder."},"create_time":{"type":"string","description":"UTC time of pull profile creation"},"last_updated":{"type":"string","description":"UTC time of pull profile last update"}}}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Unlink a Pull Profile From an Application Version

> Unlink a pull profile from an app version. It will not delete the pull profile.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoint/{endpoint_name}/pull-profile/{pull_profile_name}/app/{app_name}/version/{version_name}":{"delete":{"summary":"Unlink a Pull Profile From an Application Version","deprecated":false,"description":"Unlink a pull profile from an app version. It will not delete the pull profile.","operationId":"pull-profile-unlink-app-version","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"endpoint_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"pull_profile_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"app_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"version_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"204":{"description":"Success","headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Delete a Pull Profile

> Delete a pull profile. All the application versions linked won't receive the data upload anymore. It will not delete your endpoint storage.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Integration"},{"name":"Endpoint Storage"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/storage/endpoint/{endpoint_name}/pull-profile/{pull_profile_name}":{"delete":{"summary":"Delete a Pull Profile","deprecated":false,"description":"Delete a pull profile. All the application versions linked won't receive the data upload anymore. It will not delete your endpoint storage.","operationId":"pull-profile-delete","tags":["Endpoint Storage","Integration"],"parameters":[{"name":"endpoint_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"pull_profile_name","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```


# Relays

## 📫 Relays

## Create a Relay Session

> Create a relay session with users.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Relays"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"RelaySession":{"required":["linked","ready","session_id","status"],"properties":{"session_id":{"type":"string","description":"The session ID"},"authorization_token":{"type":"integer","description":"The authorization token for the session"},"status":{"type":"string","description":"The status of the session"},"ready":{"type":"boolean","description":"If the session is ready to be used"},"linked":{"type":"boolean","description":"If the session is linked to a relay"},"error":{"type":"string","description":"The error message if the session failed"},"session_users":{"type":"array","description":"List Session Users","items":{"type":"object","properties":{"ip_address":{"type":"string","description":"The IP Address of the user"},"latitude":{"type":"number","description":"The latitude of the user"},"longitude":{"type":"number","description":"The longitude of the user"},"authorization_token":{"type":"integer","description":"The authorization token of the user"}}}},"relay":{"description":"The relay details the session is linked to","allOf":[{"type":"object","properties":{"ip":{"type":"string","description":"The IP Address of the relay"},"host":{"type":"string","description":"The host of the relay"},"ports":{"type":"object","properties":{"server":{"type":"object","properties":{"port":{"type":"integer","description":"The port number"},"protocol":{"type":"string","description":"The protocol of the port"},"link":{"type":"string","description":"The link to the port"}}},"client":{"type":"object","properties":{"port":{"type":"integer","description":"The port number"},"protocol":{"type":"string","description":"The protocol of the port"},"link":{"type":"string","description":"The link to the port"}}}}}}}]},"webhook_url":{"type":"string","description":"The webhook URL that we will call once the session is ready"}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/relays/sessions":{"post":{"summary":"Create a Relay Session","deprecated":false,"description":"Create a relay session with users.","operationId":"relay-session-create","tags":["Relays"],"parameters":[{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"users":{"type":"array","description":"List of IP Addresses of session users","minItems":1,"items":{}},"filters":{"type":"array","description":"List of location filters to apply to the session","items":{}},"webhook_url":{"type":"string","description":"URL to send the session data to"}},"required":["users"]}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RelaySession"}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Get a Relay Session

> Retrieve the information for a relay session.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Relays"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"RelaySession":{"required":["linked","ready","session_id","status"],"properties":{"session_id":{"type":"string","description":"The session ID"},"authorization_token":{"type":"integer","description":"The authorization token for the session"},"status":{"type":"string","description":"The status of the session"},"ready":{"type":"boolean","description":"If the session is ready to be used"},"linked":{"type":"boolean","description":"If the session is linked to a relay"},"error":{"type":"string","description":"The error message if the session failed"},"session_users":{"type":"array","description":"List Session Users","items":{"type":"object","properties":{"ip_address":{"type":"string","description":"The IP Address of the user"},"latitude":{"type":"number","description":"The latitude of the user"},"longitude":{"type":"number","description":"The longitude of the user"},"authorization_token":{"type":"integer","description":"The authorization token of the user"}}}},"relay":{"description":"The relay details the session is linked to","allOf":[{"type":"object","properties":{"ip":{"type":"string","description":"The IP Address of the relay"},"host":{"type":"string","description":"The host of the relay"},"ports":{"type":"object","properties":{"server":{"type":"object","properties":{"port":{"type":"integer","description":"The port number"},"protocol":{"type":"string","description":"The protocol of the port"},"link":{"type":"string","description":"The link to the port"}}},"client":{"type":"object","properties":{"port":{"type":"integer","description":"The port number"},"protocol":{"type":"string","description":"The protocol of the port"},"link":{"type":"string","description":"The link to the port"}}}}}}}]},"webhook_url":{"type":"string","description":"The webhook URL that we will call once the session is ready"}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/relays/sessions/{session_id}":{"get":{"summary":"Get a Relay Session","deprecated":false,"description":"Retrieve the information for a relay session.","operationId":"relay-session-get","tags":["Relays"],"parameters":[{"name":"session_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RelaySession"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## List all Relay Sessions

> List all the active relay sessions.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Relays"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"RelaySession":{"required":["linked","ready","session_id","status"],"properties":{"session_id":{"type":"string","description":"The session ID"},"authorization_token":{"type":"integer","description":"The authorization token for the session"},"status":{"type":"string","description":"The status of the session"},"ready":{"type":"boolean","description":"If the session is ready to be used"},"linked":{"type":"boolean","description":"If the session is linked to a relay"},"error":{"type":"string","description":"The error message if the session failed"},"session_users":{"type":"array","description":"List Session Users","items":{"type":"object","properties":{"ip_address":{"type":"string","description":"The IP Address of the user"},"latitude":{"type":"number","description":"The latitude of the user"},"longitude":{"type":"number","description":"The longitude of the user"},"authorization_token":{"type":"integer","description":"The authorization token of the user"}}}},"relay":{"description":"The relay details the session is linked to","allOf":[{"type":"object","properties":{"ip":{"type":"string","description":"The IP Address of the relay"},"host":{"type":"string","description":"The host of the relay"},"ports":{"type":"object","properties":{"server":{"type":"object","properties":{"port":{"type":"integer","description":"The port number"},"protocol":{"type":"string","description":"The protocol of the port"},"link":{"type":"string","description":"The link to the port"}}},"client":{"type":"object","properties":{"port":{"type":"integer","description":"The port number"},"protocol":{"type":"string","description":"The protocol of the port"},"link":{"type":"string","description":"The link to the port"}}}}}}}]},"webhook_url":{"type":"string","description":"The webhook URL that we will call once the session is ready"}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/relays/sessions":{"get":{"summary":"List all Relay Sessions","deprecated":false,"description":"List all the active relay sessions.","operationId":"relay-session-list","tags":["Relays"],"parameters":[{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"sessions":{"type":"array","description":"List of relay sessions","items":{"$ref":"#/components/schemas/RelaySession"}},"total_count":{"type":"integer","minimum":0},"pagination":{"type":"object","properties":{"number":{"type":"integer","minimum":0},"next_page_number":{"type":"integer","minimum":1,"nullable":true},"previous_page_number":{"type":"integer","minimum":1,"nullable":true},"paginator":{"type":"object","properties":{"num_pages":{"type":"integer","minimum":1}},"required":["num_pages"]},"has_next":{"type":"boolean","default":true},"has_previous":{"type":"boolean","default":false}},"required":["number","next_page_number","previous_page_number","paginator","has_next","has_previous"]}},"required":["total_count","pagination"]}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Authorize a user on a Relay Session

> Authorize a user on a Relay Session

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Relays"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/relays/sessions:authorize-user":{"post":{"summary":"Authorize a user on a Relay Session","deprecated":false,"description":"Authorize a user on a Relay Session","operationId":"relay-user-authorize","tags":["Relays"],"parameters":[{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string","description":"Session Request ID (ex: 0724828881e2-S)"},"user_ip":{"type":"string","description":"User Ip to Authorize"}},"required":["session_id","user_ip"]}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string","description":"The session ID"},"authorization_token":{"type":"integer","description":"The authorization token for the session"},"status":{"type":"string","description":"The status of the session"},"ready":{"type":"boolean","description":"If the session is ready to be used"},"linked":{"type":"boolean","description":"If the session is linked to a relay"},"error":{"type":"string","description":"The error message if the session failed"},"session_user":{"description":"List Session Users","allOf":[{}]},"relay":{"description":"The relay details the session is linked to","allOf":[{}]},"webhook_url":{"type":"string","description":"The webhook URL that we will call once the session is ready"}},"required":["linked","ready","session_id","status"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Remove a user on a Relay Session

> Authorize a user on a Relay Session

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Relays"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"RelaySession":{"required":["linked","ready","session_id","status"],"properties":{"session_id":{"type":"string","description":"The session ID"},"authorization_token":{"type":"integer","description":"The authorization token for the session"},"status":{"type":"string","description":"The status of the session"},"ready":{"type":"boolean","description":"If the session is ready to be used"},"linked":{"type":"boolean","description":"If the session is linked to a relay"},"error":{"type":"string","description":"The error message if the session failed"},"session_users":{"type":"array","description":"List Session Users","items":{"type":"object","properties":{"ip_address":{"type":"string","description":"The IP Address of the user"},"latitude":{"type":"number","description":"The latitude of the user"},"longitude":{"type":"number","description":"The longitude of the user"},"authorization_token":{"type":"integer","description":"The authorization token of the user"}}}},"relay":{"description":"The relay details the session is linked to","allOf":[{"type":"object","properties":{"ip":{"type":"string","description":"The IP Address of the relay"},"host":{"type":"string","description":"The host of the relay"},"ports":{"type":"object","properties":{"server":{"type":"object","properties":{"port":{"type":"integer","description":"The port number"},"protocol":{"type":"string","description":"The protocol of the port"},"link":{"type":"string","description":"The link to the port"}}},"client":{"type":"object","properties":{"port":{"type":"integer","description":"The port number"},"protocol":{"type":"string","description":"The protocol of the port"},"link":{"type":"string","description":"The link to the port"}}}}}}}]},"webhook_url":{"type":"string","description":"The webhook URL that we will call once the session is ready"}},"type":"object"},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/relays/sessions:revoke-user":{"post":{"summary":"Remove a user on a Relay Session","deprecated":false,"description":"Authorize a user on a Relay Session","operationId":"relay-user-revoke","tags":["Relays"],"parameters":[{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string","description":"Session Request ID (ex: 0724828881e2-S)"},"authorization_token":{"type":"integer","description":"Relay Authorization Token"}},"required":["authorization_token","session_id"]}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RelaySession"}}},"headers":{}},"204":{"description":"No Content","headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```

## Delete a Relay Session

> Delete a relay session.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Relays"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/relays/sessions/{session_id}":{"delete":{"summary":"Delete a Relay Session","deprecated":false,"description":"Delete a relay session.","operationId":"relay-session-delete","tags":["Relays"],"parameters":[{"name":"session_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"responses":{"204":{"description":"No Content","headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```


# Deprecated

{% hint style="warning" %}
Legacy v1/deploy API endpoint is **outdated, and has been replaced by v2 API** - see [Dedicated Servers](/docs/api/dedicated-servers).
{% endhint %}

## ⚠️ Deploy Server

> DEPRECATED. \[Rate Limit: 40 req/s] Create a new deployment - containerized instance of an application version.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"InjectedEnvVariable":{"required":["key","value"],"properties":{"key":{"type":"string","description":"Custom key used to access the injected variable within the deployment. Automatically sanitized."},"value":{"type":"string","description":"Custom value to inject in your deployment, up to 4 KB."},"is_hidden":{"type":"boolean","description":"Enable encryption at rest to prevent reading the value with API.","default":false}},"type":"object"},"V1DeployFilter":{"type":"object","properties":{"field":{"type":"string","description":"Deployment field used for filtering."},"values":{"type":"array","description":"Values to compare against.","items":{"type":"string"}},"filter_type":{"type":"string","description":"Filter operator","enum":["any","all","not"]}},"required":["field","filter_type","values"]},"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/deploy":{"post":{"summary":"⚠️ Deploy Server","deprecated":true,"description":"DEPRECATED. [Rate Limit: 40 req/s] Create a new deployment - containerized instance of an application version.","operationId":"deployment-create","tags":["Deployments","Dedicated Servers"],"parameters":[{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"app_name":{"type":"string","description":"Application to deploy."},"version_name":{"type":"string","description":"Version to deploy. If omitted, last created version will be deployed."},"ip_list":{"type":"array","items":{"type":"string"},"description":"Mutually exclusive with geo_ip_list. List of user IPs, determining server location choice."},"geo_ip_list":{"type":"array","description":"Mutually exclusive with ip_list. List of user IPs and coordinates, determining server location choice. Coordinates override IP address.","items":{"type":"object","properties":{"ip":{"type":"string","description":"User public IP address."},"latitude":{"type":"number","description":"User latitude."},"longitude":{"type":"number","description":"User longitude."}},"required":["ip","latitude","longitude"]}},"filters":{"type":"array","description":"Restricts possible locations for the deployment.","items":{"type":"object","properties":{"field":{"type":"string","description":"Deployment field used for filtering."},"values":{"type":"array","description":"Values to compare against.","items":{"type":"string"}},"filter_type":{"type":"string","description":"Filter operator.","enum":["any","all","not"]}},"required":["field","filter_type","values"]}},"env_vars":{"type":"array","description":"List of environment variables to set in your deployment instance. These variables will be available in your container.","items":{"$ref":"#/components/schemas/InjectedEnvVariable"}},"tags":{"type":"array","description":"List of tags to mark your deployments for easy filtering.","items":{"type":"string"}},"webhook_url":{"type":"string","description":"URL to receive simple HTTP notifications in your game backend when the new deployment becomes Ready."},"container_log_storage":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enable or disable uploading logs."},"endpoint_storage":{"type":"string","description":"Endpoint storage identifier to upload deployment logs."}},"required":["enabled","endpoint_storage"],"description":"Endpoint storage to upload deployment logs."},"command":{"description":"Overrides container command for the deployment, use with caution.","type":"string","nullable":true},"arguments":{"description":"Overrides container arguments for the deployment, use with caution.","type":"string","nullable":true}},"required":["app_name"]}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"request_id":{"type":"string","description":"ID of your deployment, also referred to as request ID."},"request_dns":{"type":"string","description":"FQDN (URL) to connect to the deployment."},"request_app":{"type":"string","description":"Application which has been deployed."},"request_version":{"type":"string","description":"App version which has been deployed."},"request_user_count":{"type":"integer","description":"Number of users included in the deployment request.","minimum":1},"filters":{"type":"array","description":"List of filters applied to choose the location of the deployment.","items":{"$ref":"#/components/schemas/V1DeployFilter"}},"tags":{"type":"array","description":"List of tags to mark your deployments for easy filtering.","items":{"type":"string"}},"container_log_storage":{"description":"Object storage to upload deployment logs."}},"required":["request_app","request_dns","request_id","request_user_count","request_version","filters"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"","content":{"application/json":{"schema":{"title":"","type":"object","properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"required":["message"]}}},"headers":{}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"422":{"description":"Unprocessable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{}}}},"headers":{}}}}}}}
```

## ⚠️ Updates properties of a deployment

> DEPRECATED. Updates properties of a deployment. Currently only the \`is\_joinable\_by\_session\` property can be updated.

```json
{"openapi":"3.0.1","info":{"title":"Edgegap v1 API","version":"2026.04.24"},"tags":[{"name":"Deployments"},{"name":"Dedicated Servers"}],"servers":[{"url":"https://api.edgegap.com","description":"https://api.edgegap.com"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"Authorization","description":"[Manage API tokens in dashboard.](https://app.edgegap.com/user-settings?tab=tokens)"}},"schemas":{"Error":{"required":["message"],"properties":{"message":{"type":"string","description":"A message depending of the request termination"}},"type":"object"}}},"paths":{"/v1/deployments/{request_id}":{"patch":{"summary":"⚠️ Updates properties of a deployment","deprecated":true,"description":"DEPRECATED. Updates properties of a deployment. Currently only the `is_joinable_by_session` property can be updated.","operationId":"deployment-update","tags":["Deployments","Dedicated Servers"],"parameters":[{"name":"request_id","in":"path","description":"","required":true,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"","schema":{"type":"string"}},{"name":"Content-Type","in":"header","description":"","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"is_joinable_by_session":{"type":"boolean","description":"Whether a session can connect to this deployment or not."}}}}},"required":true},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"is_joinable_by_session":{"type":"boolean","description":"If the deployment is joinable by sessions"}},"required":["is_joinable_by_session"]}}},"headers":{}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{}}}}}}}
```


# Release Notes

{% hint style="info" %}
View older Release Notes in our [Archive](/docs/release-notes/archive).
{% endhint %}

### 2026.06.11 (Latest)

![](/files/a1kT40Kzvb0tyEB9XYOL)

**New ✨**

* [Unity SDK](/unity/developer-tools#software-development-kit) v3.2.2 brings full Matchmaking integration for Group Up flow and various [observable event](/unity/matchmaking#observe-events) additions related to Groups. Explore updated [Matchmaking](/unity/matchmaking#simple-example) and a brand new [Matchmaking](/unity/matchmaking#region-picker) to draw inspiration for manual region selection UI. Stay tuned for more examples and updates coming soon!

***

### 2026.06.01

![](/files/A75cRNsLJqiKuGN3ybwe) ![](/files/vCSBzlo4GmMkPxotRWoW)

**New ✨**

* Test deployments using our [new wizard UI ](https://app.edgegap.com/deployment-management/deployments/create)(now supports private fleets).
* View allocated resources directly on deployment details page.

**Improvements 💪**

* Unified billing forms for easier billing details' changes.

**Fixed ✔**

* Fixed a minor private fleet host pinning issue.
* Fixed broken links in the release notes.

***

### 2026.05.19

![](/files/A75cRNsLJqiKuGN3ybwe)

**New ✨**

* Prevent losing track of autorestarted servers caused by running out of memory or unexpected error - configure [Process Restart Policies](/learn/orchestration/application-and-versions#safety-guardrails) for your app version in dashboard or with API.
* [Configure billing alerts](https://app.edgegap.com/notifications) to receive emails when on-demand cost exceeds custom thresholds.

**Improvements 💪**

* Improved visual indication for inviting members on the [Settings page](https://app.edgegap.com/user-settings?tab=organizations).

**Fixed ✔**

* Fixed overlapping map and delete modal in the create scaling policy section on the Server Browser page.

***

### 2026.05.13

![](/files/a1kT40Kzvb0tyEB9XYOL) ![](/files/vB2zNNVOm174oel9ustB) ![](/files/0lRCbvAkph29qqVn3IuX) ![](/files/vCSBzlo4GmMkPxotRWoW)

**New ✨**

[Unity SDK](/unity/developer-tools#software-development-kit) v3.0.0, introducing a major update:

* Introducing [Server Browser SDK features](/unity/server-browser) with full integration examples for server and client.
* New Unity integration and customization guides for [Matchmaking](/unity/matchmaking) and [Server Browser](/unity/server-browser).
* Stop servers and read [Injected Variables](/learn/matchmaking/matchmaker-in-depth#injected-variables) with expanded Matchmaking Simple Example.
* Stop servers and read [Deployments](/learn/orchestration/deployments#injected-environment-variables) with new Deployment Agent sample, supports projects which do not use Matchmaking or Server Browser.

[Server Browser](/learn/server-browser) v1.0.0, now entering General Availability:

* Find servers in your region quickly and easily with [Auto-Assigned Seat Reservations](/learn/server-browser#auto-assigned-reservation).
* Disable Scaling Policies temporarily by setting `status`  to `draft`  or `archive` .
* Update existing Scaling Policies with a new `PATCH`  method.

[Container Registry](/learn/advanced-features/edgegap-container-registry) improvements for Continuous Integrations:

* Bulk delete tags [with dashboard](https://app.edgegap.com/registry-management/repositories/list) (up to 20 tags) or with API (up to 50 tags).
* Request additional registry storage [with dashboard](https://app.edgegap.com/registry-management/repositories/list) :information\_source: icon.

**Breaking Changes** ⚠️

[Unity SDK](/unity/developer-tools#software-development-kit) v3.0.0 - in namespace `Edgegap.Matchmaking`:

* Merged method `Client._Abandon -> Client.StopMatchmaking` .
  * Low Impact - mostly customized client handler implementations.
  * Resolution - replace `_Abandon`  calls with `StopMatchmaking`  method.
* Merged event listener `onTicketUpdate -> onAssignmentUpdate`.
  * Low Impact - most implementations didn't need to use this listener.
  * Resolution - find and replace with [new events](/unity/matchmaking#client-events) under `onAssignmentUpdate`  listener.
* Decoupled `Ping` , `SafeHttpRequest` , and `Observable` . Moved to namespace `Edgegap` .
  * Low Impact - functionality remains the same, simply moved to another namespace.
  * Resolution - update import statements or namespaced references as needed.
* Removed caching feature due to poor console compatibility and onboarding friction.
  * Low Impact - crashed players may create duplicate tickets instead of resuming from cache.
  * Resolution - implement custom cache using `onAssignmentUpdate`  event listener.
* Renamed assembly `com.edgegap.unity-matchmaking-sdk -> com.edgegap.unity-sdk` .
  * Low Impact - some highly customized build pipelines referencing this SDK.
  * Resolution - update SDK assembly name references in your custom tools.

[Server Browser](/learn/server-browser) v1.0.0:

* Modified API path separator from `:` to `/`, specifically `:keep-alive` and `:confirmations`.
* Removed metadata update merging behavior - omitted keys will be removed.

**Improvements 💪**

[Server Browser](/learn/server-browser) v1.0.0:

* Added complete information of all slots to the `GET` server instance API response.
* Added more detailed service health indications in monitoring API.
* Various openapi specification improvements for examples and response codes.
* Improved error messages for scaling policy builder form in dashboard.

[Unity SDK](/unity/developer-tools#software-development-kit) v3.0.0 - in namespace `Edgegap.Matchmaking`:

* Mitigated duplicate tickets belonging to the same player by adding conflict detection.
* Exposed more matchmaking error events for custom error handling and UI integration.
* Improved customization and reuse by making all utilities scripts public.
* Simplified [parametrized events](/unity/matchmaking#client-events)' filtering with unified syntax, e.g. `updated [{status}]` .
* Improved customization for request backoff and retry parameters.

[Private Fleets](/learn/orchestration/private-fleets) improved operations:

* [List private fleet hosts API](/docs/api/dedicated-servers#private-fleets) now only returns active hosts (excl. hosts in maintenance).

Other improvements:

* Keep track of your releases with [Analytics](https://app.edgegap.com/analytics/dashboards/list) container count displaying up to 7 days of data.
* Display redacted billing page to unprivileged users in dashboard. Sensitive information still requires organization Administrator or Owner privileges (ask your organization owner).
* Deployment archive now uses a default filter unless a user-defined filter is available.

**Fixed ✔**

[Unity SDK](/unity/developer-tools#software-development-kit) v3.0.0:

* Improved reliability of matchmaking queue abandonment for sudden client exit - APIs no longer apply backoff waiting period on the first (non-retry) attempt.
* Prevented code stripping for Android and iOS builds using `AlwaysLinkAssembly`  annotation.

Other fixes:

* Prevent silent fail when bulk deleting versions including a version(s) used by a Matchmaker.
* Caching indicator will only show up once a version is saved with active caching enabled (UI fix).

***

### 2026.04.14

**New ✨**

* Manage your Scaling Policies now directly in Server Browser dashboard.
* View CPU throttling metrics in deployment history metrics to help identify applications with insufficient CPU allocation or code-related CPU spikes.
* [Billing page](https://app.edgegap.com/user-settings?tab=invoices) now shows a detailed breakdown of your usage and auto-renewals.


# Archive

## 2026

<details>

<summary>View Release Notes 2026</summary>

### 2026.03.30

**New ✨**

* Deployment wizard in dashboard now uses user IP by default, and allows customization.

**Improvements 💪**

* Improved self-healing capabilities for private fleet hosts to ensure high availability.
* Updated Server Browser example configurations on the dashboard to use the latest version.
* Updated Matchmaker example configurations on the dashboard to use the latest version.
* Updated Clusters to a newer Kubernetes version with no action required on your end.
* Updated Fishnet sample application when creating a new version in the dashboard.

**Fixed ✔**

* Fixed private fleet host status icons not displaying in dashboard.
* Fixed filtering by tags in dashboard deployments table.
* Fixed link rendering in dashboard release notes.
* Fixed missing translations in dashboard

### 2026.03.25

**New ✨**

* Filter deployments by private fleet name with [the API](/learn/orchestration/deployments#filter-deployments) and [dashboard](https://app.edgegap.com/deployment-management/deployments/list).

**Improvements 💪**

* Deployment tags now allow up to 40 characters, enabling tagging with UUIDs.

**Fixed ✔**

* Fixed the dashboard invoice list showing current month instead of the previous month.

### 2026.03.18

**New ✨**

* [🚀 Automated Scaling for Server Browser](/learn/server-browser#configuration) - Configure fixed capacity or pre-warm standby servers regionally with simple yet powerful query syntax. Includes support for [Private Fleets](/learn/orchestration/private-fleets) and  [Cloud](/learn/orchestration/deployments#match-bound) deployments, with new examples for various genres!

**Improvements 💪**

* Improved deployment list hover state for actions copy ID and go to app version.

**Fixed ✔**

* Fixed a bug where IPv6 user locations weren't resolved properly. Note that IPv6 is less reliable for pinpointing player location.
* [Get Locations API ](/docs/api/dedicated-servers#get-v1-locations) `administrative_division` field will now always return a string value. When the information is not available (provider limitations), this API will return an empty string.
* Fixed the Registry Artifacts dashboard page returning a 404 error.

![](https://img.shields.io/badge/Server%20Browser-Edgegap.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTQ2NCAyNTZhMjA4IDIwOCAwIDEgMCAtNDE2IDAgMjA4IDIwOCAwIDEgMCA0MTYgMHpNMCAyNTZhMjU2IDI1NiAwIDEgMSA1MTIgMCAyNTYgMjU2IDAgMSAxIC01MTIgMHptMzA2LjcgNjkuMUwxNjIuNCAzODAuNmMtMTkuNCA3LjUtMzguNS0xMS42LTMxLTMxbDU1LjUtMTQ0LjNjMy4zLTguNSA5LjktMTUuMSAxOC40LTE4LjRsMTQ0LjMtNTUuNWMxOS40LTcuNSAzOC41IDExLjYgMzEgMzFMMzI1LjEgMzA2LjdjLTMuMyA4LjUtOS45IDE1LjEtMTguNCAxOC40ek0yODggMjU2YTMyIDMyIDAgMSAwIC02NCAwIDMyIDMyIDAgMSAwIDY0IDB6Ii8+PC9zdmc+\&color=%233A52DA\&style=for-the-badge) v0.0.5

* Introducing [#automated-scaling](#automated-scaling "mention") and [#example-policies](#example-policies "mention").
* Adding more [#configuration](#configuration "mention") Examples for your inspiration.
* Improved [#flow-and-hierarchy](#flow-and-hierarchy "mention") diagram to get started faster.

<img src="https://img.shields.io/badge/Matchmaking-Edgegap.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIj48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTAgODhDMCA3NC43IDEwLjcgNjQgMjQgNjRsMTAwLjMgMGMzMCAwIDU4LjIgMTQgNzYuNCAzNy44bDg0LjggMTExLjNjOS4xIDExLjkgMjMuMiAxOC45IDM4LjIgMTguOWwxMDYuNCAwLTM5LTM5Yy05LjQtOS40LTkuNC0yNC42IDAtMzMuOXMyNC42LTkuNCAzMy45IDBsODAgODBjOS40IDkuNCA5LjQgMjQuNiAwIDMzLjlsLTgwIDgwYy05LjQgOS40LTI0LjYgOS40LTMzLjkgMHMtOS40LTI0LjYgMC0zMy45bDM5LTM5LTEwNi40IDBjLTE1IDAtMjkuMSA3LTM4LjIgMTguOUwyMDAuNyA0MTAuMkMxODIuNSA0MzQgMTU0LjMgNDQ4IDEyNC4zIDQ0OEwyNCA0NDhjLTEzLjMgMC0yNC0xMC43LTI0LTI0czEwLjctMjQgMjQtMjRsMTAwLjMgMGMxNSAwIDI5LjEtNyAzOC4yLTE4LjlsODQuOC0xMTEuM2MzLjktNS4xIDguMi05LjcgMTIuOS0xMy44LTQuNy00LjEtOS04LjgtMTIuOS0xMy44TDE2Mi41IDEzMC45QzE1My40IDExOSAxMzkuMyAxMTIgMTI0LjMgMTEyTDI0IDExMkMxMC43IDExMiAwIDEwMS4zIDAgODh6Ii8+PC9zdmc+&#x26;color=%233A52DA&#x26;style=for-the-badge" alt="" data-size="original"> v3.2.2

🩹 **Bugfixes:**

* Minor bugfix to disband teams if a matched player deletes their ticket before match found.

### 2026.03.11

**New ✨**

* Access relevant documentation directly from dashboard using our new widget.
* View release notes and new feature announcements directly in dashboard home page.
* Copy outputs now with a single click in our [Unreal Engine](/unreal-engine) docker extension.

**Fixed ✔**

* Fixed a bug where caching remained active when an application was disabled.
* Fixed a bug where using secret environment variables with non-ASCII characters would return an error when creating a deployment.
* Fixed a bug where credits balance wasn't updating to reflect used or expired credit grants.

### 2026.02.24

**Improvements 💪**

* Improved hover states on the [Deployment List](https://app.edgegap.com/deployment-management/deployments/list?deployments-table-limit=50\&deployments-table-page=1) page for better clarity on available click actions.
* Upgraded Dashboard framework to improve performance and stability. If you notice unexpected behavior, please report it in our [Community Discord](https://discord.gg/NgCnkHbsGp).

**Fixed ✔**

* Fixed a bug where webhook results in the deployment details page incorrectly showed HTTP status code 0 when the webhook handler returned no JSON body.

### 2026.02.16

**New ✨**

* Create [persistent app versions](/learn/orchestration/persistence) from dashboard, can only be deployed in [private fleets](/learn/orchestration/private-fleets).

### 2026.02.10

**New ✨**

* Measure your deployment total egress with new metric in deployment's metrics history.
* Preview your deployment's public IP address and [webhook](/learn/orchestration/deployments#webhooks-and-postbacks) responses in deployment details.

**Improvements 💪**

* Enhanced [v2/deployments](https://docs.edgegap.com/docs/api/dedicated-servers#post-deployments) webhook payloads with:
  * deployment's available resources: `vcpu_units`, `memory_mib` ; and
  * private fleet context: `host_id`, `host_in_private_fleet`, `private_fleet_id`  fields.

{% hint style="success" %}
**Upgrade to v2/deployments today to unlock observability enhancements!** Edgegap's managed services (Matchmaker and Server Browser) use the latest deployment APIs automatically.
{% endhint %}

* Dashboard now indicates when application version changes will reset active caching.
* Updated deployment details dashboard layout to display important information at the top.
* Updated player count metric in dashboard to exclude users from non-ready deployments.
* Separated billing items for Matchmaker, Server Browser, and Clusters for transparency.
* Clarified [pricing page](https://app.edgegap.com/user-settings?tab=memberships) explaining products and services included in each tier.
* Clarified pricing timeline and commitment for private fleets in confirmation modal.

### 2026.01.27

**New ✨**

* Added [dashboard notifications](https://app.edgegap.com/notifications) for Private fleet schedules, with a summary of provisioned hosts.

**Improvements 💪**

* Major improvements for [Deployments in Dashboard](https://app.edgegap.com/deployment-management/deployments/list), making fast scanning more convenient.
* Increased the default deployment list size to 50 for better visibility into recent deployments.
* Improved [Private fleet](https://app.edgegap.com/private-fleet-management/private-fleets/list) schedule date picker to auto-save when clicking outside date picker.
* Added `caching_percent` attribute (always returned) when fetching an [app version with API](https://docs.edgegap.com/docs/api/versioning#get-v1-app-app_name-version-version_name).
* Re-enabled and improved [billing recipients feature in dashboard settings](https://app.edgegap.com/user-settings?tab=general).
* Added timezone indication for [dashboard notifications](https://app.edgegap.com/notifications) to help align multi-national teams.

**Fixed ✔**

* Fixed image tag autocompletion when [creating or editing app version in the dashboard](https://app.edgegap.com/application-management/applications/list).

### 2026.01.12

**Improvements 💪**

* Improved messaging on the deployment details page when no [log storage](/docs/endpoint-storage/save-container-logs) is configured, displaying a clear explanation instead of a generic error.
* Enhanced container image validation on the dashboard to better prevent the creation of application versions with invalid container images.

**Fixed ✔**

* Fixed an issue where the available credits section on the dashboard incorrectly displayed “Not Available.”
* Fixed the [Out of Memory insights graph](https://app.edgegap.com/analytics/dashboards/list) introduced in the previous release, which was not displaying data correctly.

### 2026.01.07

**New ✨**

* [**Private Fleets**](/learn/orchestration/private-fleets) — Reserve and manage your own fleet of persistent servers (bare metal or VMs) across Edgegap locations worldwide via our self-serve platform. Edgegap fully orchestrates your fleet and automatically scales to our regionless cloud when traffic exceeds capacity, optimizing player experience and infrastructure costs.
* [**Server Browser**](/learn/server-browser) — Enable players to browse and join persistent servers with ease. Designed for MMOs and social games, it includes game-ready authentication, automated session management with latency optimization, and a scalable private cluster architecture.

### 2026.01.05

![](https://img.shields.io/badge/Server%20Browser-Edgegap.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTQ2NCAyNTZhMjA4IDIwOCAwIDEgMCAtNDE2IDAgMjA4IDIwOCAwIDEgMCA0MTYgMHpNMCAyNTZhMjU2IDI1NiAwIDEgMSA1MTIgMCAyNTYgMjU2IDAgMSAxIC01MTIgMHptMzA2LjcgNjkuMUwxNjIuNCAzODAuNmMtMTkuNCA3LjUtMzguNS0xMS42LTMxLTMxbDU1LjUtMTQ0LjNjMy4zLTguNSA5LjktMTUuMSAxOC40LTE4LjRsMTQ0LjMtNTUuNWMxOS40LTcuNSAzOC41IDExLjYgMzEgMzFMMzI1LjEgMzA2LjdjLTMuMyA4LjUtOS45IDE1LjEtMTguNCAxOC40ek0yODggMjU2YTMyIDMyIDAgMSAwIC02NCAwIDMyIDMyIDAgMSAwIDY0IDB6Ii8+PC9zdmc+\&color=%233A52DA\&style=for-the-badge) v0.0.4

* Entering OPEN BETA, introducing [filtering and sorting for server instances and slots](#search-and-browse)!

</details>

***

## 2025

<details>

<summary>View Release Notes 2025</summary>

### 2025.12.16

**Improvements 💪**

* Added new insights for Deployments Out Of Memory (OOM) in [Analytics](https://app.edgegap.com/analytics/dashboards/list).

**Fixed ✔**

* Added missing [pagination](/docs/api#response-pagination) support for [Registry API - listing image tags method](/docs/api/versioning#get-v1-container-registry-images-image_name-tags).

### 2025.11.03

<img src="https://img.shields.io/badge/Matchmaking-Edgegap.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIj48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTAgODhDMCA3NC43IDEwLjcgNjQgMjQgNjRsMTAwLjMgMGMzMCAwIDU4LjIgMTQgNzYuNCAzNy44bDg0LjggMTExLjNjOS4xIDExLjkgMjMuMiAxOC45IDM4LjIgMTguOWwxMDYuNCAwLTM5LTM5Yy05LjQtOS40LTkuNC0yNC42IDAtMzMuOXMyNC42LTkuNCAzMy45IDBsODAgODBjOS40IDkuNCA5LjQgMjQuNiAwIDMzLjlsLTgwIDgwYy05LjQgOS40LTI0LjYgOS40LTMzLjkgMHMtOS40LTI0LjYgMC0zMy45bDM5LTM5LTEwNi40IDBjLTE1IDAtMjkuMSA3LTM4LjIgMTguOUwyMDAuNyA0MTAuMkMxODIuNSA0MzQgMTU0LjMgNDQ4IDEyNC4zIDQ0OEwyNCA0NDhjLTEzLjMgMC0yNC0xMC43LTI0LTI0czEwLjctMjQgMjQtMjRsMTAwLjMgMGMxNSAwIDI5LjEtNyAzOC4yLTE4LjlsODQuOC0xMTEuM2MzLjktNS4xIDguMi05LjcgMTIuOS0xMy44LTQuNy00LjEtOS04LjgtMTIuOS0xMy44TDE2Mi41IDEzMC45QzE1My40IDExOSAxMzkuMyAxMTIgMTI0LjMgMTEyTDI0IDExMkMxMC43IDExMiAwIDEwMS4zIDAgODh6Ii8+PC9zdmc+&#x26;color=%233A52DA&#x26;style=for-the-badge" alt="" data-size="original"> v3.2.1

🩹 **Bugfixes:**

* Minor deployment bugfixes, resolved several errors when starting your matchmaker.

### 2025.11.18

![](https://img.shields.io/badge/Server%20Browser-Edgegap.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTQ2NCAyNTZhMjA4IDIwOCAwIDEgMCAtNDE2IDAgMjA4IDIwOCAwIDEgMCA0MTYgMHpNMCAyNTZhMjU2IDI1NiAwIDEgMSA1MTIgMCAyNTYgMjU2IDAgMSAxIC01MTIgMHptMzA2LjcgNjkuMUwxNjIuNCAzODAuNmMtMTkuNCA3LjUtMzguNS0xMS42LTMxLTMxbDU1LjUtMTQ0LjNjMy4zLTguNSA5LjktMTUuMSAxOC40LTE4LjRsMTQ0LjMtNTUuNWMxOS40LTcuNSAzOC41IDExLjYgMzEgMzFMMzI1LjEgMzA2LjdjLTMuMyA4LjUtOS45IDE1LjEtMTguNCAxOC40ek0yODggMjU2YTMyIDMyIDAgMSAwIC02NCAwIDMyIDMyIDAgMSAwIDY0IDB6Ii8+PC9zdmc+\&color=%233A52DA\&style=for-the-badge) v0.0.3

* Initial release of Server Browser service launched in CLOSED BETA.
* List Servers, Manage Capacity, and Get Connection Details.
* Support for match sessions with Cloud Deployments and always online with Private Fleets.

### 2025.11.03

**Improvements 💪**

* Added SSO support on the account registration page.
* Improved caching errors for [Deployments in Error](https://docs.edgegap.com/docs/release-notes/pages/0UXQAhtFuL0FkdoUmYTh#id-4.-deployment-error) for [V2 deployment API](/docs/api/dedicated-servers#post-deployments).
* Improved notifications for auto-cleaned [Deployments in Error](https://docs.edgegap.com/docs/release-notes/pages/0UXQAhtFuL0FkdoUmYTh#id-4.-deployment-error) clarifying no charges were added.
* Improved caching indicator criteria, green caching status is now more reliable.

**Fixed ✔**

* Fixed incorrect browser auto-translation of quick start token.
* Fixed an issue preventing matchmakers or clusters from starting in certain regions.

### 2025.10.31

<img src="https://img.shields.io/badge/Matchmaking-Edgegap.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIj48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTAgODhDMCA3NC43IDEwLjcgNjQgMjQgNjRsMTAwLjMgMGMzMCAwIDU4LjIgMTQgNzYuNCAzNy44bDg0LjggMTExLjNjOS4xIDExLjkgMjMuMiAxOC45IDM4LjIgMTguOWwxMDYuNCAwLTM5LTM5Yy05LjQtOS40LTkuNC0yNC42IDAtMzMuOXMyNC42LTkuNCAzMy45IDBsODAgODBjOS40IDkuNCA5LjQgMjQuNiAwIDMzLjlsLTgwIDgwYy05LjQgOS40LTI0LjYgOS40LTMzLjkgMHMtOS40LTI0LjYgMC0zMy45bDM5LTM5LTEwNi40IDBjLTE1IDAtMjkuMSA3LTM4LjIgMTguOUwyMDAuNyA0MTAuMkMxODIuNSA0MzQgMTU0LjMgNDQ4IDEyNC4zIDQ0OEwyNCA0NDhjLTEzLjMgMC0yNC0xMC43LTI0LTI0czEwLjctMjQgMjQtMjRsMTAwLjMgMGMxNSAwIDI5LjEtNyAzOC4yLTE4LjlsODQuOC0xMTEuM2MzLjktNS4xIDguMi05LjcgMTIuOS0xMy44LTQuNy00LjEtOS04LjgtMTIuOS0xMy44TDE2Mi41IDEzMC45QzE1My40IDExOSAxMzkuMyAxMTIgMTI0LjMgMTEyTDI0IDExMkMxMC43IDExMiAwIDEwMS4zIDAgODh6Ii8+PC9zdmc+&#x26;color=%233A52DA&#x26;style=for-the-badge" alt="" data-size="original"> v3.2.0

🩹 **Bugfixes:**

* Various smaller specification fixes and documentation consistency updates.
* Various stability and self-healing fixes across matchmaker infrastructure.

**✨ Improvements and new features:**

* **Introducing** [#group-up](#group-up "mention") **feature** - managing groups is now easy and doesn't require 3rd party!
  * No more sharing complex ticket attributes amongst group members, you only need Group ID.
  * Start matching as a group once all of your players mark themselves as ready.
  * Validate group member attributes against the group leader upon joining, preventing unmatchable groups (attributes of group players wouldn't match under profile's rules).
  * Validate group size and decline new memberships once max team size is reached.
  * Read our updated documentation with the new user flow, SDK updates coming soon!
* Tickets (memberships) now include your match ID - track players and add UI elements to share your team or opponent teams nicknames, skill rating, or other properties stored in third parties.
* Major [#rate-limits](#rate-limits "mention") overhaul based on internal stress testing, better handling of short bursts.
* Improved match fill rate due to delaying partial matches size until end of expansion.
  * If maximum team size reached, match immediately.
  * Otherwise match at the end of current expansion, if minimum team size reached.
* Set expiration and removal periods per profile and optimize for best player experience.

{% hint style="warning" %}
To update your Configuration, increase version, and copy expiration and removal fields to each profile.
{% endhint %}

### 2025.09.11

**New ✨**

* Easily change your organization's owner under [**Settings > Organizations > Manage Roles**](https://app.edgegap.com/user-settings?tab=organizations).

**Fixed ✔**

* Fixed live metrics showing a sum of per-core usage instead of overall percentage.
* Fixed an issue with authentication warning showing on dashboard sign-in sometimes.

### 2025.09.09

**New ✨**

* Link your Discord account for faster support and easier integration.
* Added a new server location in Berlin, Germany.

**Fixed ✔**

* Fixed deployments getting stuck with an empty environment variable and `is_hidden=true`.
* Fixed a bug where a "no login" email was incorrectly sent after a new tier subscription.

### 2025.08.27

**Improvements 💪**

* Dashboard now remembers your signed-in device for 7 days before logging you out.

**Fixed ✔**

* Fixed a bug with deployments not being reused for Sessions after 500x Sessions.
* Fixed an error thrown due to removing endpoint storage from an application version.
* Fixed an error caused by more complex dashboard deployment filters with timestamps.
* Fixed an error with some dashboard notifications not displaying descriptions properly.

### 2025.08.20

**New ✨**

* [Specify your preferred cluster region](https://app.edgegap.com/cluster-management/clusters/list) for private clusters (custom services) through dashboard!

**Improvements 💪**

* Rate limiting alerts are now only sent if exceeded multiple times per minute.
* Updated [Edgegap registry](https://app.edgegap.com/registry-management/repositories/list) URL to `registry.edgegap.com`. Prefix `registry2` kept as an alias.

**Fixed ✔**

* Fixed live metrics errors in deployment dashboard page due to fetching data too early.

### 2025.08.14

**New ✨**

* Specify your preferred matchmaker region for [private matchmaker tiers](https://docs.edgegap.com/learn/matchmaking/matchmaker-in-depth#hosting-cluster) through dashboard! [Change the region of your running cluster](https://app.edgegap.com/matchmaker-management-v2/matchmakers/list) with a stop-start. [Learn more about zero downtime updates.](https://docs.edgegap.com/learn/matchmaking/matchmaker-in-depth#rolling-updates-and-ab-tests)
* Request China mainland region availability for your [app version](https://app.edgegap.com/application-management/applications/list), subject to Edgegap review.

**Improvements 💪**

* [Deployment history metrics](https://docs.edgegap.com/learn/advanced-features/deployments#container-metrics) interval is now 2 minutes, matching the metric aggregation period. Live metrics remain unaggregated with 1 second interval measurements.

**Fixed ✔**

* Fixed missing deployment tags in deployment list and filtering for [Smart Fleets](https://app.edgegap.com/fleet-management/smart-fleets/list).

### 2025.08.07

**Improvements 💪**

* Updated game samples [Fishnet](/docs/sample-projects/unity-netcodes/fishnet-on-edgegap) and [Unity NGO](/docs/sample-projects/unity-netcodes/unity-netcode-on-edgegap) for latest Unity 6 LTS.
* Added network metrics legend to deployment live metrics.
* Added security schema to v2 openapi specification to simplify testing.

**Fixed ✔**

* Fixed an internal error in [metrics API](/docs/api/dedicated-servers#get-v1-metrics-deployment-request_id) for deployments not reaching [status Ready](https://docs.edgegap.com/docs/release-notes/pages/0UXQAhtFuL0FkdoUmYTh#id-3.-deployment-ready)

### 2025.07.23

**New ✨**

* Create application versions with up to **4 vCPU and 8 GB of RAM** from dashboard.

**Fixed ✔**

* Fixed an occasional issue with missing data points in deployment details history charts.
* Fixed a bug preventing creating third party registry profiles with a token longer than 1000 characters (e.g., for Google Registry).

### 2025.07.08

**New ✨**

* **Edgegap Container Registry capacity increased to 10 GB for all users**, including free tier!

**Improvements 💪**

* Improved application creation dashboard page user experience.

**Fixed ✔**

* Fixed a bug where the total player count wasn’t displayed correctly on the dashboard homepage in some cases. This represents a sum of all IP addresses and coordinates in deploy requests, including matchmaker deployments.
* Fixed an issue on the new application version page where inputs weren’t pre-filled properly when redirected from the Unity plugin.

***

### 2025.07.02

**New ✨**

* Create and edit app versions more easily, now with automatic input suggestions for containers!
* Filter your [analytics](https://app.edgegap.com/analytics/dashboards/list) reports by application version, to monitor resource usage more easily for versions with varying resource requirements and the same docker image.
* Try our simplified and updated game samples without building servers:
  * Unreal Engine 5.5.4: [Lyra Sample](/docs/sample-projects/unreal-engine/lyra-sample)
  * Unity 6: [Photon Fusion 2 Asteroids](/docs/sample-projects/unity-netcodes/photon-fusion-2-on-edgegap) | [Mirror Pong](/docs/sample-projects/unity-netcodes/mirror-pong) | [FishNet HashGrid](/docs/sample-projects/unity-netcodes/fishnet-on-edgegap)
* Export deployment logs to [endpoint storage](https://app.edgegap.com/endpoint-storage-management/endpoint-storages/list) as newline-delimited JSONs (NDJSON).

**Improvements 💪**

* Updated and improved API specification for [Create Application Version](/docs/api/versioning#post-v1-app-app_name-version) and [Update Application Version](/docs/api/versioning#patch-v1-app-app_name-version-version_name). No changes have been made to API endpoints request/response payloads.
* Added more hints for [upgrading matchmaker tiers](/learn/matchmaking/matchmaker-in-depth#rolling-updates-and-ab-tests) in dashboard.
* View your free credits expiration date in settings.

**Fixed ✔**

* Fixed a bug where deployment tags were not displayed on the archived deployment's detail page.

***

### 2025.06.30

**New ✨**

* **Deploy servers easier, faster, and more reliably than ever** with `v2/deployments` .
  * Matchmaker and engine plugins will be updated shortly to use this API (in a new version).
  * We're keeping `v1/deploy`  functioning for legacy integrations, no changes are required.

**Improvements 💪**

* [Smart Fleets](https://docs.edgegap.com/docs/fleet) now scale up more gradually when started with a high minimum value in policy. You should no longer see errors due to high amount of rapid deployment retries in such scenario.
* Updated [subscription pricing visuals in dashboard](https://app.edgegap.com/user-settings?tab=memberships). No changes have been made to platform pricing.

**Fixed ✔**

* Fixed disabling container log storage by query parameter for [self-stop deployment API endpoint](/docs/api/dedicated-servers#delete-v1-self-stop-request_id-access_point_id).

***

### 2025.06.10

<img src="https://img.shields.io/badge/Matchmaking-Edgegap.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIj48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTAgODhDMCA3NC43IDEwLjcgNjQgMjQgNjRsMTAwLjMgMGMzMCAwIDU4LjIgMTQgNzYuNCAzNy44bDg0LjggMTExLjNjOS4xIDExLjkgMjMuMiAxOC45IDM4LjIgMTguOWwxMDYuNCAwLTM5LTM5Yy05LjQtOS40LTkuNC0yNC42IDAtMzMuOXMyNC42LTkuNCAzMy45IDBsODAgODBjOS40IDkuNCA5LjQgMjQuNiAwIDMzLjlsLTgwIDgwYy05LjQgOS40LTI0LjYgOS40LTMzLjkgMHMtOS40LTI0LjYgMC0zMy45bDM5LTM5LTEwNi40IDBjLTE1IDAtMjkuMSA3LTM4LjIgMTguOUwyMDAuNyA0MTAuMkMxODIuNSA0MzQgMTU0LjMgNDQ4IDEyNC4zIDQ0OEwyNCA0NDhjLTEzLjMgMC0yNC0xMC43LTI0LTI0czEwLjctMjQgMjQtMjRsMTAwLjMgMGMxNSAwIDI5LjEtNyAzOC4yLTE4LjlsODQuOC0xMTEuM2MzLjktNS4xIDguMi05LjcgMTIuOS0xMy44LTQuNy00LjEtOS04LjgtMTIuOS0xMy44TDE2Mi41IDEzMC45QzE1My40IDExOSAxMzkuMyAxMTIgMTI0LjMgMTEyTDI0IDExMkMxMC43IDExMiAwIDEwMS4zIDAgODh6Ii8+PC9zdmc+&#x26;color=%233A52DA&#x26;style=for-the-badge" alt="" data-size="original"> v3.1.0

🩹 **Bugfixes:**

* Matchmakers now correctly validate tickets with multiple profiles including different rules.

**✨ Improvements and new features:**

* More optimizations to maximize match fill rate with [player\_count rule](#matchmaking-rules). Tickets will now wait until end of expansion (or expiration) if only partial match is possible (>min and \<max team size).
  * Full matches (max team size reached) are made immediately (no change).
* Upgrade to Enterprise [#hosting-cluster](#hosting-cluster "mention") to unlock matchmaking [#analytics](#analytics "mention")! Gain insights into matchmaker load and performance, no code or configuration required. Metrics at launch include:
  * total tickets, backfills, assignments, and deployments made over custom period of time,
  * per minute rates for the above metrics over custom period of time,
  * totals and time series insights into expired tickets, expanded matches, match fill rate,
  * API usage metrics, and more.
* Improved [#matchmaking-rules](#matchmaking-rules "mention") documentation with better examples and visuals.

***

### 2025.05.21

{% hint style="success" %}
🚀 **Matchmaker 3.0.0 is here!**

This major update introduces smarter team filling with min/max team size (breaking change), faster and more efficient matchmaking, and improved handling of backfills and latency. [See full changelog...](https://docs.edgegap.com/docs/release-notes/pages/kBCQHitUL4u9O0iTPk7x#id-3.0.0-may-20-2025)\
\
To upgrade, stop your matchmaker, edit config, and restart. Quick restart won’t apply version updates.
{% endhint %}

**New ✨**

* [**Testing and Releases:**](https://app.edgegap.com/test-and-release-management/test-and-release) You can now schedule releases, playtests, demos, and other key events directly from the dashboard. This gives you access to live support and a dedicated meeting room on your launch day, so you're never alone when it matters most.

**Improvements 💪**

* New matchmakers will now use upgraded Kubernetes version automatically. We'll reach out to notify owners of running matchmakers to schedule updates ahead of time.

**Fixed ✔**

* Fixed a bug where a `label` attribute was incorrectly added to the JSON of pre-configured configurations when creating a matchmaker.

<img src="https://img.shields.io/badge/Matchmaking-Edgegap.svg?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIj48cGF0aCBmaWxsPSJ3aGl0ZSIgZD0iTTAgODhDMCA3NC43IDEwLjcgNjQgMjQgNjRsMTAwLjMgMGMzMCAwIDU4LjIgMTQgNzYuNCAzNy44bDg0LjggMTExLjNjOS4xIDExLjkgMjMuMiAxOC45IDM4LjIgMTguOWwxMDYuNCAwLTM5LTM5Yy05LjQtOS40LTkuNC0yNC42IDAtMzMuOXMyNC42LTkuNCAzMy45IDBsODAgODBjOS40IDkuNCA5LjQgMjQuNiAwIDMzLjlsLTgwIDgwYy05LjQgOS40LTI0LjYgOS40LTMzLjkgMHMtOS40LTI0LjYgMC0zMy45bDM5LTM5LTEwNi40IDBjLTE1IDAtMjkuMSA3LTM4LjIgMTguOUwyMDAuNyA0MTAuMkMxODIuNSA0MzQgMTU0LjMgNDQ4IDEyNC4zIDQ0OEwyNCA0NDhjLTEzLjMgMC0yNC0xMC43LTI0LTI0czEwLjctMjQgMjQtMjRsMTAwLjMgMGMxNSAwIDI5LjEtNyAzOC4yLTE4LjlsODQuOC0xMTEuM2MzLjktNS4xIDguMi05LjcgMTIuOS0xMy44LTQuNy00LjEtOS04LjgtMTIuOS0xMy44TDE2Mi41IDEzMC45QzE1My40IDExOSAxMzkuMyAxMTIgMTI0LjMgMTEyTDI0IDExMkMxMC43IDExMiAwIDEwMS4zIDAgODh6Ii8+PC9zdmc+&#x26;color=%233A52DA&#x26;style=for-the-badge" alt="" data-size="original"> v3.0.0

**⚠️ Breaking changes:**

* Use [min/max team size to fill teams efficiently](#matchmaking-rules) (replaces player count expansions):
  * in your configuration `player_count` rule, replace `team_size` with `min_team_size` and `max_team_size` to achieve "best effort" matching attempting to maximize match fill rate,
  * to require a specific number of players per team, set both min and max to the same value,
  * backfills bypass `player_count` rule and always match with 1 ticket (unchanged).
* Tickets, group tickets, and backfills with all latencies above the highest `max_latency` in a given profile will be immediately rejected with `400 Bad Request` response to ticket create request, instead of expiring:
  * only applies if [latency rule](#matchmaking-rules) is configured,
  * to bypass this behavior, create an expansion with `max_latency: 99999` (any value higher than your client latency measurement timeout).
* [Injected environment variables](#injected-environment-variables) containing ticket data now include field `id` (ticket ID) so they can be reused more easily when creating [#backfills](#backfills "mention").

🩹 **Bugfixes:**

* [#backfill](#backfill "mention") now uses configured deletion and expiration period (like tickets and group tickets).
* [#backfill](#backfill "mention") now correctly matches using configured [`intersection` rules](#matchmaking-rules).
* Fixed [openAPI specification](#matchmaking-api) for POST [#backfills](#backfills "mention") request (requires `public_ip`) and GET /tickets response (`team_id` is optional), including examples.

**✨ Improvements and new features:**

* Up to 3x more potential matches are considered now, producing more optimal groups and maximizing match fill rate.
* Up to 200% faster matching speed due to concurrency optimizations.
* Up to 40% increased match fill rate due to optimization of expansions algorithm.
* Improved service stability and increased speed of quick restarts.

{% hint style="info" %}
Benchmarks were produced with chaos-generated data using [Advanced Example configuration](#configuration).
{% endhint %}

***

### 2025.05.14

**New ✨**

* A warning notice is now displayed when creating or updating an application version using the `latest` Docker tag, which is [strongly discouraged to avoid unpredictable caching behavior](/learn/orchestration/application-and-versions#container-parameters-required).
* Review :green\_circle: [available](/learn/orchestration/deployments#high-availability) and :blue\_circle: [burstable](/learn/orchestration/deployments#high-availability) deployment locations [in dashboard map in real time](https://app.edgegap.com/).

**Fixed ✔**

* Deployment details page now shows all deployment tags (previously showing only 10 results).

***

### 2025.05.01

**New ✨**

* View all associated tags that will be removed when deleting an artifact in [Registry Dashboard](https://app.edgegap.com/registry-management/repositories/list).
* Added a direct “Pay Invoice” link in the [billing section](https://app.edgegap.com/user-settings?tab=invoices) for easier manual payment.

**Improvements 💪**

* [Recent Deployments chart in Dashboard](https://app.edgegap.com/) no longer shows the last incomplete 5 minute interval.
* All table sorting preferences in dashboard are now saved across sessions.
* Removed duplicate deletion messages in dashboard for some edge cases.

***

### 2025.04.28

**New ✨**

* **View live metrics refreshed every 1 second from deployment page!** Troubleshoot and optimize resource usage more easily by monitoring CPU and memory usage in real time.
* **Toggle caching and inspect caching status** easily in your [app versions list dashboard page](https://app.edgegap.com/application-management/applications/list).
* **Prevent slow deployments due to configuration errors** when releasing updates. You will be now [automatically notified](https://app.edgegap.com/notifications) when more than 5 uncached deployments are made within 1 minute.

**Improvements 💪**

* Open recently updated resources' links in dashboard in a new tab with middle-click.
* Improved sign in and sign up layout for a smoother and more intuitive onboarding experience.

**Fixed ✔**

* Fixed a bug where container logs stored on your S3 endpoint were not displayed correctly if the app version name contained special characters on the archived deployment details page.
* Fixed duplicate displaying of logs for some running deployments.

***

### 2025.04.15

{% hint style="success" %}
**Free tier increased to allow** [Apps and Versions](/learn/orchestration/application-and-versions) **with 1.5 vCPU and 3 GB of memory**, to allow testing more demanding servers (e.g. [Unreal Engine Lyra sample](https://dev.epicgames.com/documentation/en-us/unreal-engine/lyra-sample-game-in-unreal-engine)) before upgrading to pay as you go tier.
{% endhint %}

**Improvements 💪**

* Improved registration and onboarding to provide a smoother experience.
* Improved quick skimming of recent resources - icons are now placed on the left.
* Updated [`/v1/stop/{request_id}`](/docs/api/dedicated-servers#v1-stop-request_id) API reference with clearer explanations and refined terminology to make it easier to understand. No changes were made to the actual API endpoint.

***

### 2025.04.10

**New ✨**

* A brand new table is now available on the dashboard home screen! It shows your most recently updated resources—application versions, matchmakers, and clusters—so you can stay on top of what's happening at a glance and jump straight to what matters.

**Fixed ✔**

* Fixed an issue where users registering with an expired invitation link were mistakenly prompted to create an organization. The flow now clearly shows an error if the link is no longer valid.
* Fixed a rare issue where deployment container logs were not visible on the dashboard, even when they should have been.
* Fixed a bug where the caching notification displayed the wrong time format, leading to confusing information.

***

### 2025.04.02

**New ✨**

* Our brand-new analytics section is now live on the dashboard! For Pay-as-You-Go users, gain deeper insights into your running containers with near real-time metrics on vCPU and RAM usage, network bandwidth, and more. Optimize performance and stay ahead with better resource monitoring!

**Improvements 💪**

* You can now sort the versions list for your applications by name on the dashboard, making it easier to organize and manage them.

***

### 2025.03.27

**New ✨**

* Deployment tags are now displayed on the session list page, and you can filter sessions by deployment tags. &#x20;
* We now apply the same validations when starting a matchmaker as when creating one. This change clarifies whether a matchmaker might result in flaky matchmaking due to misconfiguration.&#x20;

**Improvements 💪**

* We've refreshed the [`/v1/deploy`](/docs/api/dedicated-servers#v1-deploy) API reference with clearer explanations and improved terminology for better comprehension. No changes were made to the specifications.
* Added visual banners for new users to guide them to the documentation when landing on the dashboard. &#x20;

***

### 2025.03.20

**Improvements 💪**

* The matchmaker creation form has been improved to speed up the creation of new matchmakers using templates. Additionally, changes have been made to ensure ongoing progress is not lost when editing the configuration in the creation modal. We also try to prefill the form with your latest application version when using templates.
* The registration process using invitation links has been improved to automatically use the email provided by the organization owner, ensuring the flow isn't disrupted by a different email during registration.

***

### 2025.03.17

**New ✨**

* [Introducing Self-Healing Fleets](https://docs.edgegap.com/docs/fleet): Edgegap now retries your Fleet deployments in Error up to 5 times every day, if the current deployment count is below your target capacity. [Read more about why your deployment may end up in Error.](https://docs.edgegap.com/learn/advanced-features/deployments#id-4.-deployment-error)

**Improvements 💪**

* Renamed matchmaker dashboard button label to "Force Stop" instead of "Remove" for matchmakers in Error. This action doesn't change your matchmaker URL or Auth token.

***

### 2025.03.05

**New ✨**

* Increased the limit of simultaneous errored deployments for free-tier users from 1 to 10. We observed that some users were hitting the previous limit, which led to a poor experience. This change provides more flexibility when testing deployments on the platform.
* Organization owners can now view pending member invitations on the organization settings page.

**Improvements 💪**

* Added additional validation when creating a matchmaker to help prevent the use of invalid or uncached application versions.
* Reorganized the layout of the organization settings page to improve navigation and make it easier to manage your organization and add members.

***

### 2025.02.19

Hosting for Legacy Managed Matchmaker and [Legacy Advanced Matchmaker](/learn/advanced-features/managed-clusters) has been deprecated and is no longer available in the API or dashboard. See [Gen2 Managed Matchmaker](broken://pages/Isqy0gFGJvmxDhMGDieb) or [Advanced Matchmaker on Managed Clusters](/learn/advanced-features/managed-clusters#advanced-matchmaker) for our latest generation of matchmaking products.

**Improvements 💪**

* Made the quick restart button for the matchmaker more visible on the dashboard.
* Improved the display of credentials used for pushing images on the registry page.

**Fixed ✔**

* Fixed a bug where the same application version name could be used when duplicating an application version on the dashboard.
* Fixed a bug where certain content, such as tokens, environment variables and container requirements, was incorrectly translated by browser auto-translate features.

***

### 2025.02.12

**New ✨**

* We've redesigned the sidebar layout on the dashboard to make it more user-friendly and easier to navigate.

**Improvements 💪**

* Made further improvements to the matchmaker readiness validation when starting a Gen2 matchmaker, reducing the likelihood of errors during deployment.
* Improved the homepage layout with a smoother loading experience and better element transitions.

**Fixed ✔**

* Fixed a validation issue where uppercase characters could be used in the application version container configuration. This change now enforces lowercase characters only.
* Fixed a bug where the deployment time remaining displayed on the dashboard was inaccurate in some cases.
* Fixed a bug where the matchmaker Swagger documentation was not regenerated after a configuration update.

***

### 2025.02.06

**New ✨**

* We've added a locking mechanism that prevents the deletion of an application version if it is currently in use by a matchmaker. This change applies to both the API and the dashboard.

**Fixed ✔**

* Fixed inconsistencies in the API specifications.

***

### 2025.01.30

**Fixed ✔**

* Fixed an issue where refreshing the matchmaker details page on the dashboard would result in a 500 error.
* Fixed an occurrence where the cache for certain app versions could be disabled when communication with Docker was unreliable.
* Fixed a bug where the beacons list from the API could include duplicate cities.

***

### 2025.01.21

**New ✨**

* We've added more locations to our network, providing greater coverage and improved performance for your deployments.

**Improvements 💪**

* Enhanced the management of default registry profiles for better clarity.

**Fixed ✔**

* Fixed a bug where the container registry page displayed "Invalid DateTime" instead of the actual date in the last pull/push section.
* Fixed a bug on the app version details page where the caching progress always displayed as "low (red)" even when the actual coverage was "high (green)."

***

### 2025.01.15

**New ✨**

* When using a [Docker registry profile](/learn/orchestration/application-and-versions#container-parameters-required), creating a new app version will automatically fetch the available images and tags from your repository for selection.
* [Deployment environment variables](https://docs.edgegap.com/learn/advanced-features/application-and-versions/#other-parameters-optional) can now be up to 4KB in size.

**Fixed ✔**

* Fixed an issue where the plugin for initializing the container registry did not work and failed to provide proper access.
* Corrected the return status code from 401 Unauthorized to 400 Bad Request when sending an overly long environment variable in a deployment request.
* Fixed a layout issue on the dashboard across multiple pages.
* Fixed the matchmaker Gen2 startup flow to reduce potential errors during deployment.

***

### 2025.01.13

{% hint style="info" %}
From this release, the API will enforce a CPU/Memory ratio of 1:2. This change aims to keep costs as low as possible for everyone and improve the orchestration of resource usage across all users.
{% endhint %}

**New ✨**

* You can now use application environment variables up to 4KB in size.
* Our Mava AI chatbot is now available in dashboard, in addition to Discord, allowing access from multiple locations.
* We've added a new heatmap to app version details displaying the balance points of your deployment requests.
* The homepage map now displays our available beacons.
* We've added an option to perform a quick restart of your Gen2 matchmaker without needing to change anything in the configuration. Note that this will delete all current tickets and temporarily make the API inaccessible.

**Improvements 💪**

* The relay page now displays the status of your relay sessions.
* Adding tags from the dashboard now uses the same validation as the API, allowing a greater range of values.
* Improved the matchmaker readiness validation when starting a Gen2 matchmaker, reducing false positives where the status displayed as ready but the matchmaker was still unreachable for a short time.
* Accelerated the reload time for a Gen2 matchmaker.
* Made backend optimizations to the dashboard for a slightly faster experience. If you encounter unexpected errors on certain pages, please let us know via our [Discord](https://discord.com/invite/NgCnkHbsGp).

**Fixed ✔**

* Fixed an issue where metric charts sometimes did not display on the deployment details page.
* Fixed an issue where session sockets used always displayed as 0, even when sessions were present in the deployment details page under the session tab.

**Documentation 📚**

* new: [Unreal Engine learning center](broken://pages/MFyFoGY8VM8NFI1Rnp1L) - [Developer Tools](/unreal-engine/developer-tools) and [Getting Started with Servers](/unreal-engine)
* updated [Gen2 Unity SDK documentation in Unity Developer Tools](/unity/developer-tools#matchmaking-sdk)
* added [Advanced Example configuration to Getting started with Gen2](broken://pages/Isqy0gFGJvmxDhMGDieb)
* added and updated example requests/responses for [Gen2 Matchmaker In-Depth](https://docs.edgegap.com/learn/matchmaking/gen2-matchmaker-in-depth)
* added and updated tips for using [Ping Beacons](/learn/orchestration/ping-beacons)

</details>

<details>

<summary>Matchmaking Changelog Archive (v2.1.0 and before)</summary>

#### 2.1.0 (Feb 24, 2025)

**⚠️ Breaking changes:**

* Separated game profile and expansion stage information in the [#injected-environment-variables](#injected-environment-variables "mention"):
  * `MM_MATCH_PROFILE` will now only include the profile name as it appears in the configuration.
  * Introduced `MM_EXPANSION_STAGE` which will contain the expansion stage as a string (e.g. "initial", "15", "30").
* Ticket assignments now include the group ID when [#endpoint-tickets](#endpoint-tickets "mention"). Group ID is also included as an [#injected-environment-variables](#injected-environment-variables "mention"), as a mapping of group ID to a list of the group's player IDs.
* Ticket assignments now include the team ID when [#endpoint-tickets](#endpoint-tickets "mention"). The team ID is also included in every ticket data [#injected-environment-variables](#injected-environment-variables "mention").
* [#endpoint-tickets](#endpoint-tickets "mention") now returns `409 Conflict` HTTP code instead of `204 No Content` to indicate the ticket can't be deleted since the deployment is starting. To replace leavers, use a [#backfill](#backfill "mention") issued by the server after a pre-specified timeout period.
* [#endpoint-backfills](#endpoint-backfills "mention") request body parameter `attributes.deployment_request_id` has been moved to `attributes.assignment.request_id`.
* [#endpoint-backfills](#endpoint-backfills "mention") request body now requires full assignment details as part of `attributes` parameter in addition to the `request_id`.

🩹 **Bugfixes:**

* Resolved intersection rule values are now [#injected-environment-variables](#injected-environment-variables "mention") in the `MM_INTERSECTION` environment variable.
* Quick restart feature now reliably regenerates API endpoints and openAPI specification when configuration is changed.
* Fixed several bugs during matchmaker (re)start causing prolonged startup time or getting matchmaker stuck.

**✨ Improvements and new features:**

* Increased rate limits and scalability of all API endpoints, across all matchmaker tiers.
* When assigning a player to a [#backfill](#backfill "mention"), the new player's ticket ID will be added as a tag to the Backfill's [Deployments](/learn/orchestration/deployments).
* Added swagger UI authentication feature to test API directly in web UI without needing postman.
* Improved openAPI examples to reflect realistic requests and responses more closely.
* Added new [#inspect-api](#inspect-api "mention") meant for development and debugging purposes.
  * Allows listing all current player tickets in a paginated list.
  * Allows listing all current matches in a paginated list.

#### 1.0.0 (Dec 9, 2024)

* [#backfill](#backfill "mention"): Upon (popular) request, we’re adding backfill with automated ticket assignment, which allows you to reuse server seats when players leave the session.
  * Ideal for filling empty player seats after a match has begun, or for replacing players that have left during a match.
* [#join-as-group](#join-as-group "mention"): We’re adding the ability to join as a group to the already available ability to fill multiple teams with players.
  * Ideal for joining up in a matchmaking queue with a group of friends or coming from a common lobby.
* [Developer Tools](/unity/developer-tools#matchmaking-sdk) and [Developer Tools](/unreal-engine/developer-tools#edgegap-integration-kit-by-betide-studio) matchmaking SDKs:
  * To make integration easier, we’re now offering Software Development Kits for the most popular game engines.
* Fixed a bug where the [#latencies-attributes](#latencies-attributes "mention") was not applied correctly.
* Tickets will now be automatically canceled after a [#matchmaking-process](#matchmaking-process "mention") if they haven't been assigned to a deployment.
* You can now [#endpoint-tickets](#endpoint-tickets "mention") to enhance the flow of your matchmaking process.
* Deployments made by the matchmaker are now tagged with ticket IDs.
* You can now edit your configuration while the matchmaker is running. This triggers a quick reload of your configuration without requiring a full on/off cycle for your matchmaker. Note: This feature is not recommended for production environments, as it deletes all current tickets and temporarily makes the API unresponsive.
* Fixed [#injected-environment-variables](#injected-environment-variables "mention") to use the correct primitive types instead of arrays.
* Fixed [#injected-environment-variables](#injected-environment-variables "mention") JSON values, which previously contained escaped characters.

#### 0.2.3 (Oct 8, 2024)

{% hint style="info" %}
This version is no longer supported, please use a newer version for your matchmaker.
{% endhint %}

* Fixed a bug where certain headers were not accepted by the matchmaker when requests were made from a WebGL application (CORS policies).

#### 0.2.2 (Oct 3, 2024)

{% hint style="info" %}
This version is no longer supported, please use a newer version for your matchmaker.
{% endhint %}

* Fixed issue with TLS/SSL certificate validation preventing matchmaker from launching.

#### 0.2.1 (Sep 30, 2024)

{% hint style="info" %}
This version is no longer supported, please use a newer version for your matchmaker.
{% endhint %}

* Fixed a bug causing the beacons endpoint to return a 500 error.

#### 0.2.0 (Sep 25, 2024)

{% hint style="info" %}
This version is no longer supported, please use a newer version for your matchmaker.
{% endhint %}

* Basic authentication is now mandatory for all endpoints.
* Added the ability to configure the number of retries on server assignment failure.
* Team-based matchmaking is now the default for all matchmaking configurations.
* Both application and version are now required fields in all profiles.
* Introduced a new endpoint to monitor the matchmaker's status.
* Updated the format of the tickets environment variable in the deployment.
* Added a configuration option to allow hosts to communicate with the matchmaker.
* The debug API is now only available when explicitly enabled in the configuration (it is currently disabled for rework).
* The `game_profile` key in the GET ticket response has been replaced by `profile`.

</details>

***

## 2024

<details>

<summary>View Release Notes 2024</summary>

### 2024.12.09

{% hint style="info" %}
Match Players & Launch Games Instantly, the Easy Way

Edgegap’s “Gen 2” Matchmaker is now out of Early Access! What might warrant such a change, you might ask?

**New, Powerful Features!**

Joining the region-less, latency-based matchmaking (the only matchmaker with this feature, that we are aware of) and team matchmaking, we have:

* [Automated Backfill](broken://pages/Isqy0gFGJvmxDhMGDieb): Upon (popular) request, we’re adding backfill with automated ticket assignment, which allows you to reuse server seats when players leave the session.
  * Ideal for filling empty player seats after a match has begun, or for replacing players that have left during a match.
* [Group Matchmaking](broken://pages/Isqy0gFGJvmxDhMGDieb): We’re adding the ability to join as a group to the already available ability to fill multiple teams with players.
  * Ideal for joining up in a matchmaking queue with a group of friends or coming from a common lobby.
    {% endhint %}

**New ✨**

* The dashboard now enforces a minimum of 1/4 vCPU when creating an application version. This change aims to reduce integration issues observed with smaller instances and improve first time user experience. If you need access to smaller increments, please contact us.
* The API now enforces a minimum of 1/4 vCPU when creating an application version. **Users previously using smaller instances will not be affected by this change.** If you need access to smaller increments, please contact us.
* We've added a warning message when updating the maximum duration of an application version. Changing this value can cause running deployments to be immediately stopped.
* You can now edit your configuration while the matchmaker is running. This triggers a quick reload of your configuration without requiring a full on/off cycle for your matchmaker. Note: This feature is not recommended for production environments, as it deletes all current tickets and temporarily makes the API unresponsive.

**Improvements 💪**

* The minimum size for Matchmaker Gen2 has been reduced to 1 CPU & 2GB of Memory, and it's pricing has been decreased to $0.0312/hour. This change lowers costs for development and testing environments.
* We've improved the flow for organization invitations when the invited user is already registered on the platform.
* We've improved the matchmaker Gen2 health status to prevent false positives.

**Fixed ✔**

* Fixed a bug where relay sessions were not automatically deleted when players timed out or disconnected from the relay.
* Fixed a bug where the disable option for an application wasn't preventing deployments from being created.
* Fixed a bug with the deployment metric charts that caused them to display incorrect data on the dashboard.

**Documentation 📚**

* We've fixed the search bar to work in the Learning Center as well.
* Added documentation for the new matchmaker Gen2 features: [Automated Backfill](broken://pages/Isqy0gFGJvmxDhMGDieb) and [Group Matchmaking](broken://pages/Isqy0gFGJvmxDhMGDieb).

***

### 2024.11.21

**New ✨**

* We've added functionality to request our new Premium Consultation Service, available through the user modal on the dashboard.
* Starting from this release, application versions that don't use caching will be deployed on a subset of locations. As a result, your deployments without caching activated may be further away than before. This change has no effect on cached deployments. This is part of our effort to optimize resource usage and provide a better service to all users.
* The dashboard home page now displays a new announcement box at the top to keep you informed of important updates and changes.
* We've added a progress bar to the matchmaker Gen2 creation and removal process to give you a better idea of the time remaining.
* You can now save credentials from external container registries and use them as default credentials when creating a new application version on the dashboard. This feature will be integrated with our plugins in the future. Default credentials are also working with our registry.
* We've added a button to copy your request ID on the deployment page. This feature will help you provide the necessary information when contacting support.
* To improve user experience, all application versions must now have a set maximum time to live (TTL) of 24 hours. While this maximum TTL has been enforced internally since the 2024.09.09 release, this change makes it visible on the dashboard, reducing confusion about deployments being deleted unexpectedly.
* You can now use filters in the list deployments API route to filter deployments. Some of the available filters are status, version, request ID, tags, and more. Documentation will be updated soon with additional details.
* We've added an [API route to delete a lobby](/docs/api).
* We've added an [API route to list all lobbies](/docs/api).

**Improvements 💪**

* Whitespace is now supported in the creation of organization names.
* Updated lobby name validation to prevent unexpected release errors.
* We now support S3 buckets from AWS, alongside most other S3-compatible storage services already supported.

**Fixed ✔**

* Fixed a UI bug on the deployment page where using a large number of tags would break the layout.
* Fixed a bug where making an API call to a lobby soon after creating it would return a 503 Service Unavailable error.
* Fixed a bug where some individual port environment variables were not injected properly into the deployment's container.
* Fixed a bug where dashboard deployment logs did not display detailed error messages when a deployment failed.
* Fixed a bug where application versions ports would be deleted unexpectedly when updating the application version.
* Fixed a bug that caused session and relay session creation to be slower than expected.
* Fixed a bug where it was impossible to delete a Gen2 matchmaker when too many errors had occurred when releasing it.

**Documentation 📚**

* Added an [Unreal Top-Down](/docs/sample-projects/unreal-engine/top-down-sample) sample project.
* Consistency fixes for the Gen2 matchmaker documentation.
* Updated the Learning Center with new content and minor consistency improvements.

***

### 2024.10.30

**New ✨**

* You will now receive a dashboard notification when you reach an API rate limit. This notification helps you avoid unexpected behavior when interacting with the API and alerts you to potential issues and unexpected costs.
* You can now filter your deployments by city, country, and continent on the dashboard.
* Matchmaker Gen2 JSON errors are now more descriptive and easier to understand when creating or updating a matchmaker.
* The JSON configuration editor for the Gen2 matchmaker now supports copy-pasting, undo/redo, collapse/expand, JSON error highlighting, and auto-correction.

**Improvements 💪**

* We’ve improved the private matchmaker creation process to make it more user-friendly and intuitive. The flow for creating and deleting a private matchmaker will be slightly longer, but we plan to add a reload feature, allowing you to change your configuration without having to stop and start the matchmaker.

**Fixed ✔**

* Fixed a bug with the application version bulk delete feature that was not functioning correctly on the dashboard.
* Fixed a bug in which the maximum deployment time was not correctly applied when deploying an application version with caching disabled.
* Fixed a bug where the dashboard validation for endpoint storage was returning false positives for misconfigured buckets.

**Documentation 📚**

* We've introduced our new [Learning Center](/learn/orchestration), where you can find all the resources you need to get started with Edgegap.

***

### 2024.10.08

{% hint style="info" %}
Matchmaker Gen2 We still ironing out bugs and adding new features to our Matchmaker Gen2. We now have a dedicated changelog for it, which you can find \[here]\(/docs/gen2-matchmaker-changelog).
{% endhint %}

**New ✨**

* The dashboard will set the maximum duration for deployment to 60 minutes by default when creating a new application version. This change will help prevent unexpected costs. You can still adjust the duration to your needs.

**Fixed ✔**

* Fixed an issue with lobbies where they would not start correctly and return an error message when getting the status.
* Fixed the average deployment time section on the dashboard's home page that was sometimes displaying an error message.

***

### 2024.09.25

{% hint style="info" %}
Matchmaker Gen2 Our new Matchmaker Gen2 is now live and available to everyone! You can access it directly through the dashboard in the same place as the previous matchmaker.

This latest version offers increased flexibility and control over your matchmaking process, all while being easier to use. You can start by creating matchmakers on our free cluster for early testing, and then seamlessly transition to your own dedicated cluster for live games or uninterrupted testing.

Matchmaker Gen2 is currently in early access until we fully phase out the previous version, and we’re super excited to have everyone onboard! Check out our [documentation](broken://pages/Isqy0gFGJvmxDhMGDieb) to get started, and don’t hesitate to share your feedback with us on [Discord](https://discord.com/invite/NgCnkHbsGp).
{% endhint %}

**New ✨**

* We are introducing a "bring your own DNS" feature. This will allow you to use your own domain name for your deployments. This feature is currently in early access, if you are interested in testing it, please reach out to our support team.
* Effective with this release, we will now delete protected deployment after a period of 48h.

**Improvements 💪**

* Our plugin for Unity has been updated and is now available on the [asset store](https://assetstore.unity.com/packages/tools/network/edgegap-game-server-hosting-212563).

**Fixed ✔**

* Fixed some color formatting issues with the session logs on the dashboard.

***

### 2024.09.09

{% hint style="info" %}
We’ve begun our closed BETA for Matchmaker Gen 2! This new version is a complete overhaul of the current managed matchmaker, offering greater flexibility and control over your matchmaking process.
{% endhint %}

{% hint style="success" %}
Resource Management To improve resource management and help prevent unnecessary costs on your end, we'll now automatically close deployments that run for over 24 hours. If you need a deployment to stay active longer, let us know and we'll be happy to assist.
{% endhint %}

**New ✨**

* Empty relay sessions will now automatically delete after 10 minutes. This change helps manage resources more efficiently and reduces the amount of code needed to manage your relay sessions.

**Improvements 💪**

* The container registry page on the dashboard now correctly displays repositories with subfolders.
* The registry management API endpoints now support repositories with subfolders.
* When creating a new session-type application version, the auto-deploy feature will now be enabled by default. This should reduce confusion around the deployment process. You can still disable auto-deploy if your workflow requires it.
* We've added more locations to our network to provide even more coverage and better performance for your deployments.

**Fixed ✔**

* Fixed a bug where some locations were not displayed correctly on the deployment details page when they were deleted. This occurred with older terminated deployments.
* Fixed a small UI bug with the form inputs on the dashboard.

**Documentation 📚**

* Added an info box on using the `X-Real-IP` header in ticket creation when using the managed matchmaker ([doc](broken://pages/Isqy0gFGJvmxDhMGDieb)).
* Fix the injected selector example in the managed matchmaker configuration ([doc](broken://pages/Isqy0gFGJvmxDhMGDieb)).

***

### 2024.08.13

**New ✨**

* You can now delete your relay session directly from the relay details page on the dashboard.
* We've added a feature for the **relay session** to automatically detect when your players are disconnected and remove them from the relay session. This feature will help you manage your sessions more efficiently. We will add a feature to delete empty sessions automatically in the near future.
* Lobbies will now automatically close after 3 hours for free tier users. This change will help manage resources more efficiently and ensure optimal performance for all users.

**Improvements 💪**

* The default deployment port verification timeout has been increased to 30 seconds. This change will not make your deployment process longer but will prevent errors for customers with longer boot times for their containers.
* We've reenabled the Oauth login for the dashboard. This change will allow you to use your Google or GitHub account to log in to the dashboard.

**Fixed ✔**

* Fixed an issue where using an invalid IP would break the deployment details page on the dashboard.
* Fixed an issue where the sessions tab inside the deployment details page could not be accessed.
* Fixed an issue where tooltips and guided tours on the dashboard were not working correctly upon refreshing the page.
* Fixed an issue causing longer than expected loading times for the login page on the dashboard.

**Documentation 📚**

* Expanded documentation for sessions management with Fishnet netcode integrations ([doc](/docs/sample-projects/unity-netcodes/fishnet-on-edgegap)).
* Expanded documentation for sessions management with Mirror netcode integrations ([doc](/docs/sample-projects/unity-netcodes/mirror-on-edgegap)).
* Added more warnings and troubleshooting steps for the managed matchmaker sample configuration.
* Added more insights into the Lobby service compatibility with our services .
* Added step to set autodeploy to true when using the managed matchmaker.

***

### 2024.07.24

**New ✨**

* We now provide both IP and FQDN of our beacons when listing them. This will allow you to choose between the two options when coding your integration. We are introducing a permissive rate limit for the API. This change aims to ensure fair usage and maintain optimal performance while providing flexibility for most use cases.

**Improvements 💪**

* We've tweaked the memory usage chart on the dashboard and the API to provide more accurate data when the RAM is spiking. This will help monitor potential issues and optimize your deployments.
* We've removed the automatic creation of the demo application when using the initialization button on our plugins. This behavior was causing confusion and issues when mixed with our free tier limitations.
* Small consistency improvements on the dashboard UI.
* Improved monitoring of our locations to ensure the best performance for your deployments.

**Documentation 📚**

* Added an example for seat sessions management with Fishnet netcode integration ([doc](/docs/sample-projects/unity-netcodes/fishnet-on-edgegap))

***

### 2024.07.11

**Improvements 💪**

* We updated our internal application used for the ping beacons. This update uses a more performant language, ensuring minimal application overhead for the most accurate latency measurement. Usage still remains the same, and no changes are required on your end.
* We updated the pricing page on the dashboard to match the pricing page on our website. This update includes a more detailed breakdown of the features available in each tier.

**Fixed ✔**

* Fixed an issue where creating a oneClick token with the dashboard would generate the token correctly but not display it properly within the creation modal.

**Documentation 📚**

* Added example for seat sessions management with Mirror netcode integration ([doc](/docs/sample-projects/unity-netcodes/mirror-on-edgegap)).

***

### 2024.07.03

{% hint style="warning" %}
**Notice to all Matchmaker users:** As part of our ongoing efforts to enhance our infrastructure, we will be transitioning the matchmaker clusters to a new provider. This change is necessary to ensure better performance and reliability for your services.

To facilitate this transition, you will need to re-release your matchmaker. The URL will remain the same, so no changes are required on that front. Please note that there will be a brief downtime of approximately 5 minutes during this process.

If you wish to perform the migration using an A/B strategy, you can simply start a new matchmaker. It will be on the new cluster with a new URL. Once you replace the URL in your game clients, delete the old one. This approach minimizes any potential disruption and ensures a seamless transition.

**Deadline: Please complete this transition by Friday, the 12th of July. After this date, we will re-release all matchmakers still running on the old provider.**
{% endhint %}

**New ✨**

* We've added pagination to the artifacts list on the container registry page, making it easier to view all your images.
* We've added API routes to get and delete image tags from the container registry, allowing you to manage your tags directly from your scripts, CI/CD, or applications.

**Fixed ✔**

* Fixed some unexpected behavior with pagination on the dashboard tables.

**Documentation 📚**

* Updated PlayFab matchmaker bridge documentation for Edgegap integration. ([doc](https://github.com/edgegap/docs-gitbook/blob/main/docs/playfab-bridge/README.md)).
* Documentation for deleting artifact tags via API ([doc ](https://github.com/edgegap/docs-gitbook/blob/main/docs/container/edgegap-container-registry/README.md#delete-build-artifacts-via-api), [API](https://github.com/edgegap/docs-gitbook/blob/main/api/README.md#tag/Container-Registry)).
* Added common error warning in the Fishnet integration example ([doc](https://github.com/edgegap/docs-gitbook/blob/main/docs/sample-projects/fishnet-on-edgegap/README.md#configure-the-client-build)).
* Updated documentation links for netcode integrations with our relays ([doc](https://github.com/edgegap/docs-gitbook/blob/main/docs/distributed-relay-manager/README.md#getting-started)).

***

### 2024.06.27

{% hint style="info" %}
A brief notice about our current release cycle: We are currently working on a major update to our matchmaker, focusing our efforts on improving performance, flexibility, and reliability. We're also introducing new features to make creating custom matchmaker rules easier. This explains the slower than usual amount of feature releases you might have noticed.

We aim to release this update in the next quarter. Stay tuned for more information.
{% endhint %}

**New ✨**

* Day by day billing is now available for all organizations. You can now see the cost of your deployments on a daily basis. This feature is available on the billing section of the settings page.
* Relay users are now automatically removed from the session when the relay detects a player has disconnected.

**Fixed ✔**

* Bug where the container logs download file was not correctly generated on the deployment details page.
* Bug where free tier users could not use the sessions with our matchmaker.
* Bug with the removal of a user when a lobby is started.

***

### 2024.05.06

**New ✨**

* The organization owner can now enable two-factor authentication (2FA) for all members of the organization. Once enabled, all members will be required to set up 2FA on their next login.
* We've added a bulk delete option for application versions on the dashboard. You can now select multiple versions and delete them simultaneously.

**Improvements 💪**

* The repository list on the registry page is now paginated.

**Fixed ✔**

* Bug where egress unit was not correctly displayed on the relays charts.
* Bug where the one-to-one port mapping option was not correctly displayed on the port creation form.

***

### 2024.04.29

**New ✨**

* An option is now available to automatically scroll to the bottom of the container logs on the deployment details page.

**Improvements 💪**

* Previously, the `verify_image` option could only be applied to the API route for creating an application version. Now, you can also use it with the API route for updating an application version.

**Fixed ✔**

* Fix a bug where applying a filter on dashboard table would remove the current pagination setting.
* Fix a bug where it was not possible to delete an image from the dashboard repository page when there is multiple images for a same tag.

***

### 2024.04.03

**New ✨**

* See your upcoming billing cycle amount due directly on the dashboard. This feature is available for organization owners on the billing section of the settings page.
* You can now filter the deployment list by tags directly on the dashboard. The API version will be available soon.
* You can now distinctly choose between displaying your current players or Edgegap locations on the homepage map. We're currently working to make it available for your deployments as well.

**Improvements 💪**

* Enhanced clarity in the matchmaker component form fields for better usability.
* Provided additional insights into the subsequent steps after creating a one-click token for plugins.
* Implemented various minor UI enhancements across the dashboard to improve the first-time user experience.

**Fixed ✔**

* Resolved several minor issues with layouts and forms across the dashboard.

***

### 2024.02.29

**New ✨**

* Owners of organizations can now add additional recipients to billing emails. This feature is available on the general settings page.
* You can now select a default landing section for the deployment details page. Choose between the map, details, container logs, and charts sections to be the first one shown when opening the deployment details page.

{% hint style="info" %}
The \`inject\_context\_env\` option in application versions will be deprecated since context will be injected by default in all deployments. These variables are prefixed with \`ARBITRIUM\_\` and are available in the environment of the container. This will not change any behavior and **will be transparent** to the user.&#x20;
{% endhint %}

**Improvements 💪**

* Updated messaging around relay add-ons on the tier subscription page to better explain the key differences between them and our authoritative servers offering.
* Added a warning to inform users that the dashboard is less optimized for mobile devices.
* Registry credentials will now be auto-filled when creating a new application version on the dashboard if you use our registry, making the process faster and less error-prone.

**Fixed ✔**

* Fixed a bug where multiple `one-click` tokens could be created instead of using the existing one with plugins.
* Fixed a bug in matchmaker environment variables where value validation was too strict. Key and value are now validated based on POSIX standards.
* Fixed a bug where creating a session with too many users for the available socket count with the dashboard wizard would fail without proper error message.
* Fixed a bug where the colors of charts on the deployment details were not as expected.

***

### 2024.02.19

#### Special Announcement ❗

{% hint style="info" %}
Users on the Free Tier now have access to 1 CPU for their deployment, up from 0.5 CPU. Along with this change, we're also introducing a 1-hour max deployment time for Free Tier users.

These changes are being introduced to make sure Free Tier users can quickly test more resource-intensive game servers on Edgegap and to ensure these resources are used more efficiently on our infrastructure.

Our self-serve plans continue to offer unlimited deployment time. For more details, explore each option's expanded capacity and features on our pricing page. [View Tiers](https://app.edgegap.com/user-settings?tab=memberships)
{% endhint %}

**New ✨**

* Additional onboarding questions to help us better understand your needs and provide a more tailored experience.
* The remaining time of a deployment will be shown on the deployment details and deployment list page when applicable.
* Button to create a deployment from the home page.
* Terminate multiple sessions directly from the session list page.

**Improvements 💪**

* Added more guidance when container validation fails during application version creation.
* Added more guidance for application version credentials during application version creation.
* Added more guidance to create container log storage from the deployment container logs section.
* Added more explanation to the application version port creation modal to help users better understand the process.

**Fixed ✔**

* Unexpected behavior on the deployment details page when using the back button.

***

### 2024.02.01

**New ✨**

* View the current caching status of your application versions on their details page.
* Terminate multiple deployments directly from the deployment list page.

**Fixed ✔**

* Bug where the deployment details page chart, logs, and container logs were not loading correctly in some cases.
* Smaller bugs and UI improvements across the dashboard.

***

### 2024.01.23

**New ✨**

* The modal to create policies on the fleet management page is now showing the available locations to deploy your servers.
* You can now duplicate an existing application version from the dashboard, including the ports and environment variables.
* You will now see at a glance on the dashboard homepage if you have any deployment in error.
* The application version creation page is now showing your available images if you use the Edgegap Container Registry.

**Improvements 💪**

* Colors standardization for a better experience on both light and dark mode.
* Modal standardization, they will now be using the same format across the dashboard to improve the user experience.
* Standardized the registry access request email to be more in line with the other emails sent by the dashboard.

**Fixed ✔**

* Bug where you could not update the port of an application version. Note that you still cannot update a port when one or more deployment are running.
* Fix a typo in the pull command on the Edgegap Container Registry page.

</details>

***

## 2023

<details>

<summary>View Release Notes 2023</summary>

### 2023.12.13

**New ✨**

* The invoice will now include a summary of the egress and vCPU usage (grouped by size) for the month.
* We will send a notification to your dashboard notification center when there's a deployment we can't cache due to misconfiguration.

**Improvements 💪**

* Improved the homepage "Average Deployment Time" statistic to reflect the last 24 hours of deployments instead of the current live deployments.
* You can now update your application and version names directly from the dashboard.
* Minor UI tweaks to the dashboard for consistency and clarity.

**Fixed ✔**

* Resolved a bug that caused padding issues with the "Recent Deployment" chart when resizing the homepage.

***

### 2023.12.05

**New ✨**

* We've added JSON snippets on multiple object creation within the dashboard to help you integrate faster with our API.
* A button to delete your deployment within the deployment list to help you manage your deployments faster.
* Added a button to create deployment from the deployment list. You will be able to select a specific application version.

**Improvements 💪**

* Added more locations to deploy your applications.
* You can now click on your application version name to access the application version details page quicker from the deployment details page.
* Added a new call to action on many pages displaying a list of objects. The call to action will be displayed when the list is empty.
* The deployment and session modals have received some UI tweaks to be more consistent with the rest of the dashboard. It also displays a button to create a new application version if you don't have any.

**Fixed ✔**

* Fixed a small bug where the organization name was not displayed correctly on the dashboard navbar.
* Fixed some weird loading behavior on the dashboard login and account creation.
* Fixed a bug where sometimes the dashboard was displaying two different pages at the same time.
* Fixed a bug where some pages on the dashboard were scrollable when they shouldn't be.

***

### 2023.11.29

**New ✨**

* Button to delete your artifacts from the container registry on the new dashboard.
* Create an account with your Google or GitHub account on the new dashboard.

**Improvements 💪**

* Optimization with the caching to prevent our system from automatically turning off the cache option on an invalid application version repository and image configuration.
* Insights on the new dashboard when your application version container image validation fails.

***

### 2023.11.14

**New ✨**

* Container registry access requests now get auto-approved after successful validation.

**Improvements 💪**

* Minor tweaks on the new dashboard.
* Various backend optimizations to continuously improve the experience when deploying your game servers.

**Fixed ✔**

* Bug preventing the editing of registry username or token on the dashboard.
* Bug causing relay sessions to end up in unintended locations or fail without clear reasons.
* Bug in the API where application version names could be empty.

***

### 2023.10.25

We are back with another monumental release 🎉, and this time we are filling in the missing pieces to provide you with a holistic dashboard experience. Your patience and feedback have been invaluable, and we are thrilled to unveil the newly incorporated features. These additions are designed to empower you with more control and insight over your orchestration processes. Let’s dive into what’s new!

**New ✨**

* **Matchmaker Integration:** Optimize player experiences with our enhanced matchmaking capabilities now available right from the dashboard. Tailor matchmaking processes with configurable rulesets to meet the unique needs of your player base.
* **Sessions Management:** Monitor and manage your sessions directly from the dashboard, providing you with real-time insights into player interactions. Leverage robust filtering options to sift through session data effortlessly, making deployment management a breeze.
* **Smart Fleets:** Manage your fleets more intelligently with the new UI integrations, ensuring optimal resource allocation and performance. Gain a clear view of your fleet's status and performance with real-time metrics and interactive graphs.

**Coming Soon 🕒**

* **OneClick Plugin:** With just a click, deploy your game servers directly from Unity and Unreal game engines using our OneClick plugin. This integration aims to streamline the process.
* **2F Auth & SSO Integration:** Enhance your security posture with Two-Factor Authentication and Single Sign-On capabilities, coming soon to the dashboard.
* **And More:** Stay tuned for more updates as we continually strive to refine and expand our offerings based on your feedback.

We are dedicated to providing you with a powerful, user-friendly dashboard, and these updates mark a significant stride towards that goal. Your continuous feedback is a cornerstone of our development process, and we eagerly await your thoughts on these new features.

As always, thank you for your trust and partnership. Happy deploying!

***

### 2023.09.26

This release introduces a new UI interface for our Container Registry Connector and adds invoice management to your user settings. We're also excited to preview upcoming features: Smart Fleets and Sessions will soon be accessible via the UI.

**New ✨**

* **Container Registry Connector:** Now available in the new dashboard. Offers a privately managed container repository with enhanced security, connection scans, and more.
* **Invoices in User Settings:** Users can now manage and view their invoices directly from their settings.

**Coming Soon 🕒**

* **Smart Fleets:** Manage your fleets more intelligently, soon to be available in the UI.
* **Sessions:** Monitor and manage your sessions directly from the UI, coming soon.

***

### 2023.08.23

🎉 **We're thrilled to announce the release of the completely revamped Dashboard!**

It's been a while since our last release notes, and we want to take a moment to explain why. Our team has been working tirelessly on this major overhaul, focusing on delivering a set of groundbreaking features and improvements. This extended development period has allowed us to ensure that every aspect of the new dashboard meets our high standards of quality and innovation. This marks the beginning of a new journey as we fine-tune and add awesome features. We've poured our hearts and minds into this project, and we're eager to share the outcome that will undoubtedly elevate your orchestration management experience.

**Major Enhancements**

**Enhancing Speed, Control, and Insight 🚀**

* **Lightning-Fast Experience:** The new dashboard has been completely restructured with a cutting-edge framework, bringing you a lightning-fast user experience. This pivotal upgrade considerably reduces loading times, helping you manage your Deployments and Applications with unprecedented efficiency.
* **Advanced Sorting and Filtering Options:** Enjoy greater control and flexibility with our advanced sorting and filtering options for your Deployments and Applications. This optimized functionality will allow you to pinpoint exactly what you need, freeing up more time to focus on improving your applications.
* **Real-Time Metrics and Graphs:** The console now integrates real-time metrics and interactive graphs, providing a clear view of the status and performance of your Deployments and Applications. These powerful tools will assist in your decision-making and development.
* **Deployment History:** We're introducing the ability to sort and filter past deployments, providing you with a streamlined way to navigate through your deployment history. This feature will empower you to find the information you need quickly and efficiently.

**Distributed P2P Relays 🗼**

We're introducing Distributed Relays, an innovative distributed peer-to-peer relay network. This exciting feature raises the bar by enhancing application performance and network reliability. While the relay system itself will function regardless of the console via the API, the metrics and graphs associated with it are exclusive to the new Console.

**Endpoint Storage 🗃️**

Your container logs are now securely stored in remote S3 buckets, ensuring that logs that would previously be deleted are now saved. This robust and scalable solution enhances log management and accessibility.

**Revamp to Organization and Billing 💼**

Managing your organizations and billing has never been easier. For users who upgraded in the old dashboard, you'll be pleased to know that you are grandfathered into that membership. However, switching to the new billing system could potentially lower your costs, providing an opportunity for savings. Contact our support team to transfer applications between organizations, and enjoy a more transparent and user-friendly billing experience.

**Coming Soon**

**New Features and Enhancements 🎮**

* **Matchmaker**: Enhanced matchmaking capabilities for optimal player experiences.
* **Container Registry**: A privately managed container repository that works much like standard public container registries, but with the added benefit of increased security, vulnerability scans, and other goodies!
* **Smart Fleets**: Previously only available through the API, now accessible through the UI.
* **Sessions**: Previously only available through the API, now accessible through the UI.
* **2F Auth & SSO**: Increased security with two-factor authentication and single sign-on.

We're continuously working on new features and improvements to provide you with the best possible experience. Stay tuned for more updates and exciting announcements in the near future!

Your feedback is vital to our ongoing development, and we encourage you to share your thoughts and suggestions with us. 💡

Thank you for your continued support and partnership. Happy deploying! 🙏

The Edgegap Team

***

### 2023.05.16

**New ✨**

* You can now add and remove tags to your deployment using the API. Additionally, you have the option to tag from within your deployment's container. It was previously only possible to tag from the dashboard and at the start of the deployment request. To learn more about this new feature, please refer to [this documentation](https://github.com/edgegap/docs-gitbook/blob/main/docs/deployment/tags/README.md).
* You now have access to more robust filtering options for sessions and relay sessions, which function in a similar manner to the deployments filter.

**Improvements 💪**

* Added new locations to deploy your applications.

**Fixed ✔**

* A correction has been made to address a mismatch in the locations. Certain locations were erroneously identified as "United States" instead of the accurate designation "United States of America."

***

### 2023.04.18

**New ✨**

* A feedback tool is available on almost all pages of the documentation. You can now tell us whether the information you just read was helpful or not.
* We are excited to announce a new way to deploy on the edge with Edgegap distributed relays. With this update, you can now deploy relay sessions across multiple locations in our infrastructure, providing you with all the benefits of the Edgegap solution, including low latency, scalability, and easy integration. To learn more about this new feature, please refer to [this documentation](https://github.com/edgegap/docs-gitbook/blob/main/docs/distributed-relay-manager/README.md).

**Improvements 💪**

* Deployments and sessions that are in an error status will be automatically deleted after a certain period of time. Currently, this is set to occur after 24 hours. Deployments or sessions in error do not have any live games or applications running, so users will not be disconnected from any server.

***

### 2023.03.07

**Improvements 💪**

* We have increased the maximum termination grace period from 180 seconds to 3600 seconds, allowing more time for post-delete processes to complete. Please note that this extended time period will be billed, even if the deployment is in a terminating state, as compute resources are still in use during this time.

**Fixed ✔**

* Bug in which the "Contact Us" button on the login page was not functioning properly, instead redirecting users back to the login page.
* Bug where, when specifying container logs storage during application version creation through the API, the storage was associated but not activated by default. As a result, users had to manually activate it using the dashboard form.

***

### 2023.01.17

**New ✨**

* Documentation has been restructured. We aim to make integration and search for core components of the Edgegap solution easier.

**Fixed ✔**

* Bug with the Container Registry page on the dashboard where you couldn't see your images if you previously pushed the same tag twice.
* The `kind` in the `session_config` key of the body of an application version creation response with the API will now be `Seat` instead of `seat` to be consistent with the parameter needed for the creation.

</details>

***

## 2022

<details>

<summary>View Release Notes 2022</summary>

### 2022.12.06

**New ✨**

* Organization Admins now have access to the approximate cost for the month on the billing page.

**Improvements 💪**

* API Documentation has been updated with relevant titles for all the routes.
* Added more locations to deploy your applications.
* Our documentation for the Unity plugin has been updated according to the last version.

**Fixed ✔**

* Bug where the deployment details page was sometimes returning a 500.

***

### 2022.11.21

**New ✨**

* You can now override your Dockerfile *ENTRYPOINT* and *CMD* by adding commands and arguments in your application version or deployment. [link](https://github.com/edgegap/docs-gitbook/blob/main/docs/application/command-override/README.md)
* Added more environment variables with your port information. It is now easier to retrieve the data without parsing a JSON string. [link](https://github.com/edgegap/docs-gitbook/blob/main/docs/deployment/injected-variables/README.md)

**Improvements 💪**

* Added more locations to deploy your applications.

**Fixed ✔**

* Bug where ports were not shown correctly on the "Save as New" application version page.

***

### 2022.11.08

**New ✨**

* Notifications on the dashboard are now available to give users feedback and visibility of the system status. Notifications will be gradually added over the following months.

**Improvements 💪**

* Improved look of the documentation homepage.
* Images from the dashboard have been updated in the documentation.
* Added more locations to deploy your applications.

***

### 2022.10.24

**New ✨**

* New location beacons that can be used to ping locations within our network to improve your matchmaking and deployment strategies. [link](https://github.com/edgegap/docs-gitbook/blob/main/docs/deployment/locations/beacons/README.md)
* Bulk delete is now available from the dashboard.
* 1:1 Port mapping is now available for legacy game servers or games that don't support NAT punching. [link](https://github.com/edgegap/docs-gitbook/blob/main/docs/application/one-to-one-port-mapping/README.md)
* Port mapping and IP of your deployment are now accessible as environment variables from your deployed container.
* Added multiple popular netcode samples and documentation to ease integration.

**Fixed ✔**

* A rare occurrence of error when accessing application version pages on the dashboard.

***

### 2022.10.11

**New ✨**

* Linking endpoint storage to an application version to store your logs will now be available from the API.
* All admins of an organization will now be able to see its invoices.

**Improvements 💪**

* Added more locations to deploy your applications.

**Fixed ✔**

* Fix the dashboard UI on the email management page.

***

### 2022.09.27

**New ✨**

* The billing invoice will now only contain a summary of your resource usage. You can still view a full detailed invoice from the Edgegap dashboard.

**Improvements 💪**

* UI updates of the dashboard to improve the first-time user experience and application creation.
* Added more locations to deploy your applications.

**Fixed ✔**

* Minor bug fixes and improvements.

***

### 2022.08.30

**New ✨**

* API route to get geolocation information about an IP or a list of IPs. link
* The termination grace period is now customizable within the settings of an app version. Increasing it lets you gracefully terminate your application before the container is fully killed.
* Best practices CICD guide to help integrate Edgegap in your development pipelines. [link](https://github.com/edgegap/docs-gitbook/blob/main/docs/tools-and-integrations/cicd-integration/README.md)

**Improvements 💪**

* Added information parsing buttons in the Container Registry section for easier manipulation of registry commands.

**Fixed ✔**

* Bug where Container Registry was throwing irrelevant error message in the dashboard when application count tier limit was reached.

***

### 2022.08.16

**New ✨**

* Bulk delete for deployments and sessions.

**Improvements 💪**

* More locations added in different parts of the world.
* Deployment created by the Fleet manager will be automatically tagged with the fleet and policy name.

**Fixed ✔**

* Bug where fleet's policy with coordinates without decimal wasn't working.

***

### 2022.08.02

**New ✨**

* Added a default configuration when creating a new Fleet in the Fleet Manager.
* Deployment storage is now available for small files (5Mb limit).

**Improvements 💪**

* Dashboard UI tweaks for consistency.

**Fixed ✔**

* Bug where app version ports were not showing on the app version page.

***

### 2022.07.19

**New ✨**

* Fleet manager: the Deployment Scaler has been removed and replaced by the Fleet Manager. You can now create your fleets, policies, and filters which will automatically scale your Sessions at specific sites according to your configuration. [Documentation](https://github.com/edgegap/docs-gitbook/blob/main/docs/deployment/session/fleet-manager/fleet/README.md)
* You can now check out Edgegap's services uptime here: <https://status.edgegap.com/>

**Improvements 💪**

* Small Dashboard UI change for consistency.

**Fixed ✔**

* Container image validation when updating an application version.
* The deployment's FQDN will no longer include the application name. It was sometime causing issues in the FQDN creation process. From now on, it will only have the deployment's request ID.

***

### 2022.06.21

**New ✨**

* Platform logs have been reworked to be more relevant to users.

**Fixed ✔**

* Bug with the application version image verification not working when using certain repository providers.
* Bug where using the "filters" body parameters with deployment without IPs was returning a bad request.

***

### 2022.06.07

**New ✨**

* API route to get your public IP

**Fixed ✔**

* Bug where the application versions errors were not cleaned correctly on an update, making our caching system disable cache for these versions needlessly.
* Bug where an error was returned when requesting deployment metrics via the API too soon in the deployment lifecycle.
* Bug when creating an application where the API was requesting optional parameters when using the "verify\_image" option.
* Bug with the container registry creating duplicate application versions when pushing the same tag multiple times.

***

### 2022.05.24

**New ✨**

* It's now possible to use a webhook for Sessions deployment when they are in a "ready" state.
* You can now deploy on a specific location by using a filter for a location tag.
* Added a contact form so you can get in touch with our team directly from the dashboard.

**Improvements 💪**

* Minor API changes to match the documentation correctly.

**Fixed ✔**

* API bug with the application version update where optional parameters were throwing validation error.
* Bug where the route to add players to a session, wasn't validating the session capacity.
* Bug that resulted in the session being put in error when an application startup timed out.

***

### 2022.05.10

**New ✨**

* Deployment timeline now available from the "Platform Info" tab on the deployment details page. This visual guide will make it easier to understand and improve each step of a deployment.

**Improvements 💪**

* A warning message will now be shown on the deployment page when deploying using a reserved IP (e.g. 0.0.0.0). Since the geolocation cannot be determined in such a case, the default location of this player will be at lat/long 0.0.
* Improvement in the Edge locations lifecycle management.

***

### 2022.05.03

**New ✨**

* Edgegap Container Registry: Push and update your container images directly to the fully integrated Edgegap Container Registry for a simple version management experience ([link](https://github.com/edgegap/docs-gitbook/blob/main/docs/container/edgegap-container-registry/README.md)).
* You can now exclude region, city, country, etc from your deployment request.
* You can now validate the container image for an application version to ensure that we can pull it from your selected registry. Available from the API and the dashboard.
* You can now download a preconfigured TOML file for our CLI directly on the app version detail page.

**Improvements 💪**

* Dashboard UI color-matching with our new website.
* New style for the login and registration pages.
* The documentation tree has been redesigned to facilitate integration flow.

**Fixed ✔**

* Rare cases of a 500 status on the deployments page.
* Bug where new users that didn't verify their email have a 500 status when trying to log in.

***

### 2022.02.29

**New ✨**

* You can now access the container logs of past deployments from the deployment history in the dashboard if you have setup an Endpoint Storage S3 bucket for your account.
* You can now request telemetry for a list of IPs on existing deployments. (Documentation coming soon)

**Improvements 💪**

* Readiness verification with TLS enabled ports.

**Fixed ✔**

* Session request creation with the dashboard wizard.

***

### 2022.02.14

**New ✨**

* Page to bulk delete app versions.
* Search bar in the application list.
* "Save as new" button for app version.
* Documentation on how to switch from Gamelift to Edgegap.
* Documentation on how to create a container for an Unreal Engine project.

**Improvements 💪**

* Removed misleading logs when using the TLS upgrade option on ports.
* More comprehensible response from the API when sending a bad JSON request.
* 23 more instant locations available for deployments.

**Fixed ✔**

* Managed Matchmaker without filters sometimes crashes.

***

### 2022.02.01

**New ✨**

* CLI to help you manage your application versions and simplify integration with your CICD pipeline.
* Edgegap Unity package to help you deploy and test faster directly from the Unity Engine.

**Improvements 💪**

* More flex locations are now available.

**Fixed ✔**

* Matchmaker update form missing the name field

***

### 2022.01.18

**Improvements 💪**

* Image repository name validation with a regex ensuring valid value.

**Fixed ✔**

* Various minor fixes.

***

### 2022.01.06

**New ✨**

* Experimental code-free matchmaker.
* Crash logs are now available in Arbitrium OpenMatch.
* Save your crash logs directly in your S3 bucket.

**Improvements 💪**

* More detailed dashboard register page.
* Updated Demo application with more information about your deployment.
* Better display for invoices.

**Fixed ✔**

* Fix a bug where the session linking process was not taking the best deployment available.
* Platform and container logs displayed in the dashboard were sometimes showing nothing and/or having weird behavior.
* Download pod logs in Arbitrium OpenMatch.

</details>

***

## 2021

<details>

<summary>View Release Notes 2021</summary>

### 2021.12.08

**New ✨**

* Sessions selectors for better management between session and deployment ([link](https://github.com/edgegap/docs-gitbook/blob/main/docs/deployment/session/selectors/README.md)).

**Improvements 💪**

* Platform registration is now protected by reCAPTCHA.
* Added more Flex Locations.

**Fixed ✔**

* Logging issue spamming an error while an application was caching.

***

### 2021.11.23

**New ✨**

* System will automatically disable the cache on unused app versions ([link](https://github.com/edgegap/docs-gitbook/blob/main/docs/application/version/README.md#optional-fields)).
* Free swag available once registered on the platform.
* Session automatic cleaner. No more need to manually delete unlinked sessions ([link](https://github.com/edgegap/docs-gitbook/blob/main/docs/deployment/session/more-in-depth/README.md#empty-time-to-live)).

**Improvements 💪**

* Tier management got more simple with more insights when you need to downgrade or upgrade your tier.
* You can now choose to connect automatically in your organization instead of switching each time you log in.
* When switching in organization view, you will stay on the page you were on.
* Organization owners can now set admins that can invite people to your organization.
* Deployments logs will now indicate if you need to increase your minimal time to deploy or activate caching in case of deployment in an error state.

**Fixed ✔**

* Bug where you were unable to edit env containing "\n" for app versions and matchmakers.
* Bug where JSON snippet tool in app creation form page wasn't displaying anything. Also improved its content to reflect the API validations.
* Correction of some location information.

***

### 2021.11.02

**New ✨**

* You can now test an application deployment faster with our one-click application creation.
* Access logs of a crashed container directly from the deployment page.
* Upgrade HTTP and Websocket connections to HTTPS and WSS with the flip of a switch.

**Improvements 💪**

* Application ports now support naming for easier retrieval.
* Simplified application creation form for easier understanding. Advanced settings are still available.
* We now have a C# tutorial for OpenMatch integration.
* More location are available with advanced setting.

**Fixed ✔**

* Unusual caching behavior.

***

### 2021.10.05

**New ✨**

* Self-serve, You can now easily register and get some deployments going. However, we will always be here to help.
* The sidebar showes all the new supported tech staks @ Edgegap.
* Integration with a payment processor, you will have the option to pay your Edgegap Invoices in Arbitrium directly.
* We integrate support with Intercom support directly on your dashboard.
* Edgegap Wallet.

**Improvements 💪**

* We added new edges sites with more features.
* With one click, you can see the amount of credit remaining in your Edgegap wallet.

**Fixed ✔**

* Added a longer timeout in ASD.
* Minor cosmetic changes.

***

### 2021.08.18

**New ✨**

* Ship your logs to S3
  * We added a new feature that allows you to store your container logs automatically to S3 type storage. You can follow our doc to enable it on your apps. [link](https://github.com/edgegap/docs-gitbook/blob/main/docs/deployment/endpoint-storage/save-container-logs/README.md).

**Improvements 💪**

* Arbitrium Open Match
  * Have a look at our new feature in the Matchmaker UI that is helping you troubleshoot your deployment now with Container logs.
* Session-based deployment
  * We added more ways of doing Session-based deployment to ease your work.
  * The “Create Deployment” Button Allow to send a Session instance for Session-Based Application

**Fixed ✔**

* Various minor bugfix and enhancements in the decision-making process.
* Various updates and hardening were done on the edge computing infrastructure.
* Arbitrium Webhook feature will return if the Deployment is in Error in early steps of the process.

***

### 2021.06.18

**New ✨**

* Arbitrium Open Match
  * You can now create your Matchmaker directly in the Arbitrium Dashboard. You can follow the documentation under the Arbitrium Open Match.
* SDK generator in Dashboard
  * Increased integration between your game server and Arbitrium, you can now generate the SDK from our Dashboard.
* Protection of your deployment is automatically done.
  * If enabled in your application configuration, Arbitrium will protect your deployment. It will be reached ONLY from IP that you allowed.
* Organization support in Dashboard.
  * We have the support of Organizations built into your Dashboard. Invite your coworker to the Organization, then hop in and out to leverage the same account.
* Deployment context in the container
  * We added more information in your container. When you need to have scripts in your container to display information on the location, see the documentation to get more information on this feature.
* Session-based deployment
  * We support matches that players are getting in and out of at any given time. We will add the players in the session that is in the best location for your players.
  * The horizontal Deployment Session tool will make sure you always have sessions ready to onboard new players.

**Improvements 💪**

* New steps when terminating a deployment
  * We have a new Step when stopping a deployment, Ready -> Terminating -> Terminated.
* SSO in Arbitrium
  * We support using Okta as the Single Sign-On on all Edgegap tools.
* Dashboard Tags
  * You can see all tags assigned to your application deployment in your deployment list.

**Fixed ✔**

* When starting new processes in your container, the display of your CPU/RAM metrics was not rendered correctly in the Dashboard.
* When only one access point available, an empty IP string, the deployment was not processed correctly.

***

### 2021.04.30

**New ✨**

* We support Tier management by clients.
* New Awesome landing page with essential stats to Studio about your deployments.
* Security to the Dashboard login process.

**Improvements 💪**

* You can disable port validation from the app version page.
* Add the flexibility to allow more time to deploy the application before cached on the Access Points.
* Caching is done passively at the Edge level now.

**Fixed ✔**

* Fixed inconstancy in ping results
* Fixed typos in documentation.

***

### 2021.04.15

**New ✨**

* Now supporting AWS Local Zone

**Improvements 💪**

* Improvements made to the console logs, it's now reflecting more what a console should look like

**Fixed ✔**

* Fixed small memory leaks
* Fixed small monitoring bug
* Fixed handled-error showing as error in logs

***

### 2021.03.09

**New ✨**

* Added the option to deploy to a specific location without taking telemetry data into account.
* Added a “lock deployment” option if you need to have a deployment remain active. As long as a deployment is locked, the deployment cannot be killed by any requests.
* Added access to more information inside our BigData repository directly from the Dashboard.
* Added support for returning information to your matchmaker using a webhook.
* Added the ability to create tags on your deployments to identify them more easily.

**Improvements 💪**

* Improvements made to the DDOS detection for game servers.
* Improvements on how we handle traffic coming into our Arbitrium platform to make it faster and more secure.
* Improved the Documentation.

**Fixed ✔**

* Fixing minor bugs for stability in the backend.

</details>


# Sample Projects

Explore our sample projects and build on a strong multiplayer foundation:

<table data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td><a href="/pages/Br84AQH1TLEiyVr6zhXF">Lyra Sample<br>(Unreal Engine)</a></td></tr><tr><td><a href="/pages/NbKqNN5Lnq61udFqMeuX">Top-Down Sample<br>(Unreal Engine)</a></td></tr></tbody></table>

***

<table data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td><a href="/pages/bIDSLe6tducdvOA28vTl">Asteroids Sample<br>(Unity + Photon Fusion 2)</a></td></tr><tr><td><a href="/pages/JsJeBB8NynNMoIrY3zgI">Tanknarok Sample<br>(Unity + Photon Fusion 1)</a></td></tr><tr><td><a href="/pages/ofNQZmw58tOnPd4iilUl">2D Space Shooter<br>(Unity + NGO)</a></td></tr><tr><td><a href="/pages/yyqf6sueb8ydtVUbmumU">Pong Sample<br>(Unity + Mirror)</a></td></tr><tr><td><a href="/pages/Ss8FcIG42oR2d3yDR4uf">Space Edge Sample<br>(Unity + FishNet)</a></td></tr><tr><td><a href="/pages/Ky4ArBSwS37WVlKw4MZ7">Demo Sample<br>(Unity + Photon Bolt)</a></td></tr><tr><td><a href="/pages/uloeNBPSmIM3JPBZxmRy">Tanks WebGL Sample<br>(Unity + Mirror)</a></td></tr><tr><td><a href="/pages/sQqgyiOXuNUKqZ7VNaSE">Space Edge WebGL Sample<br>(Unity + FishNet)</a></td></tr></tbody></table>

***

<table data-view="cards"><thead><tr><th></th></tr></thead><tbody><tr><td><a href="/pages/WSNSkPNKkkHMQ43MslrJ">Single Page App Sample<br>(NuxtJS)</a></td></tr><tr><td><a href="/pages/Svl1pjN7CWsIskYNn3H1">MVC Web App Sample<br>(Ruby on Rails)</a></td></tr></tbody></table>


# Unreal Engine


# Lyra Sample

Explore how to host Unreal Engine projects on Edgegap using techniques from the [Lyra Sample](https://dev.epicgames.com/documentation/en-us/unreal-engine/lyra-sample-game-in-unreal-engine).

## ✔️ Preparation

Before we start, you will need:

* Unreal Engine 5.5 - [download using Epic Games Launcher](https://www.unrealengine.com/en-US/download),
* Lyra Starter Game - [download from Fab](https://www.fab.com/listings/93faede1-4434-47c0-85f1-bf27c0820ad0).

Open your Epic Games launcher and navigate to Unreal Engine / Library / Fab Library.

Search for "lyra", then Create Project. We recommend using SSD for faster builds.

## ⚡ Deploy and Connect

### 1. Deploy a Server on Edgegap

☑️ To get started, you'll need to [create a free account with Edgegap](https://app.edgegap.com/auth/register). No credit card required.

☑️ [Create a new app version for your application](https://app.edgegap.com/application-management/applications/lyra-sample/versions/create), choose Lyra Sample.

☑️ [Deploy a server with your Lyra Sample app version](https://app.edgegap.com/deployment-management/deployments/list).

☑️ [Open your new deployment's details](https://app.edgegap.com/deployment-management/deployments/list).

☑️ Find your deployment's unique, one-time connection details:

* **Host URL** in format `780aa4260e83.pr.edgegap.net` ,
* **External Port** in format `30854`  (5 digits).

✅ You may now proceed to the next step.

### 2. Play In Editor (PIE)

☑️ Navigate to your new Lyra Starter Game project root folder on your drive.

☑️ Edit file Config / DefaultEngine.ini with a text editor (such as notepad).

☑️ For each `[section]`  below, add the following contents if it exists or create the section.

```
[ConsoleVariables]
net.IgnoreNetworkChecksumMismatch=1
net.CurrentHandshakeVersion=2
net.MinHandshakeVersion=2
net.VerifyNetSessionID=0
net.VerifyNetClientID=0

[/Script/Engine.GameEngine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions(DefName="GameNetDriver",DriverClassName="OnlineSubsystemUtils.IpNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")

[/Script/OnlineSubsystemUtils.IpNetDriver]
MaxClientRate=1000000000 
MaxInternetClientRate=1000000000
InitialConnectTimeout=120.0

[/Script/BuildSettings.BuildSettings]
DefaultServerTarget=LyraServer
```

☑️ Open your new project in Unreal Engine.

☑️ Press the ▶️ Play button to start your game client.

☑️ Press tilde `~`  and input `open {host}:{port}`  with connection details from previous step.&#x20;

🙌 Congratulations on your first Deployment on Edgegap!

## ✏️ Customize Server Build

{% hint style="success" %}
See [Unreal Engine](/unreal-engine) for Unreal Engine to **learn how to build and customize servers**.
{% endhint %}


# Top-Down Sample

Launching an Unreal Engine game with dedicated game servers can be daunting project, from individual developers to experienced medium-sized teams.

Fortunately, the Edgegap platform makes it easy, with a simple integration process that takes minutes. Your game will then be online, ready to connect players worldwide to play. Helping you allocate precious development resources to what you love – making a great game.

Here is Edgegap's plugin video tutorial:

{% embed url="<https://youtu.be/J8WNC6itYTE>" %}

***

{% hint style="success" %}
See our [Unreal Engine sample project](https://github.com/edgegap/unreal-sample) using the Top-Down sample for inspiration.
{% endhint %}

{% hint style="info" %}
See [Unreal Engine](/unreal-engine) for Unreal Engine to **learn how to build and customize servers**.
{% endhint %}


# Unity


# Photon Fusion 2

Explore how to host Unity projects on Edgegap using techniques from the [Fusion 2 Asteroids Sample](https://doc.photonengine.com/fusion/current/game-samples/fusion-asteroids).

{% embed url="<https://youtu.be/bg41HpPmE3E>" %}

## ✔️ Preparation

Before we start, you will need:

* Unity 6 - [download using Unity Hub](https://unity.com/releases/unity-6),
* Fusion 2 Asteroids Sample project (modified for Edgegap) - [download from GitHub](https://github.com/edgegap/netcode-sample-photon-fusion-2).

## ⚡ Deploy and Connect

### 1. Deploy a Server on Edgegap

☑️ To get started, you'll need to [create a free account with Edgegap](https://app.edgegap.com/auth/register). No credit card required.

☑️ [Create a new app version for your application](https://app.edgegap.com/application-management/applications/fusion-2-asteroids-sample/versions/create), choose Fusion 2 Asteroids Sample.

☑️ Open your new project in Unity.

☑️ Navigate to Tools / Edgegap Hosting, then to step 6. Deploy a Server on Edgegap.

☑️ Press Deploy to cloud and [open your new deployment's details](https://app.edgegap.com/deployment-management/deployments/list).

☑️ Find your deployment's unique, one-time connection details:

* **Host URL** in format `780aa4260e83.pr.edgegap.net` .

✅ You may now proceed to the next step.

### 2. Connect from Editor

☑️ Open your new project in Unity.

☑️ Press the ▶️ Play button to start your game client.

☑️ Input Host URL (connection details from previous step) as room name.

☑️ Press Start Edgegap button to connect to your server.

☑️ Connect a second virtual Player with [Multiplayer Play Mode](https://docs-multiplayer.unity3d.com/mppm/current/about/) or [ParrelSync](https://github.com/VeriorPies/ParrelSync).

🙌 Congratulations on your first Deployment on Edgegap!

## ✏️ Customize Server Build

{% hint style="success" %}
See [Unity](/unity) for Unity to **learn how to build and customize servers**.
{% endhint %}

### Create an App on Photon

{% hint style="info" %}
To make the initial demo easy, we used a free tier Photon Cloud account owned by Edgegap.
{% endhint %}

☑️ [Create a free account with Photon](https://dashboard.photonengine.com/).

☑️ [Create an Application on Photon](https://dashboard.photonengine.com/app/create):

* `Multiplayer Game`,
* `Fusion` Photon SDK,
* `Fusion 2` SDK version.

☑️ Find your Application ID in format `85314a99-56fc-4ab3-ba26-50efca09f303` .

☑️ Input your ID in Photon Settings under Tools / Fusion / Fusion Hub (Alt + F).

### Integrate Fusion 2 Project with Edgegap

{% hint style="success" %}
See `EdgegapServerManager.cs`  script for an example integration of Fusion 2 with Edgegap.
{% endhint %}

{% hint style="warning" %}
Your `NetworkProjectConfig`  **must use `Peer Mode = Single` (Ded. Server)**, not `Multiple` (Client-Host)!
{% endhint %}

Game clients will connect to game servers through [Photon Fusion 2 ](https://doc.photonengine.com/fusion/current/manual/connection-and-matchmaking/matchmaking#creating-and-joining-a-game-session)[Session (room) ](https://doc.photonengine.com/fusion/current/manual/connection-and-matchmaking/matchmaking#creating-and-joining-a-game-session)[feature](https://doc.photonengine.com/fusion/current/manual/connection-and-matchmaking/matchmaking#creating-and-joining-a-game-session).

Your game server needs to register it's IP address and external port with the Session name, when calling `_runnerInstance.StartGame(StartGameArgs args)`:

* use `GameMode.Server`  to ensure your connection isn't relayed over Photon Cloud (adds latency),
* use `NetAddress.CreateFromIpPort` method from Fusion,
* provide server's IP address:\
  `Environment.GetEnvironmentVariable("ARBITRIUM_PUBLIC_IP")`
* provide server's external port:\
  `Environment.GetEnvironmentVariable("ARBITRIUM_PORT_GAMEPORT_EXTERNAL")`
  * this is the default port mapping name if you use our [Unity plugin to package your server](/unity).

Use Edgegap Deployment Host URL as a Session name:

* in game server, get it with\
  `$"{Environment.GetEnvironmentVariable("ARBITRIUM_REQUEST_ID")}.pr.edgegap.net"`
* when testing game client, get it from dashboard - deployment details page / Host URL
  * create deployments for testing from our [quickstart hosting plugin](/unity/developer-tools#dedicated-servers-quickstart-plugin),
* when live in game client, get it from [matchmaking / ticket assignment / fqdn](/learn/matchmaking/matchmaker-in-depth#player-api):
  * see also [getting started with Matchmaking](/learn/matchmaking).

{% tabs %}
{% tab title="Client Integration" %}
{% code title="EdgegapClientManager.cs" %}

```csharp
// get fusion 2 network runner
_runnerInstance = FindFirstObjectByType<NetworkRunner>();
_runnerInstance.ProvideInput = true;

// get this value from Edgegap Matchmaker Assignment or Fusion 2 Sessions API
string sessionName = "<requestId.pr.edgegap.net>";

// find fusion 2 session (using session name)
var result = await _runnerInstance.StartGame(
    new StartGameArgs() {
        GameMode = GameMode.Client,
        SessionName = sessionName,
        ObjectProvider = _runnerInstance.GetComponent<NetworkObjectPoolDefault>(),
    }
);

// fusion will now fetch server IP and port based on session name and connect
```

{% endcode %}
{% endtab %}

{% tab title="Server Integration" %}
{% code title="EdgegapServerManager.cs" %}

```csharp
// read edgegap injected environment variables
string requestId = Environment.GetEnvironmentVariable("ARBITRIUM_REQUEST_ID");
string listenPort = Environment.GetEnvironmentVariable("ARBITRIUM_PORT_GAMEPORT_INTERNAL");
string connectIP = Environment.GetEnvironmentVariable("ARBITRIUM_PUBLIC_IP");
string connectPort = Environment.GetEnvironmentVariable("ARBITRIUM_PORT_GAMEPORT_EXTERNAL");

// get fusion 2 network runner
_runnerInstance = FindFirstObjectByType<NetworkRunner>();
_runnerInstance.ProvideInput = true;

// register fusion 2 session for clients to find it later (using session name)
var result = await _runnerInstance.StartGame(
    new StartGameArgs() {
        GameMode = GameMode.Server,
        SessionName = $"{requestId}.pr.edgegap.net",
        ObjectProvider = _runnerInstance.GetComponent<NetworkObjectPoolDefault>(),
        Address = NetAddress.Any(listenPort),
        CustomPublicAddress = NetAddress.CreateFromIpPort(connectIP, connectPort),
    }
);

// load initial scene
if (result.Ok && _runnerInstance.IsServer) {
    await _runnerInstance.LoadScene(sceneName);
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Troubleshooting

<details>

<summary><code>Game Does Not Exist (32758)</code></summary>

* Photon rooms require the player to be connecting to the Photon Cloud region where the deployment is located. Deploying from dashboard uses a randomized player IP.
* You may need to find the deployment's location on the map, and configure your game client in Assets / Photon / Fusion / Resources / PhotonAppSettings.asset with the matching [Photon Cloud region](https://doc.photonengine.com/fusion/current/manual/connection-and-matchmaking/regions#photon-cloud-for-gaming).
* Edgegap [deploys as close to the player as possible](https://docs.edgegap.com/docs/sample-projects/unity-netcodes/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-server-score-strategy-best-practice) when using our [plugins](/unity/developer-tools) or [API](/docs/api/dedicated-servers) so in production, setting Photon Cloud region is not required.

</details>


# Photon Fusion 1

### Photon Fusion on Arbitrium

This guide will help you create a headless server on Edgegap for a Unity project using [Photon Fusion](https://doc.photonengine.com/en-us/fusion/current/getting-started/fusion-intro) as its networking solution.

We will use the sample project [Tanknarok from Photon Fusion](https://doc.photonengine.com/en-us/fusion/current/samples/fusion-tanknarok) as an example.

Because Edgegap works best when it knows where all the players are (so it can pick a server in the optimal location) the Tanknarok game will first start in client-hosted mode and then switch to the Edgegap server once all the players have joined. This is because Tanknarok has a playable lobby that runs before the match starts where players are able to join and leave at will. So the flow of logic is as follow:

1. Players connect to the same room using `AutoHostOrClient` mode. Which automatically picks the first player in the room as the host.
2. All players send their IP address to the host when they join.
3. Once the players ready-up, the host uses these IP addresses to start an Edgegap server version of the game with a randomized room code (using a guid).
4. When the host sees that the Edgegap instance has finished deploying, it tells the other players about the new room code.
5. All players, including the host, disconnect from their current room and reconnect in Client mode to the new room being hosted by the Edgegap server.
6. When the Edgegap server sees all expected players have joined, it starts the match.
7. When the Edgegap server sees that everyone has left the match, it shuts itself down.

### Account setup

To run this application, you will need to have both a Photon account and an account with Edgegap. First, you will need to make a Fusion app on the Photon dashboard and set the app id in the `PhotonAppSettings` scriptable object file. Then, you will need to add an application and create an API token on the dashboard. You will use these to set the App Name, Version, and Api Token in the `EdgegapConfig` scriptable object file.

{% hint style="info" %}
When making the Edgegap app, this program requires 512 CPU units and 512 MB of memory.
{% endhint %}

There are also two plugins to help with development: Newtonsoft Json for better Json serialization and [ParrelSync](https://github.com/VeriorPies/ParrelSync) for easier testing with multiple unity editors.

### Step 1: Server mode setup

You can grab the modified Tanknarok project on [GitHub](https://github.com/edgegap). You will also need to import the [Photon Fusion SDK](https://doc.photonengine.com/en-us/fusion/current/getting-started/sdk-download) into the project.

The Photon Fusion Tanknarok project needed to be slightly altered to work as a server. This mostly involved automatically starting the match and setting the mode.

1. Modified `GameLauncher.cs` to launch on start with `GameMode.Server`.
2. Modified `FusionLauncher.cs` to call `_spawnWorldCallback` once game is running instead of relying on `InstantiatePlayer` (since no players are instantiated for server).
3. Skipping the lobby scene if it’s a server and going straight to the match when all the players have joined.

This has already been completed in the modified Tanknarok project available our [GitHub](https://github.com/edgegap).

#### Dockerfile

The docker file is pretty basic. We’re just using an ubuntu base, copying over the compiled linux server build, setting the permissions on the file, and running it.

### Step 2: The first client

The initial client takes on a role of host and of managing the Edgegap server. It does this through the `EdgegapManager.cs` file, which in turn uses the `EdgegapAPIInterface.cs` file, which wraps up most of the functionality surrounding interacting with Edgegap.

In `GameManager.cs`, the `OnAllPlayersReady` function for the host will deploy an Edgegap server instead of starting the match. It does this based on the IP addresses of all the players. Unity coroutines allow these actions to happen asynchronously. The Deploy method takes a callback function, which is fed the name of the new room the server will use. A Photon RPC function is sent to signal to all the clients to switch over.

```cs
    if (EdgegapManager.EdgegapPreServerMode)
    {
        //if host, spin up new edgegap server
        //once server is ready, connect to it

        FindObjectOfType<GameLauncher>().ShowMatchStartingUI();

        if (Object.HasStateAuthority)
        {
            string[] ips = PlayerManager.allPlayers.Select(p => p.ipAddress).ToArray();
            StartCoroutine(EdgegapManager.Instance.Deploy(ips, RpcOnEdgegapServerReady));
        }
    }
    else
    {
        LoadLevel(_levelManager.GetRandomLevelIndex(), -1);
    }
```

The switch over happens in an empty scene named `TransferringScene`.

Inside EdgegapManager, a randomized room code is created and passed, along with the player count as Environment variables to the newly created instance through the deploy API. This is combined with the `status` api calls that start checking once a second to see when the instance is done deploying. You could also use the [webhook](broken://pages/v9GuToulGG9XRGbptZwc) option.

```cs
    /// <summary>
    /// Calls the Edgegap API to deploy a server
    /// </summary>
    /// <param name="clientIpList">A list of client IP addresses</param>
    /// <param name="OnDeployed">A callback method with the session name as a parameter</param>
    public IEnumerator Deploy(string[] clientIpList, Action<string> OnDeployed = null)
    {
        var roomCode = Guid.NewGuid().ToString();
        yield return EdgegapAPIInterface.RequestDeployment(clientIpList,
             new Dictionary<string, string> {
                { "room_code", roomCode },
                { "player_count", clientIpList.Length.ToString() } });

        yield return EdgegapAPIInterface.WaitForDeployment();

        OnDeployed(roomCode);
    }
```

### Step 3: Joining players

Besides switching over to the new Edgegap server when told to, joining players also need to tell the host player what their IP address is. Since Photon doesn’t provide this information, an RPC call in `GameManager` called `ShareIpAddress` is called from the `Player.cs` class in the `Spawned` method.

The host then stores these IP addresses in the player class for all the players based on who the source of the RPC message was:

```cs
    [Rpc(sources:RpcSources.All, RpcTargets.StateAuthority)]
    public void RpcShareIpAddress(string ipAddress, RpcInfo info = default)
    {
        if (info.Source == PlayerRef.None)
        {
            // source will be None for local player
            Player.local.ipAddress = ipAddress;
            return;
        }

        foreach (var player in PlayerManager.allPlayers)
        {
            if (player.playerID == info.Source.PlayerId)
            {
                player.ipAddress = ipAddress;
                break;
            }
        }
    }
```

### Step 4: Cleaning up

Back in the server, the modified `GameManager.cs` keeps track of the number of players in the match in the main Update function. It both checks for when the target number of players have joined, so it can start the match, but also determines when it’s time to shut itself down. If the game has been running for over a minute and there aren’t any players left, it calls the Edgegap API to stop its own deployment.

```cs
    if (EdgegapManager.IsServer())
    {
        if (GameManager.playState == GameManager.PlayState.LOBBY)
        {
            // if we're in the lobby and all players have joined, start the match
            if (PlayerManager.allPlayers.Count >= EdgegapManager.ExpectedPlayerCount)
            {
                OnAllPlayersReady();
            }
        }

        if(Time.time > 60 && PlayerManager.allPlayers.Count == 0
            && GameManager.playState != GameManager.PlayState.TRANSITION)
        {
            // if it's been over a minute and there's no one here, stop the server
            Debug.LogWarning("Shutting Down");
            StartCoroutine(EdgegapAPIInterface.StopDeploymentFromServer());
            GameManager.playState = PlayState.TRANSITION;
        }
    }
```

You now have a Photon Fusion project available to deploy on demand!


# Unity NGO

Explore how to host Unity projects on Edgegap using techniques from the [NGO Boss Room Sample](https://unity.com/demos/small-scale-coop-sample).

{% hint style="info" %}
This sample **does not require any Unity Gaming Services (UGS)**, Multiplay, or Relays to run.
{% endhint %}

{% embed url="<https://youtu.be/8Qi-wXswjkw>" %}

## ✔️ Preparation

Before we start, you will need:

* Unity 6 - [download using Unity Hub](https://unity.com/releases/unity-6),
* Unity NGO \[Netcode for Game Objects] Boss Room Sample project (modified for Edgegap):
  * [download from Github](https://github.com/edgegap/netcode-sample-unity-ngo-bossroom).

## ⚡ Deploy and Connect

### 1. Deploy a Server on Edgegap

☑️ To get started, you'll need to [create a free account with Edgegap](https://app.edgegap.com/auth/register). No credit card required.

☑️ [Create a new app version for your application](https://app.edgegap.com/application-management/applications/unity-ngo-bossroom-sample/versions/create), choose NGO Boss Room Sample.

☑️ [Deploy a server with your NGO Boss Room Sample app version](https://app.edgegap.com/deployment-management/deployments/list).

☑️ [Open your new deployment's details](https://app.edgegap.com/deployment-management/deployments/list) and find your unique, one-time connection details:

* **External Port** in format `30854`  (5 digits).

☑️ Navigate to tab Logs and find (CTRL+F) your unique, one-time connection details:

* `ARBITRIUM_PUBLIC_IP` in format `172.234.244.38` .

✅ You may now proceed to the next step.

### 2. Connect from Editor

☑️ Open your new project in Unity.

☑️ Verify that you have opened scene: `Assets/Scenes/MainMenu.unity`.

☑️ Press the ▶️ Play button to start your game client:

* press **START WITH DIRECT IP** button,
* select tab **JOIN WITH IP.**

☑️ Input connection details from previous step.

☑️ Press **JOIN** button to connect to your server.

☑️ Connect a second virtual Player with [Multiplayer Play Mode](https://docs-multiplayer.unity3d.com/mppm/current/about/) or [ParrelSync](https://github.com/VeriorPies/ParrelSync).

🙌 Congratulations on your first Deployment on Edgegap!

## ✏️ Customize Server Build

{% hint style="success" %}
See [Unity](/unity) for Unity to **learn how to build and customize servers**.
{% endhint %}

### Run as Dedicated Server

For this sample to run as dedicated server, we made the following changes:

{% tabs %}
{% tab title="Server Initialization" %}
New script (added to your `MainMenu` scene in a new empty `GameObject`):

<pre class="language-csharp" data-title="Assets/Scripts/EdgegapServerStarter.cs" data-line-numbers><code class="lang-csharp">using System;
using System.Collections;
using System.Collections.Generic;
using Unity.BossRoom.ConnectionManagement;
using UnityEngine;

namespace Unity.Multiplayer.Samples.BossRoom
{
    public class EdgegapServerStarter : MonoBehaviour
    {
<strong>        public string portMapName = <a data-footnote-ref href="#user-content-fn-1">"gameport"</a>;
</strong>
        // Start is called before the first frame update
        void Start()
        {
            if (Application.isBatchMode)
            {
                ConnectionManager connectionManager = GameObject.Find("ConnectionManager").GetComponent&#x3C;ConnectionManager>();
<strong>                string internalPortAsStr = Environment.GetEnvironmentVariable($"ARBITRIUM_PORT_{portMapName.ToUpper()}_INTERNAL");
</strong>
                if (internalPortAsStr == null || !ushort.TryParse(internalPortAsStr, out ushort port))
                {
                    throw new Exception($"Could not find port mapping, make sure your app version port name matches with \"{portMapName}\"");
                }

<strong>                connectionManager.StartHostIp("<a data-footnote-ref href="#user-content-fn-2">SERVER</a>", "0.0.0.0", port);
</strong>            }
        }
    }
}
</code></pre>

{% endtab %}

{% tab title="Connection Management" %}
Modified files:

<pre class="language-csharp" data-title="Assets/Scripts/ConnectionManagement/ConnectionMethod.cs"><code class="lang-csharp"><strong>116        public override async Task SetupHostConnectionAsync()
</strong>117        {
<strong>118            <a data-footnote-ref href="#user-content-fn-3">//SetConnectionPayload(GetPlayerId(), m_PlayerName); // Need to set connection payload for host as well, as host is a client too</a>
</strong>119            var utp = (UnityTransport)m_ConnectionManager.NetworkManager.NetworkConfig.NetworkTransport;
120            utp.SetConnectionData(m_Ipaddress, m_Port);
121        }
</code></pre>

<pre class="language-csharp" data-title="Assets/Scripts/ConnectionManagement/ConnectionState/StartingHostState.cs"><code class="lang-csharp"><strong>66        async void StartHost()
</strong>67        {
              ...

72                // NGO's StartHost launches everything
<strong>--                <a data-footnote-ref href="#user-content-fn-3">//if (!m_ConnectionManager.NetworkManager.StartHost())</a>
</strong><strong>73                <a data-footnote-ref href="#user-content-fn-4">if (!m_ConnectionManager.NetworkManager.StartServer())</a>
</strong>74                {
75                    StartHostFailed();
76                }

              ...
</code></pre>

<pre class="language-csharp" data-title="Assets/Scripts/Gameplay/GameplayObjects/Character/ClientCharacter.cs"><code class="lang-csharp"><strong>112        public override void OnNetworkSpawn()
</strong>113        {
               ...

125            m_ServerCharacter.IsStealthy.OnValueChanged += OnStealthyChanged;
126            m_ServerCharacter.MovementStatus.OnValueChanged += OnMovementStatusChanged;
<strong>127            <a data-footnote-ref href="#user-content-fn-3">//OnMovementStatusChanged(MovementStatus.Normal, m_ServerCharacter.MovementStatus.Value);</a>
</strong> 
               ...
</code></pre>

{% endtab %}

{% tab title="Editor/Server Compatibility" %}
Modify files:

<pre class="language-csharp" data-title="Assets/Scripts/ConnectionManagement/ConnectionState/HostingState.cs"><code class="lang-csharp"><strong>148        ConnectStatus GetConnectStatus(ConnectionPayload connectionPayload)
</strong>149        {
               ...
                
<strong>155            <a data-footnote-ref href="#user-content-fn-5">//if (connectionPayload.isDebug != Debug.isDebugBuild)</a>
</strong><strong>156            //{
</strong><strong>157            //    return ConnectStatus.IncompatibleBuildType;
</strong><strong>158            //}
</strong>
               ...
</code></pre>

{% endtab %}
{% endtabs %}

[^1]: default port name on Edgegap

[^2]: player name for server can be any value

[^3]: p2p only ⇒ comment this out

[^4]: start as dedicated server instead

[^5]: Editor compatibility ⇒ comment this out


# Mirror Pong

Explore how to host Unity projects on Edgegap using techniques from the [Mirror Pong Sample](https://mirror-networking.gitbook.io/docs/manual/examples/pong).

{% embed url="<https://youtu.be/bvBkObGLTMk>" %}

## ✔️ Preparation

Before we start, you will need:

* Unity 6 - [download using Unity Hub](https://unity.com/releases/unity-6),
* Mirror Unity package - [download from Unity Asset Store](https://assetstore.unity.com/packages/tools/network/mirror-129321),
  * this package includes the Pong sample.

Create a new project and import the Mirror package above.

## ⚡ Deploy and Connect

### 1. Deploy a Server on Edgegap

☑️ To get started, you'll need to [create a free account with Edgegap](https://app.edgegap.com/auth/register). No credit card required.

☑️ [Create a new app version for your application](https://app.edgegap.com/application-management/applications/mirror-pong-sample/versions/create), choose Mirror Pong Sample.

☑️ [Deploy a server with your Mirror Pong Sample app version](https://app.edgegap.com/deployment-management/deployments/list).

☑️ [Open your new deployment's details](https://app.edgegap.com/deployment-management/deployments/list).

☑️ Find your deployment's unique, one-time connection details:

* **Host URL** in format `780aa4260e83.pr.edgegap.net` ,
* **External Port** in format `30854`  (5 digits).

✅ You may now proceed to the next step.

### 2. Connect from Editor

☑️ Open your new project in Unity.

☑️ Open scene in folder Mirror / Examples / Pong / Scenes / MirrorPong.unity.

☑️ Press the ▶️ Play button to start your game client.

☑️ Input connection details from previous step.

☑️ Press Client button to connect to your server.

☑️ Connect a second virtual Player with [Multiplayer Play Mode](https://docs-multiplayer.unity3d.com/mppm/current/about/) or [ParrelSync](https://github.com/VeriorPies/ParrelSync).

🙌 Congratulations on your first Deployment on Edgegap!

## ✏️ Customize Server Build

{% hint style="success" %}
See [Unity](/unity) for Unity to **learn how to build and customize servers**.
{% endhint %}


# Mirror

### Mirror on Edgegap

This guide will help you create a headless server on Edgegap for a Unity project using [Mirror](https://mirror-networking.com/) as its networking solution.

This guide will use the open-source sample project `Tanks`, which is already available in your Mirror sample, location Assets/Mirror/Examples/Tanks.

The final sample can be found on our [GitHub](https://github.com/edgegap/netcode-sample-unity-mirror).

### Build the game server

Once ready with your game, head to the `Build` screen of the Unity Editor, under `File -> Build Settings` in the top menus. Make sure to select the right presets depending on your version of Unity.

* Prior to version 2021.2:
  * Set `Target Platform` to `Linux`;
  * Set `Architecture` to `x86_64`;
  * Check the `Server Build` option.
* Otherwise:
  * Set `Platform` to `Dedicated Server`;
  * Set `Target Platform` to `Linux`.

Then press build and select a new empty folder named `linux_server` as the file destination. Transfer `linux_server` folder to a second empty folder, which will be refered as the `[SERVER BUILD]` folder in this document.

### Containerizing the dedicated game server

We will create a docker image containing the dedicated game server in this part. You might also be interested in reading [Unity Server in Docker](broken://pages/1Dq4r2S8x09H7WCiKDyc).

If you need more informations about Docker with Edgegap, please refer to [this](/docs/tools-and-integrations/container/docker) documentation.

#### Dockerfile

```
FROM ubuntu:bionic
MAINTAINER <author_detail>

ARG debian_frontend=noninteractive
ARG docker_version=17.06.0-ce

RUN apt-get update && \
    apt-get install -y libglu1 xvfb libxcursor1 ca-certificates && \
    apt-get clean && \
    update-ca-certificates

EXPOSE 3389/TCP
EXPOSE [GAME PORT]/TCP
EXPOSE [GAME PORT]/UDP

COPY linux_server/ /root/linux_server/
COPY boot.sh /boot.sh

WORKDIR /root/
ENTRYPOINT ["/bin/bash", "/boot.sh"]
```

Take note of the port you use for network communications, referred as the `[GAME PORT]`. By default, the port used is `7777`. You can find this information in the Unity Editor, on the `NetworkManager` game object, in the `Transport` component.

* Copy the above lines and paste them in your Dockerfile, placed inside `[SERVER BUILD]`. Modify the `[GAME PORT]` placeholders to your game port.

Having the `[GAME PORT]` opened on both TCP and UDP allow you to use any transport you prefer in the `NetworkManager` Mirror component. Finally, create a file named `boot.sh` at the root of the `[SERVER BUILD]` folder. It will be executed when starting the image in a container.

* Copy the following two lines, make sure to replace the `[YOUR GAME]` placeholders with the name of the generated file.

```
xvfb-run --auto-servernum --server-args='-screen 0 640X480X24:32' /root/build/[YOUR GAME].x86_64 -batchmode -nographics
```

At this point, you should have the following hierarchy:

> * `[SERVER BUILD] folder` > > - `Dockerfile` > > - `boot.sh` > > - `linux_server` folder > > > - Unity generated files

* Start a command prompt in the `[SERVER BUILD]` folder, and run the following Docker commands:

{% hint style="warning" %}
For ARM CPU (Mac M1, M2, etc.) users, add `--platform linux/amd64`  option to your build command.
{% endhint %}

#### Using Linux

```bash
# build the image
docker build . -t <IMAGE_NAME>:<IMAGE_VERSION>

# login, a prompt will ask the password
docker login -u '<REGISTRY_USERNAME>' <REGISTRY_URL>

# add another tag to your image corresponding to the registry
docker image tag <IMAGE_NAME>:<IMAGE_VERSION> <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_VERSION>

#push the image
docker push <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_VERSION>
```

#### Using cmd

```bash
# build the image
docker build . -t <IMAGE_NAME>:<IMAGE_VERSION>

# login, a prompt will ask the password
docker login -u <REGISTRY_USERNAME> <REGISTRY_URL>

# add another tag to your image corresponding to the registry
docker image tag <IMAGE_NAME>:<IMAGE_VERSION> <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_VERSION>

#push the image
docker push <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_VERSION>
```

#### Using Powershell

```bash
# build the image
docker build . -t <IMAGE_NAME>:<IMAGE_VERSION>

# login, a prompt will ask the password
docker login -u '<REGISTRY_USERNAME>' <REGISTRY_URL>

# add another tag to your image corresponding to the registry
docker image tag <IMAGE_NAME>:<IMAGE_VERSION> <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_VERSION>

#push the image
docker push <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_VERSION>
```

After these commands, you should be able to see your uploaded image on the Edgegap website if you are using the Edgegap Container Registry. See [this doc](/learn/advanced-features/edgegap-container-registry) if you want to use the Edgegap registry. You can also use another private registry.

### Deploying to Edgegap

Navigate to the `Applications & Games` page of the website. Click on the `Create New` button in the top right hand corner to access the application form. Here are the fields and how to fill them properly:

* Application name : Can be any notable name you want to use to easily recognize your application among others.
* Image : Can be any specific image you want to use to easily recognize your application among others.
* Version name : You may want to use a version name to describe the scope of the version you are deploying. Examples may be “demo”, “production”, “v1”, “v2”
* Container :
  * Registry : “\[URL]”, where \[URL] is the value from the credentials you can display on the Container Repository page.
  * Image repository : “\[PROJECT]/\[YOUR GAME]”, where \[PROJECT] and \[YOUR GAME] are the values you used earlier when pushing the docker image.
  * Tag : “\[TAG]”, where \[TAG] is the value you used earlier when pushing the docker image.
  * Tick “Using a private repository”
  * Private registry username : “\[USERNAME]”, where \[USERNAME] is the value from your credentials.
  * Private registry token : “\[TOKEN]”, where \[TOKEN] is the value from your credentials.
  * Requirements : Left as is.
  * Ports :
    * Click the `+ Add port` link to add a new port, and add the following entries :
      * `[GAME PORT]` - `TCP/UDP` - disable Verifications
      * 3389 - TCP - disable Verifications

Once your application has been created, you can press the `Deploy` button to proceed with deploying your game server. Choose the region you want to deploy in, and enter the number of random players you want to generate depending on your game. Check that everything is running smoothly by verifying the following:

* Latest Status should be set to `Ready`.
* In the `Port Mapping` tab, you should be seeing the port you set in the application creation form:

<figure><img src="/files/yQF4wUSyLafsnLxmKawa" alt=""><figcaption></figcaption></figure>

### Add sample HUD in your client application

* Set the `Port` value of the `Transport` component of the `NetworkManager` to the external port defined in the `Port Mapping` tab of your deployment.

In this example, the port was set to `31887`. This primarily depends on the game you are developing and will most likely be set programmatically in the game’s codebase.

* Set the value of `Network Address` of the `Network Manager` to your deployment's `Host`. This URL can be found in the `Deployment Summary` on the dashboard or with the API.

In this example, the address was set to `0ace560706a5.pr.edgegap.net`. Again, this value will most likely be set programmatically during the client’s communication with the master server/API responsible for the matchmaking process.

With the correct information, you should be able to connect to the game server normally and be able to start playing right away.

You now have a Mirror project available to deploy on demand!

With seat-based deployments, it's possible to use Mirror to create a system that automatically removes hanging Edgegap sessions once a player disconnects from the server, using `NetworkManager` callback functions, and a `NetworkBehaviour` script attached to the player prefab that uses a Remote Procedure Call (RPC) function and a Command function.

When the server starts, the `NetworkManager` retrieves the list of `session IDs` linked to its deployment from the Edgegap API and stores it. Afterwards, when a new player connects to the server, a client-side function is initiated via `RPC` that will send the `player's IP address` back to the server with a `command`. With the player's IP, the server checks for a matching IP in each session's data; The server gets the session data using its cached ID with the Edgegap API. If a match is found, the `session ID` is mapped to that player's `NetworkConnectionToClient`.

Since new sessions can be added after the server starts, the list of session IDs is updated and the new sessions are checked if a match can't be found the first time around.

Finally, once a player disconnects from the server, the server uses that player's `NetworkConnectionToClient` to retrieve their associated `session ID`, then uses the Edgegap API to delete that session. This frees a socket in the deployment for a new player to join.

#### PlayerNetworkBehaviour

```cs
public class PlayerNetworkBehaviour : NetworkBehaviour
{
    [ClientRpc]
    public void RpcSendClientIpAddress()
    {
        StartCoroutine(FetchPublicIpAndSendToServer());
    }

    private IEnumerator FetchPublicIpAndSendToServer(" alt=""><figcaption></figcaption></figure>
    {
        UnityWebRequest request = UnityWebRequest.Get("https://api.ipify.org?format=json");
        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.Success)
        {
            string responseText = request.downloadHandler.text;
            string clientIp = JObject.Parse(responseText)["ip"].ToString();
            CmdSendIpAddressToServer(clientIp);
        }
        else
        {
            Debug.LogError("Failed to fetch public IP address.");
        }
    }

    [Command]
    public void CmdSendIpAddressToServer(string clientIp)
    {
        NetworkConnectionToClient conn = connectionToClient;
        if (conn != null)
        {
            CustomNetworkManager.Instance.StoreClientIpAddress(conn, clientIp);
        }
        else
        {
            Debug.LogError("Connection not found.");
        }
    }
}
```

#### CustomNetworkManager

```cs
public class CustomNetworkManager : NetworkManager
{
    private static readonly string edgegapUrl = "https://api.edgegap.com/v1";
    private string apiToken;
    private string deploymentId;
    private Dictionary<NetworkConnectionToClient, string> connectionToSessionIdMap = new Dictionary<NetworkConnectionToClient, string>();
    private Dictionary<NetworkConnectionToClient, bool> mappingInProgress = new Dictionary<NetworkConnectionToClient, bool>();

    private ArrayList currentDeploymentSessions = new();
    private ArrayList newSessions = new();

    public static CustomNetworkManager Instance;

    public override void Awake()
    {
        base.Awake();
        Instance = this;
    }

    public override void OnStartServer()
    {
        base.OnStartServer();
        deploymentId = System.Environment.GetEnvironmentVariable("ARBITRIUM_REQUEST_ID");
        apiToken = System.Environment.GetEnvironmentVariable("API_TOKEN");
        if (string.IsNullOrEmpty(apiToken))
        {
            Debug.LogError("Edgegap API token not found in environment variables.");
        }

        StartCoroutine(GetNewSessionList());
    }

    public override void OnServerAddPlayer(NetworkConnectionToClient conn)
    {
        base.OnServerAddPlayer(conn);
        Debug.Log($"Adding player for connection: {conn.connectionId}");

        // Get the PlayerNetworkBehaviour from the player object
        PlayerNetworkBehaviour playerNetworkBehaviour = conn.identity.GetComponent<PlayerNetworkBehaviour>();
        playerNetworkBehaviour.RpcSendClientIpAddress();
    }

    public void StoreClientIpAddress(NetworkConnectionToClient conn, string clientIp)
    {
        mappingInProgress[conn] = true; // Set mapping in progress to true
        Debug.Log($"Stored IP address for connection {conn.connectionId}: {clientIp}");

        // Start mapping the session to the connection
        StartCoroutine(CheckCachedSessions(conn, clientIp));
    }

    private IEnumerator GetNewSessionList()
    {
        newSessions.Clear();
        string url = $"{edgegapUrl}/status/{deploymentId}";
        UnityWebRequest request = UnityWebRequest.Get(url);
        request.SetRequestHeader("Authorization", apiToken);

        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.Success)
        {
            string responseText = request.downloadHandler.text;
            Debug.Log("Deployment Status Response: " + responseText);

            var json = JObject.Parse(responseText);
            var sessions = json["sessions"] as JArray;

            foreach (var session in sessions)
            {
                string sessionId = session["session_id"]?.ToString();

                if (!currentDeploymentSessions.Contains(sessionId))
                {
                    newSessions.Add(sessionId);
                    currentDeploymentSessions.Add(sessionId);
                }
            }

            Debug.Log("New session list initialized");
        }
    }

    private IEnumerator CheckCachedSessions(NetworkConnectionToClient conn, string clientAddress)
    {
        foreach (string sessionId in currentDeploymentSessions)
        {
            yield return GetSessionAndMap(conn, sessionId, clientAddress);

            if (connectionToSessionIdMap.ContainsKey(conn))
            {
                break;
            }
        }

        if (!connectionToSessionIdMap.ContainsKey(conn))
        {
            Debug.Log("Could not map player with current cached sessions");
            yield return GetNewSessionList();

            foreach (string sessionId in newSessions)
            {
                yield return GetSessionAndMap(conn, sessionId, clientAddress);

                if (connectionToSessionIdMap.ContainsKey(conn))
                {
                    break;
                }
            }
        }
    }

    private IEnumerator GetSessionAndMap(NetworkConnectionToClient conn, string sessionId, string clientAddress)
    {
        Debug.Log($"Fetching session details for session ID: {sessionId}");
        string url = $"{edgegapUrl}/session/{sessionId}";
        UnityWebRequest request = UnityWebRequest.Get(url);
        request.SetRequestHeader("Authorization", apiToken);

        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.Success)
        {
            Debug.Log("Session fetched successfully.");
            string responseText = request.downloadHandler.text;
            Debug.Log("Session Response: " + responseText);

            var session = JObject.Parse(responseText);
            var sessionUsers = session["session_users"] as JArray;

            if (sessionUsers != null && sessionUsers.Count > 0)
            {
                Debug.Log($"Found {sessionUsers.Count} users in session.");
                foreach (var user in sessionUsers)
                {
                    string playerIp = user["ip"]?.ToString();
                    Debug.Log($"Player IP: {playerIp}, Connection IP: {clientAddress}");
                    if (playerIp == clientAddress)
                    {
                        connectionToSessionIdMap[conn] = sessionId;
                        Debug.Log($"Mapped session ID {sessionId} to connection {conn.connectionId} with IP {playerIp}");

                        // Additional confirmation logging
                        if (connectionToSessionIdMap.ContainsKey(conn))
                        {
                            Debug.Log($"Session ID {sessionId} successfully stored for connection {conn.connectionId} with IP {playerIp}");
                        }
                        else
                        {
                            Debug.LogError($"Failed to store session ID {sessionId} for connection {conn.connectionId} with IP {playerIp}");
                        }

                        break;
                    }
                }
            }
            else
            {
                Debug.LogError("No users found in session.");
            }
        }
        else
        {
            Debug.LogError($"Error fetching session: {request.error}");
            Debug.LogError($"Response Code: {request.responseCode}");
            Debug.LogError($"Response Text: {request.downloadHandler.text}");
        }

        mappingInProgress[conn] = false; // Set mapping in progress to false once done
    }

    public override void OnServerDisconnect(NetworkConnectionToClient conn)
    {
        base.OnServerDisconnect(conn);
        Debug.Log($"Server disconnected client: {conn.connectionId}");

        StartCoroutine(HandleDisconnect(conn));
    }

    private IEnumerator HandleDisconnect(NetworkConnectionToClient conn)
    {
        while (mappingInProgress.ContainsKey(conn) && mappingInProgress[conn])
        {
            Debug.Log($"Waiting for session mapping to complete for connection {conn.connectionId}...");
            yield return new WaitForSeconds(0.1f);
        }

        if (connectionToSessionIdMap.TryGetValue(conn, out string sessionId))
        {
            Debug.Log($"Deleting session ID: {sessionId}");
            StartCoroutine(DeleteSession(sessionId));
            connectionToSessionIdMap.Remove(conn);
            mappingInProgress.Remove(conn);
            currentDeploymentSessions.Remove(sessionId);
        }
        else
        {
            Debug.LogWarning("No session ID found for this connection.");
        }
    }

    private IEnumerator DeleteSession(string sessionId)
    {
        Debug.Log($"Sending request to delete session ID: {sessionId}");
        string url = $"{edgegapUrl}/session/{sessionId}";
        UnityWebRequest request = UnityWebRequest.Delete(url);
        request.SetRequestHeader("Authorization", apiToken);

        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.Success)
        {
            Debug.Log("Session deleted successfully.");
        }
        else
        {
            Debug.LogError($"Error deleting session: {request.error}");
            Debug.LogError($"Response Code: {request.responseCode}");
            Debug.LogError($"Response Text: {request.downloadHandler.text}");
        }
    }

}
```

#### Optional features

This script can be expanded upon with optional, independent features that help manage the sessions in specific cases.

For example, a `timeout` can be set to remove `empty seat sessions` after a configurable amount of time `following full server initialization`. This is a useful feature in the case of a player that quits before their matchmaker ticket gets resolved, which would create an empty session once a match is found. If the player never sends their IP address to be mapped to the session ID before the timeout resolves, then that session gets deleted to free up a socket.

Another feature can be to `disconnect inactive players` after a prolonged period of time in order to free up sockets. Once connected to the server, the client needs to `send some minimalistic heartbeat message` to the server `every few seconds`, or otherwise gets disconnected if too many `heartbeats are missed in a row`. Both the amount of time between heartbeats and the maximum number of messages that can be missed in a row can be configured, depending on the game's needs. In this sample, a heartbeat gets sent every few seconds if the tank is moving around the map or shooting.

#### PlayerNetworkBehaviour - Seat Session Management

```cs
    public class PlayerNetworkBehaviour : NetworkBehaviour
    {
        private bool enableInactiveTimeout;
        private float clientTimeSinceLastHeartbeat = 0;
        private float secondsBetweenHeartbeats;

        [ClientRpc]
        public void RpcSendClientIpAddress()
        {
            StartCoroutine(FetchPublicIpAndSendToServer());
            secondsBetweenHeartbeats = CustomNetworkManager.Instance.GetSecondsBetweenHeartbeats();
            enableInactiveTimeout = CustomNetworkManager.Instance.IsEnableInactiveTimeout();
            clientTimeSinceLastHeartbeat = secondsBetweenHeartbeats;
        }

        ...

        private void Update()
        {
            if (isLocalPlayer && enableInactiveTimeout)
            {
                float horizontal = Input.GetAxis("Horizontal");
                float vertical = Input.GetAxis("Vertical");

                if (clientTimeSinceLastHeartbeat >= secondsBetweenHeartbeats && (Input.GetKeyDown(KeyCode.Space) || horizontal != 0 || vertical != 0))
                {
                    CmdSendHeartbeatTimeToServer(NetworkTime.predictedTime);
                    clientTimeSinceLastHeartbeat = 0;
                }

                clientTimeSinceLastHeartbeat += Time.deltaTime;
            }
        }

        [Command]
        public void CmdSendHeartbeatTimeToServer(double time)
        {
            NetworkConnectionToClient conn = connectionToClient;
            if (conn != null)
            {
                CustomNetworkManager.Instance.StoreClientHeartbeatTime(conn, time);
            }
            else
            {
                Debug.LogError("Connection not found.");
            }
        }
    }
```

#### CustomNetworkManager - Seat Session Management

```cs
public class CustomNetworkManager : NetworkManager
{
    ...
    private bool listInProgress = false;

    [Header("Empty Session Timeout")]
    [SerializeField] private bool enableEmptyTimeout;
    [SerializeField] private float deleteAfterSeconds = 15f;

    [Header("Inactive Player Timeout")]
    [SerializeField] private bool enableInactiveTimeout;
    [SerializeField] private int maxMissedHeartbeats = 3;
    [SerializeField] private float secondsBetweenHeartbeats = 3f;
    private Dictionary<NetworkConnectionToClient, double> connectionToLastHeartbeatMap = new();

    ...

    public override void OnStartServer()
    {
        base.OnStartServer();
        deploymentId = Environment.GetEnvironmentVariable("ARBITRIUM_REQUEST_ID");
        apiToken = Environment.GetEnvironmentVariable("API_TOKEN");
        if (string.IsNullOrEmpty(apiToken))
        {
            Debug.LogError("Edgegap API token not found in environment variables.");
        }
        else
        {
            Debug.Log("Edgegap API token found: " + apiToken);
        }

        StartCoroutine(InitiateSessionManage());
    }

    private IEnumerator InitiateSessionManage()
    {
        yield return new WaitForSeconds(0.5f);

        StartCoroutine(GetNewSessionList());

        if (enableEmptyTimeout)
        {
            StartCoroutine(StartServerInitSessionsTimeout());
        }
    }

    ...

    private IEnumerator GetNewSessionList()
    {
        listInProgress = true;
        newSessions.Clear();
        string url = $"{edgegapUrl}/status/{deploymentId}";
        UnityWebRequest request = UnityWebRequest.Get(url);
        request.SetRequestHeader("Authorization", apiToken);

        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.Success)
        {
            string responseText = request.downloadHandler.text;
            Debug.Log("Deployment Status Response: " + responseText);

            var json = JObject.Parse(responseText);
            var sessions = json["sessions"] as JArray;

            foreach (var session in sessions)
            {
                string sessionId = session["session_id"]?.ToString();

                if (!currentDeploymentSessions.Contains(sessionId))
                {
                    newSessions.Add(sessionId);
                    currentDeploymentSessions.Add(sessionId);
                }
            }

            Debug.Log("New session list initialized");
        }
        else
        {
            Debug.LogError($"Error fetching session list: {request.error}");
            Debug.LogError($"Response Code: {request.responseCode}");
            Debug.LogError($"Response Text: {request.downloadHandler.text}");
        }

        listInProgress = false;
    }

    ...

    private IEnumerator GetSessionAndMap(NetworkConnectionToClient conn, string sessionId, string clientAddress)
    {
        Debug.Log($"Fetching session details for session ID: {sessionId}");
        string url = $"{edgegapUrl}/session/{sessionId}";
        UnityWebRequest request = UnityWebRequest.Get(url);
        request.SetRequestHeader("Authorization", apiToken);

        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.Success)
        {
            Debug.Log("Session fetched successfully.");
            string responseText = request.downloadHandler.text;
            Debug.Log("Session Response: " + responseText);

            var session = JObject.Parse(responseText);
            var sessionUsers = session["session_users"] as JArray;

            if (sessionUsers != null && sessionUsers.Count > 0)
            {
                Debug.Log($"Found {sessionUsers.Count} users in session.");
                foreach (var user in sessionUsers)
                {
                    string playerIp = user["ip"]?.ToString();
                    Debug.Log($"Player IP: {playerIp}, Connection IP: {clientAddress}");
                    if (playerIp == clientAddress)
                    {
                        connectionToSessionIdMap[conn] = sessionId;
                        Debug.Log($"Mapped session ID {sessionId} to connection {conn.connectionId} with IP {playerIp}");

                        if (enableInactiveTimeout)
                        {
                            connectionToLastHeartbeatMap[conn] = conn.lastMessageTime;
                            StartCoroutine(CheckClientHeartbeat(conn));
                        }

                        // Additional confirmation logging
                        if (connectionToSessionIdMap.ContainsKey(conn))
                        {
                            Debug.Log($"Session ID {sessionId} successfully stored for connection {conn.connectionId} with IP {playerIp}");
                        }
                        else
                        {
                            Debug.LogError($"Failed to store session ID {sessionId} for connection {conn.connectionId} with IP {playerIp}");
                        }

                        break;
                    }
                }
            }
            else
            {
                Debug.LogError("No users found in session.");
            }
        }
        else
        {
            Debug.LogError($"Error fetching session: {request.error}");
            Debug.LogError($"Response Code: {request.responseCode}");
            Debug.LogError($"Response Text: {request.downloadHandler.text}");
        }

        mappingInProgress[conn] = false; // Set mapping in progress to false once done
    }

    ...

    #region EmptySessionTimeout

    private IEnumerator StartServerInitSessionsTimeout()
    {
        while (listInProgress)
        {
            Debug.Log($"Waiting to retrieve session list...");
            yield return new WaitForSeconds(0.1f);
        }

        foreach (string sessionId in currentDeploymentSessions)
        {
            StartCoroutine(WaitForConnectionTimeout(sessionId));
        }
    }

    private IEnumerator WaitForConnectionTimeout(string sessionID)
    {
        Debug.Log($"Checking for empty timeout on session {sessionID}");
        bool delete = false;
        DateTime timeout = DateTime.Now.AddSeconds(deleteAfterSeconds);

        while (!connectionToSessionIdMap.ContainsValue(sessionID))
        {
            yield return new WaitForSeconds(0.1f);
            if (DateTime.Now >= timeout)
            {
                delete = true;
                break;
            }
        }

        if (delete && !connectionToSessionIdMap.ContainsValue(sessionID))
        {
            Debug.Log($"No connection initiated by client after {deleteAfterSeconds}s, deleting session {sessionID}");
            StartCoroutine(DeleteSession(sessionID));
            currentDeploymentSessions.Remove(sessionID);
            newSessions.Remove(sessionID);
        }
    }

    #endregion

    #region InactivePlayerTimeout

    private IEnumerator CheckClientHeartbeat(NetworkConnectionToClient conn)
    {
        int counter = 0;

        while (counter < maxMissedHeartbeats && connectionToSessionIdMap.ContainsKey(conn))
        {
            double lastHeartbeatDiff = NetworkTime.localTime - connectionToLastHeartbeatMap[conn];

            if (lastHeartbeatDiff >= secondsBetweenHeartbeats)
            {
                counter += 1;

                Debug.Log($"Connection {conn.connectionId} has missed {counter} heartbeat(s) in a row");
            }
            else
            {
                counter = 0;
                Debug.Log($"Reset heartbeat counter for connection {conn.connectionId}");
            }

            yield return new WaitForSeconds(secondsBetweenHeartbeats);
        }

        if (counter >= maxMissedHeartbeats && conn.isReady)
        {
            Debug.Log($"Connection {conn.connectionId} inactive for too long, disconnecting");
            conn.Disconnect();
            connectionToLastHeartbeatMap.Remove(conn);
        }
    }

    public void StoreClientHeartbeatTime(NetworkConnectionToClient conn, double time)
    {
        connectionToLastHeartbeatMap[conn] = time;
    }

    public float GetSecondsBetweenHeartbeats() => secondsBetweenHeartbeats;

    public bool IsEnableInactiveTimeout() => enableInactiveTimeout;

    #endregion
}
```


# Fishnet

Explore how to host Unity projects on Edgegap using techniques from the FishNet HashGrid Sample.

{% embed url="<https://youtu.be/9Tnlgklg_qY>" %}

## ✔️ Preparation

Before we start, you will need:

* Unity 6 - [download using Unity Hub](https://unity.com/releases/unity-6),
* FishNet Unity package - [download from Unity Asset Store](https://assetstore.unity.com/packages/tools/network/fishnet-networking-evolved-207815),
  * this package includes the HashGrid sample.

Create a new project and import the FishNet package above.

## ⚡ Deploy and Connect

### 1. Deploy a Server on Edgegap

☑️ To get started, you'll need to [create a free account with Edgegap](https://app.edgegap.com/auth/register). No credit card required.

☑️ [Create a new app version for your application](https://app.edgegap.com/application-management/applications/fishnet-hashgrid-sample/versions/create), choose FishNet HashGrid Sample.

☑️ [Deploy a server with your FishNet HashGrid Sample app version](https://app.edgegap.com/deployment-management/deployments/list).

☑️ [Open your new deployment's details](https://app.edgegap.com/deployment-management/deployments/list).

☑️ Find your deployment's unique, one-time connection details:

* **Host URL** in format `780aa4260e83.pr.edgegap.net` ,
* **External Port** in format `30854`  (5 digits).

✅ You may now proceed to the next step.

### 2. Connect from Editor

☑️ Open your new project in Unity.

☑️ Open scene in folder FishNet / Demos / HashGrid / Scenes / HashGrid\_Demo.unity.

☑️ Select NetworkManager object in the scene, and modify Tugboat component in Inspector:

* set Client / Client Address to **Host URL** from previous step,
* set Server / Port to **External Port** from previous step.

☑️ Expand NetworkManager child game objects and select NetworkHudCanvas, then set `Auto Start Type`  to `Disabled`  to prevent your editor from starting in client-host mode.

☑️ Press the ▶️ Play button to start your game client.

☑️ Press Client button to connect to your server.

☑️ Connect a second virtual Player with [Multiplayer Play Mode](https://docs-multiplayer.unity3d.com/mppm/current/about/) or [ParrelSync](https://github.com/VeriorPies/ParrelSync).

🙌 Congratulations on your first Deployment on Edgegap!

## ✏️ Customize Server Build

To ensure your server build starts properly:

* edit your build profile (**Edit Build Settings** in Edgegap plugin) and add your scene,
* set `Auto Start Type`  to `Server`  in `NetworkHudCanvases`  component (child of `NetworkManager`),
* enable `Reuse Server Address` in your `Tugboat`  component to prevent error `AddressAlreadyInUse`.

{% hint style="success" %}
See [Unity](/unity) for Unity to **learn how to build and customize servers**.
{% endhint %}


# Photon Bolt

This guide will help you create a headless server on Edgegap for a Unity project using Photon Bolt as its networking solution.

In this guide, we will be using a custom demonstration project as an example.

### Preparing the Unity project

### Clone the Unity project

The first step is to clone the Edgegap demo game project from git. You can do so using this command in your command prompt:

The command will create a folder containing the source Unity project, called `demo-game`.

Once the process is over, you can open Unity Hub and add the project by pressing the `Add` button in the Projects tab then finding the project's folder on your computer:

To open the project, you will need to have the correct version of Unity installed. In our case, we will use Unity `2020.3.3`. You can find it by visiting Unity's download archive: <https://unity3d.com/get-unity/download/archive>.

Once the installation is complete, you can now select the new Unity version through the drop-down menu adjacent to the project name in Unity Hub:

You can now open the Unity project to proceed to the next steps.

### Setting up the game

### Setting up Photon Bolt

#### Add Photon App ID and compile Bolt

For the game to communicate with Photon's services, you will need to add an App to your Photon dashboard. If you do not have an account with Photon yet, you can easily create one on their [website](https://id.photonengine.com/Account/).

Once signed in, you can create an app and give it a name. Every app has its unique `App ID`. You will need to copy and paste this ID in the `Bolt Settings` window within the Unity project:

<figure><img src="/files/DkHqfiUb1nKc0mk4jZz2" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/aFuAsPzWT3KvAsxkvdpd" alt=""><figcaption></figcaption></figure>

Once you enter the ID, you can close the Bolt Settings window. In the same `Bolt` menu as before, you will have to execute two actions: `Update Prefab Database` and `Compile Assembly`, respectively.

<figure><img src="/files/NrlalTisqKnNR8amF6da" alt=""><figcaption></figcaption></figure>

At this point, you should be able to launch the game's `StartScene` scene without any errors or warnings in the debug console. However, you cannot start playing on a server just yet.

That concludes the generic changes needed for your Photon Bolt to work. These changes will work both for the clients and servers of your game.

### Preparing a headless server

For your app to work with containers efficiently, you will need to make a few changes to your game to create a "headless server" build. For most projects, this will only require a few straightforward steps.

#### Requirements for the Unity project

First, it is essential to note that Photon Bolt's headless server mode requires loading a scene holding a script called `HeadlessServerManager`.

The generic, unaltered script in question (taken from Bolt's documentation) looks like this:

```cs
using System;
using Bolt.Matchmaking;
using Bolt.Photon;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace Bolt.Samples.HeadlessServer
{
    public class HeadlessServerManager : Bolt.GlobalEventListener
    {
        public string Map = "";
        public string GameType = "";
        public string RoomID = "";

        public override void BoltStartBegin(" alt=""><figcaption></figcaption></figure>
        {
            // Register any Protocol Token that are you using
            BoltNetwork.RegisterTokenClass<PhotonRoomProperties>();
        }

        public override void BoltStartDone()
        {
            if (BoltNetwork.IsServer)
            {
                // Create some room custom properties
                PhotonRoomProperties roomProperties = new PhotonRoomProperties();

                roomProperties.AddRoomProperty("t", GameType); // ex: game type
                roomProperties.AddRoomProperty("m", Map); // ex: map id

                roomProperties.IsOpen = true;
                roomProperties.IsVisible = true;

                // If RoomID was not set, create a random one
                if (RoomID.Length == 0)
                {
                    RoomID = Guid.NewGuid().ToString();
                }

                // Create the Photon Room
                BoltMatchmaking.CreateSession(
                    sessionID: RoomID,
                    token: roomProperties,
                    sceneToLoad: Map
                );
            }
        }

        // Use this for initialization
        void Start()
        {
            // Get custom arguments from command line
            Map = GetArg("-m", "-map") ?? Map;
            GameType = GetArg("-t", "-gameType") ?? GameType; // ex: get game type from command line
            RoomID = GetArg("-r", "-room") ?? RoomID;

            // Validate the requested Level
            var validMap = false;

            foreach (string value in BoltScenes.AllScenes)
            {
                if (SceneManager.GetActiveScene().name != value)
                {
                    if (Map == value)
                    {
                        validMap = true;
                        break;
                    }
                }
            }

            if (!validMap)
            {
                BoltLog.Error("Invalid configuration: please verify level name");
                Application.Quit();
            }

            // Start the Server
            BoltLauncher.StartServer();
            DontDestroyOnLoad(this);
        }

        /// <summary>
        /// Utility function to detect if the game instance was started in headless mode.
        /// </summary>
        /// <returns><c>true</c>, if headless mode was ised, <c>false</c> otherwise.</returns>
        public static bool IsHeadlessMode()
        {
            return Environment.CommandLine.Contains("-batchmode") && Environment.CommandLine.Contains("-nographics");
        }

        static string GetArg(params string[] names)
        {
            var args = Environment.GetCommandLineArgs();
            for (int i = 0; i < args.Length; i++)
            {
                foreach (var name in names)
                {
                    if (args[i] == name && args.Length > i + 1)
                    {
                        return args[i + 1];
                    }
                }
            }

            return null;
        }
    }
}
```

Usually, it will be held by an empty GameObject in an empty scene (which will only be used for headless server builds).

The script has been slightly altered for this demo to fit the project's structure, and it is currently placed in an empty GameObject in a scene located in `Assets/Scenes/BoltHeadlessServer`.

#### Adding headless server checks to your game

The above script contains a static utility method called `IsHeadlessMode`, to be used to check if the running game is headless or not. If you are updating an existing project, you might want to add this check to remove or add features for the headless server, like preventing the instantiation of a player character.

#### Build settings

In the `Buid Settings` of the project, add the `BoltHeadlessServer` scene at the very top, so it is the first to be loaded by the server.

Next, choose `Linux` as the target platform and check `Server Build`.

<figure><img src="/files/7poVO2K3rYntu4cve1Mo" alt=""><figcaption></figcaption></figure>

Now you can build your game under Linux with the above settings.

For **client builds**, do not forget to uncheck the `BoltHeadlessServer` scene and the `IsServer` option so your game functions properly.

For every step concerning the making of a headless server for Photon Bolt, more details are available in the [official documentation](https://doc.photonengine.com/en-us/bolt/current/demos-and-tutorials/headless-server).

### Create a container for the server

If you aren't familiar with Docker or containers, you can learn more by checking out [What is Docker?](/docs/tools-and-integrations/container/docker)

To create a container from a Unity project, please refer to [Unity on Docker](broken://pages/1Dq4r2S8x09H7WCiKDyc).

### Add your app to Edgegap

You can now add your container to Edgegap so it can be hosted everywhere your players are.

If you don't know how to add an app to Edgegap, please refer to the [integration tutorial](/learn/orchestration/application-and-versions).

However, you will need to make sure the name of your app is `demo-game` and that it has a version called `v1`. Else, you will need to update these values in the script located in `Assets/Scripts/StaginController.cs`.

Also, within the `Assets/Scripts/StaginController.cs` script, you will find a constant variable called `API_TOKEN`. You will need to create a token from your Edgegap account and paste it as a string into this variable. That token will be used to authorize the API requests sent to Edgegap.

Here is the script in question, with the token from Edgegap in blue and the app parameters in red:

<figure><img src="/files/zW5UHKd5jVyzO7ta9jtp" alt=""><figcaption></figcaption></figure>

## Playing the demo game

Finally, you can start a demo game client build to play it. You can either build a client version of the game and launch or play it directly from the Unity interface. Just make sure to start it on the `StartScene` scene.

Once launched, an interface will show up. It contains a text field at the top, two large buttons and a label at the bottom left of the screen showing Bolt's current state.

<figure><img src="/files/055r1a3sq3RhJFsbzPOp" alt=""><figcaption></figcaption></figure>

You can copy your API Token from Edgegap into the text field at the top. Once your Token is pasted, you can now press the `Request Server` button to send an API call to Edgegap to start a server.

A few seconds later, you should see the label at the bottom of the interface showing the number of available servers. You will now press the `Connect to a random server` button to join a game and start playing.


# Mirror WebGL

This guide will help you use [Mirror](https://mirror-networking.com/)'s Websocket Transport and create a headless server on Edgegap for a Unity project.

This guide will use the open-source sample project `Tanks`, available in the Mirror sample under `Assets/Mirror/Examples/Tanks`.

You can find the final version of this sample on our [GitHub](https://github.com/edgegap/mirror-webgl)

### Switch the transport

We first need to make some changes to the base scene before we're ready to build the game server.

* Open `Scene.unity` located under `Assets/Mirror/Examples/Tanks/Scenes`;
* In the `NetworkManager` gameObject, remove the `KcpTransport` script and replace it with the `SimpleWebTransport` located under `Assets/Mirror/Transports/SimpleWeb`, make sure to also update the Transport field of the `NetworkManager` script component with this new transport. Make sure that the `Auto Start Server Build` option is selected as well.
* Depending on your version of Mirror, you might need to change/update the `NetworkManagerHUD` to make it work with different transports than KCP, as well as to let you input the port value in the HUD before joining a server.

Take note of the port used for network communications, referred as the `[GAME PORT]`. In this case, the port used is `7778`.

### Build the game server & Containerizing

{% hint style="info" %}
To facilitate the containerizing and deployment process, it's possible to use the latest version of the Edgegap Unity Plugin on our [GitHub](https://github.com/edgegap/edgegap-unity-plugin) to automate the process. For more information on how to use this plugin, you can check our [documentation](broken://pages/oU74qYCFcdfcNBw4Fkn9).

If you want instead, you may also follow these step-by-step instructions.
{% endhint %}

Once ready with your game, head to the `Build` screen of the Unity Editor, under `File -> Build Settings` in the top menus. Make sure to select the right presets depending on your version of Unity.

* Prior to version 2021.2:
  * Set `Target Platform` to `Linux`;
  * Set `Architecture` to `x86_64`;
  * Check the `Server Build` option.
* Otherwise:
  * Set `Platform` to `Dedicated Server`;
  * Set `Target Platform` to `Linux`.

Then press build and select a new empty folder named `linux_server` as the file destination. Transfer the `linux_server` folder to a second empty folder, which will be refered as the `[SERVER BUILD]` folder in this document. Add the following `Dockerfile` and `boot.sh` file to the `[SERVER BUILD]` folder:

#### Dockerfile

```
FROM ubuntu:bionic
MAINTAINER <author_detail>

ARG DEBIAN_FRONTEND=noninteractive
ARG docker_version=17.06.0-ce

RUN
    apt-get update && \
    apt-get install -y libglu1 xvfb libxcursor1 ca-certificates && \
    update-ca-certificates && \
    apt-get clean

EXPOSE 7778/TCP

COPY linux_server/  /root/linux_server/
COPY boot.sh        /boot.sh

WORKDIR /root/
ENTRYPOINT ["/bin/bash", "/boot.sh"]
```

#### boot.sh

```
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24:32' /root/linux_server/[YOUR GAME].x86_64 -batchmode -nographics
```

Make sure to replace the `[YOUR GAME]` placeholders with the name of the generated file

Then, start a command prompt in the `[SERVER BUILD]` folder; Run the following Docker commands to create an image of your build and push it to a private registry:

{% hint style="warning" %}
For ARM CPU (Mac M1, M2, etc.) users, add `--platform linux/amd64`  option to your build command.
{% endhint %}

#### Using Linux

```bash
# build the image
docker build . -t <IMAGE_NAME>:<IMAGE_TAG>

# login, a prompt will ask the password
docker login -u '<REGISTRY_USERNAME>' <REGISTRY_URL>

# add another tag to your image corresponding to the registry
docker image tag <IMAGE_NAME>:<IMAGE_TAG> <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>

#push the image
docker push <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>
```

#### Using cmd

```bash
# build the image
docker build . -t <IMAGE_NAME>:<IMAGE_TAG>

# login, a prompt will ask the password
docker login -u <REGISTRY_USERNAME> <REGISTRY_URL>

# add another tag to your image corresponding to the registry
docker image tag <IMAGE_NAME>:<IMAGE_TAG> <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>

#push the image
docker push <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>
```

#### Using Powershell

```bash
# build the image
docker build . -t <IMAGE_NAME>:<IMAGE_TAG>

# login, a prompt will ask the password
docker login -u '<REGISTRY_USERNAME>' <REGISTRY_URL>

# add another tag to your image corresponding to the registry
docker image tag <IMAGE_NAME>:<IMAGE_TAG> <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>

#push the image
docker push <REGISTRY_URL>/<PROJECT_NAME>/<IMAGE_NAME>:<IMAGE_TAG>
```

### Deploying server to Edgegap

After logging in on the Edgegap Dashboard, navigate to the `Applications & Games` page. Click on the `Create New` button in the top right hand corner to access the application form. Here are the fields and how to fill them properly:

* Application name : Can be any notable name you want to use to easily recognize your application among others.
* Image : Can be any specific image you want to use to easily recognize your application among others.
* Version name : You may want to use a version name to describe the scope of the version you are deploying. Examples may be “demo”, “production”, “v1”, “v2”
* Container :
  * Registry : “\[URL]”, where \[URL] is the value from the credentials you can display on the Container Repository page.
  * Image repository : “\[PROJECT]/\[YOUR GAME]”, where \[PROJECT] and \[YOUR GAME] are the values you used earlier when pushing the docker image.
  * Tag : “\[TAG]”, where \[TAG] is the value you used earlier when pushing the docker image.
  * Tick “Using a private repository”
  * Private registry username : “\[USERNAME]”, where \[USERNAME] is the value from your credentials.
  * Private registry token : “\[TOKEN]”, where \[TOKEN] is the value from your credentials.
  * Requirements : Left as is.
  * Ports : Click the `+ Add port` link to add a new port, and add the following entries :
    * `7778` - WS - enable TLS Upgrade (Beta)

{% hint style="warning" %}
If you use the WSS option, it is important to enable the `TLS Upgrade` option. Otherwise, you will encounter errors similar to this one in your container logs on the Edgegap dashboard.

```cmd
First bytes from client was not 'GET' for handshake, instead was 16-03-01
```

{% endhint %}

<figure><img src="/files/r94xwUW21CODUJhIjjDU" alt=""><figcaption></figcaption></figure>

Once your application has been created, you can press the `Deploy` button to proceed with deploying your game server. Once the latest status of you deployment is set to `Ready`, you will be able to connect to the server with a client version of the game. Take note of the `Host` url and, in the Port Mapping tab of your deployment, the `external port` that's publicly available.

### Testing the client

#### In the editor

Back in the Unity editor in the tank scene, select the `NetworkManager` gameObject and change the following settings:

* In the `Network Manager` component:
  * Set the `Network Address` to the the server deployment's `Host` url;
  * Uncheck the `Auto Start Server Build` option.
* In the `Simple Web Transport` component:
  * Set the `Port` value to the Edgegap deployment's `external port` e.g.: `32821`;
  * Make sure the `Client Use WSS` option is enabled.

<figure><img src="/files/HbebN0pMO2rcGGgidoOy" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Tt9OGpQCAc9Y7qyBoQjY" alt=""><figcaption></figcaption></figure>

Once this is done, click `Play` in the editor, then click the `Client` button; You'll get connected to the server and be able to play the game after a short moment.

#### On Itch.io

To put your game client on Itch, you will need to make a client build; You will need to install the `WebGL Build Support` module for your version of Unity to do that. Once that's done, head back to the `Build` screen of the Unity Editor, under `File -> Build Settings`, and select the following options:

* Set `Platform` to `WebGL`;
* Open up the `Player Settings`. Under `Player -> Publishing Settings`, set the `Compression Format` to `Gzip` and make sure the `Decompression Fallback` option is selected. Save these settings, and close the window.

<figure><img src="/files/4PAaWUOP2mubE2zrKV2Y" alt=""><figcaption></figcaption></figure>

Then press build and select a new empty folder named `build` as the file destination. Transfer the `build` folder to a second empty folder, which will be refered as the `[CLIENT BUILD]` folder in this document.

Once your game has finished building, compress the files of this `build` folder into a zip folder, making sure that they are at the root of it. Then you simply need to upload the zip folder to your Itch project, and make sure to select the option for the file to play in the browser. Once you launch the game, you just need to make sure that the network address and port values are set correctly in the HUD, then click the `Client` button to play.

#### Hosting the client on Edgegap

It's even possible to host your game client on Edgegap! To do so, add the following `Dockerfile` and `nginx.conf` file to the `[CLIENT BUILD]` folder:

**Dockerfile**

```
FROM nginx:alpine
MAINTAINER <author_detail>

COPY build/ /usr/share/nginx/html
copy nginx.conf /etc/nginx/
```

**nginx.conf**

```
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
worker_connections  1024;
}


http {
include       /etc/nginx/mime.types;
default_type  application/octet-stream;

log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log  /var/log/nginx/access.log  main;

sendfile        on;
#tcp_nopush     on;

keepalive_timeout  65;

#gzip  on;

include /etc/nginx/conf.d/*.conf;

server {
# Add the following config within http server configuration
# ...

    # On-disk Brotli-precompressed data files should be served with compression enabled:
    location ~ .+\.(data|symbols\.json)\.br$ {
        # Because this file is already pre-compressed on disk, disable the on-demand compression on it.
        # Otherwise nginx would attempt double compression.
        gzip off;
        add_header Content-Encoding br;
        default_type application/octet-stream;
    }

    # On-disk Brotli-precompressed JavaScript code files:
    location ~ .+\.js\.br$ {
        gzip off; # Do not attempt dynamic gzip compression on an already compressed file
        add_header Content-Encoding br;
        default_type application/javascript;
    }

    # On-disk Brotli-precompressed WebAssembly files:
    location ~ .+\.wasm\.br$ {
        gzip off; # Do not attempt dynamic gzip compression on an already compressed file
        add_header Content-Encoding br;
        # Enable streaming WebAssembly compilation by specifying the correct MIME type for
        # Wasm files.
        default_type application/wasm;
    }

    # On-disk gzip-precompressed data files should be served with compression enabled:
    location ~ .+\.(data|symbols\.json)\.gz$ {
        gzip off; # Do not attempt dynamic gzip compression on an already compressed file
        add_header Content-Encoding gzip;
        default_type application/octet-stream;
    }

    # On-disk gzip-precompressed JavaScript code files:
    location ~ .+\.js\.gz$ {
        gzip off; # Do not attempt dynamic gzip compression on an already compressed file
        add_header Content-Encoding gzip;
        default_type application/javascript;
    }

    # On-disk gzip-precompressed WebAssembly files:
    location ~ .+\.wasm\.gz$ {
        gzip off; # Do not attempt dynamic gzip compression on an already compressed file
        add_header Content-Encoding gzip;
        # Enable streaming WebAssembly compilation by specifying the correct MIME type for
        # Wasm files.
        default_type application/wasm;
    }
}
}
```

Proceed with the [same Docker commands as before](#bootsh) to build and push an image of your game client to a private repository, but from a command window opened in the `[CLIENT BUILD]` folder. Make sure to use a different image name than the one for your server.

Then, create a new application for your client on the Edgegap dashboard with the following settings:

* Application name : Can be any notable name you want to use to easily recognize your application among others.
* Image : Can be any specific image you want to use to easily recognize your application among others.
* Version name : You may want to use a version name to describe the scope of the version you are deploying. Examples may be “demo”, “production”, “v1”, “v2”
* Container :
  * Registry : “\[URL]”, where \[URL] is the value from the credentials you can display on the Container Repository page.
  * Image repository : “\[PROJECT]/\[YOUR GAME]”, where \[PROJECT] and \[YOUR GAME] are the values you used earlier when pushing the docker image.
  * Tag : “\[TAG]”, where \[TAG] is the value you used earlier when pushing the docker image.
  * Tick “Using a private repository”
  * Private registry username : “\[USERNAME]”, where \[USERNAME] is the value from your credentials.
  * Private registry token : “\[TOKEN]”, where \[TOKEN] is the value from your credentials.
  * Requirements : Left as is.
  * Ports : Click the `+ Add port` link to add a new port, and add the following entries :
    * `80` - HTTPS

<figure><img src="/files/tJvJcLyISyieCUaG3BdC" alt=""><figcaption></figcaption></figure>

Once your application has been created, you can press the `Deploy` button to proceed with deploying your game client. With both the server and client deployments set to `Ready`, open the game client's `Host` url at the specified `external port` in your browser, and you'll be able to play the game after setting the correct values in the game's HUD!

<figure><img src="/files/LCFtrUUZXsCMPZjIuAS3" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
If you need more information about the Mirror integration and websocket, you can refer to their [documentation](https://mirror-networking.gitbook.io/docs/manual/transports/websockets-transport).
{% endhint %}


# Fishnet WebGL

This guide will help you use [FishNet](https://github.com/FirstGearGames/FishNet)'s Websocket Transport, [Bayou](https://fish-networking.gitbook.io/docs/manual/components/transports/bayou), and create a headless server on Edgegap for a Unity project.

You can find the final sample project on our [GitHub](https://github.com/edgegap/netcode-sample-unity-fishnet), under the `Fishnet_v4.1.6R_WebGL` folder. This guide should work with any version of Fishnet, so long as you use the appropriate version of Bayou for it.

{% hint style="info" %}
Since FishNet version 4.1.6R comes with the Edgegap plugin included, we will use it to automatically build and containerize our game server, even if it is possible to do so manually using a Dockerfile.

For more information on the plugin, you can read our documentation [here](broken://pages/oU74qYCFcdfcNBw4Fkn9).

You will need to have Docker running for it to work properly.
{% endhint %}

### Unity requirements

This project is tested on Unity version `2021.3.16f1`. You will need the following modules to be installed with it:

* WebGL Build Support;
* Linux Dedicated Server Build Support;
* Linux Build Support (IL2CPP).

### Switch the transport

Download the Unity package for Bayou on [this GitHub](https://github.com/FirstGearGames/Bayou/releases). Import the package in the game project with the toolbar under `Asset/Import Package/Custom Package...`.

Open the `MainMenu` scene located under `Assets/SpaceEdge/Scenes`. In the `NetworkManager` gameObject, remove the `Tugboat` transport component. Replace it with the `Bayou` transport, located under `Asstes/FishNet/Plugins/Bayou`. Take note of the port value that's set, in this case `7770`.

Make sure to enable the `Start On Headless` option for the server build.

<figure><img src="/files/G6ZaWoVyzhhmTJoUiqlE" alt=""><figcaption></figcaption></figure>

### Build the game server & Containerizing

Open the Edgegap plugin with the `Edgegap/Edgegap Hosting` toolbar menu. Verify your `Edgegap API Token` and either create or load an application for the game. Make sure the `port` value matches that of the Bayou transport. Select the `WS` protocol, then enter a `New Version Tag`.

Once this is properly set up, click on `Build and Push`, which will automatically containerize your game server and create a new application version on the Edgegap dashboard after a short waiting period.

### Testing the client

#### In the editor

Back in the project, select the `MatchmakingSystem` gameObject and change the following fields:

<figure><img src="/files/v2v5mSwIr9IA90GidoJ3" alt=""><figcaption></figcaption></figure>

* AppName: Set it to the “Application name” you used with the Edgegap plugin.
* AppVersion: Set it to the “Version name” you used with the Edgegap plugin.
* AuthHeaderValue: Set it to your Edgegap API token that you used with the Edgegap plugin.

{% hint style="info" %}
Remove the `token` word from the API token because we are setting this as the Authentication Header schema during the `Awake()` method of the `MatchmakingSystem`.
{% endhint %}

Open the `MatchmakingSystem` script, located under `Assets/SpaceEdge/Scripts/Systems`, where you may also update those same values. Search for `"UDP_PORT"` and replace it with `"Game Port"`.

{% hint style="info" %}
Previous versions of this sample would manually create the application via the Edgegap dashboard, and the name `UDP_PORT` would be used for the port used by the server.

The Edgegap plugin automatically names the port `Game Port` instead, which is why we must make sure to update the script. The game will not work otherwise.
{% endhint %}

<figure><img src="/files/oTlHJIPdrCPgvxAuzrhJ" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/eeVX6KsqWtOiHspaZrOZ" alt=""><figcaption></figcaption></figure>

Make sure that you uncheck the `Start On Headless` option in the `NetworkManager` gameObject as well.

Once this is done, click `Play` in the editor, then enter a name in the text box and click on `Start Game`. The game will check for any available server, or automatically have one get deployed if none exist, before connecting to it.

{% hint style="warning" %}
If there is a `ConnectionFailed` error on the client side, it may be because the `Start On Headless` option was not enabled in the `server build`, which means the deployment is not in a state to accept connections since the server never properly started. Make sure the option is enabled before trying again by creating a new container image and app version.

It may also be because app version's `port name` is not the same as in the `MatchmakingSystem` script. Make sure that both have the same name, in this case `"Game Port"`, before trying again.
{% endhint %}

#### On Itch.io

Once you've confirmed the game client works in the editor, it should also work in Itch.io. To upload your game there, you will need to make a client build in the `Build` screen of the Unity Editor, under `File -> Build Settings`. Select the following options:

* Set `Platform` to `WebGL`;
* Open up the `Player Settings`. Under `Player -> Publishing Settings`, set the `Compression Format` to `Gzip` and make sure the `Decompression Fallback` option is selected. Save these settings, and close the window.

<figure><img src="/files/AnshSJF1Yp7PFmkLE1VG" alt=""><figcaption></figcaption></figure>

Then press build and select a new empty folder named `build` as the file destination. Compress the files of this `build` folder into a zip folder, making sure that they are at the root of it. Then you simply need to upload the zip folder to your Itch project, and make sure to select the option for the file to play in the browser. The game should then work the same way as in the editor.


# NuxtJS

### Geting Started

Edgegap offers you a very intuitive platform to deploy a serverless web application wherever you need it. For this tutorial, we will go over the steps to deploy a Nuxt app. Before checking for anything else, make sure you have yarn or another packet management software like npm or npx. For this tutorial, we will stick to yarn. You must have it running in your application directory, which can be, at the beginning just an empty folder.

Open your terminal at the location of your folder, and run the following command:

```
C:\Users\JohnDoe\Documents\demo-nuxt-edgegap> yarn
```

Once it installs all the dependencies, make sure it installed properly by running this command:

```
C:\Users\JohnDoe\Documents\demo-nuxt-edgegap> yarn dev
```

Yarn dev should return the version of yarn installed if everything went smoothly. With this out of the way, we can now look at the remaining requirements to successfully complete this tutorial.

### Requirements

### Containerize your application

Containerizing most applications is straightforward. You must create a Dockerfile and then run that file on Docker to create the image. Visual Studio Code is a good platform to create your Dockerfile since, by default, it does not put an extension to every new file that we create. That is exactly what we need. Simply create a new file and name it Dockerfile. You will see the little docker logo appear next to it.

Now, you can copy and paste this script into it to get the main dependencies. Keep in mind that this will create an image a little over 1GB in size. For the purpose of this tutorial, this is fine. But for an application in production, you will have to optimize it:

```dockerfile
FROM node:lts as builder

WORKDIR /app

COPY . .

RUN yarn install \
  --prefer-offline \
  --frozen-lockfile \
  --non-interactive \
  --production=false

RUN yarn build

RUN rm -rf node_modules && \
  NODE_ENV=production yarn install \
  --prefer-offline \
  --pure-lockfile \
  --non-interactive \
  --production=true

FROM node:lts

WORKDIR /app

COPY --from=builder /app  .

ENV HOST 0.0.0.0
EXPOSE 80

CMD [ "yarn", "start" ]
```

After saving your Dockerfile, it is time to create the image. You can easily do so by running the following command:

{% hint style="warning" %}
For ARM CPU (Mac M1, M2, etc.) users, see the [dedicated page](broken://pages/rHGntWOS9j4X4MEg09kx).
{% endhint %}

```sh
docker build . -t regsitry.edgegap.com/<YOUR_REPO>/demo-nuxt-edgegap:<YOUR_VERSION_NUMBER>
```

It is preferable you put a version number following semantic versioning, but if you don’t plan on updating your build on Edgegap after your first deployment, you can omit the version number entirely.

But before that, you need to push your newly created docker image to the image repository of your choice. Remember that we offer private repositories through Harbor that offer you a series of security advantages you would not necessarily have by using the default Docker repository.

But before that, you need to push your newly created docker image to the image repository of your choice. Remember that we offer private repositories through Edgegap Registry that offer you a series of security advantages you would not necessarily have by using the default Docker repository.

To push your image to our repository, you will have to first log in with your credentials by using this command:

```sh
docker login regsitry.edgegap.com
```

Now, the only thing left is to push your image like this:

```sh
docker push regsitry.edgegap.com/<YOUR_REPO>/demo-nuxt-edgegap:<YOUR_VERSION_NUMBER>
```

Great! Now, you are only one step away from deploying your Nuxt app on Edgegap’s platform.

### Deploy your application on Edgegap

There are two ways of deploying an application on Edgegap. For both, you will need to have an account with us, which you can create for free by clicking the link right here. First, let’s go over on how to deploy our containerized application through our website.

#### Deploying an application through Edgegap’s dashboard:

You will have to head over to the following web address and enter your credentials. Once you do so, you will be automatically redirected to your dashboard. If you have just created your account, you will see an option to launch your first application right away. If not, simply head to the “Applications and games” tab on the menu on the left of your screen. You will see an option to create a new application on the right corner:

<figure><img src="/files/SV4PrbrEhixe5I0M3vQC" alt=""><figcaption></figcaption></figure>

Once you click on it, the following form should appear:

<figure><img src="/files/FWokQQ8A6Qsp8rvmy1Lw" alt=""><figcaption></figcaption></figure>

You should fill all the required fields and, once you’re done, there is a few things you will need to modify from the defaults:

* Port number: This application needs to have port 3000 open, so please add it in the “ports” section of the creation form.
* Maximum time to deploy: As mentioned previously in this tutorial, our NuxtJS image is around 1GB in size. To deploy an image that big on Edgegap, you will have to increase the Maximum time to deploy to around 300 seconds.

Once you’ve finished filling out the form with the adequate information, you simply must submit it, and you will be ready for deployment. Simply select the region and the number of players you wish to emulate.

<figure><img src="/files/hcvV5vamqNnepRKAl0Ll" alt=""><figcaption></figcaption></figure>

Once you confirm your choices, your application will be online shortly. Here’s what you will see once it’s been deployed. You can access it yourself by clicking on the icon we’ve circled in this image.

<figure><img src="/files/L5pXJDPDuKYAYae6Acof" alt=""><figcaption></figcaption></figure>

This will open a new tab where you will find the default landing page of your Nuxt application.

#### Deploying an application through Edgegap’s API

While our web dashboard is the most user-friendly way of deploying an application, you can also do so through a very simple API request. For this, you can use postman. It is important you visit the dashboard to [create an API token](broken://pages/F4vwDNrmVSppwWjl8uzv) through the options on your profile. Here’s a simple guide on how to create your API token.

Once you have the API token in your hands, the only thing left to do is formulate a deployment request with your application name, its version, the IP addresses, the region you want to make your deployment in and, of course, your token as part of the authorization parameters. You can see what a full request looks like on our documentation here.

Once you send the request, you should see a response on your command line or on Postman should look like this:

<figure><img src="/files/gZWUV85QMDuNq68JlUJe" alt=""><figcaption></figcaption></figure>

That is all you must do to deploy an application through a CLI. To confirm your app was deployed, you can head over to the dashboard again and see your active deployment at work by clicking on the “Active deployments” tab.


# Ruby On Rails

### Installing Ruby

Edgegap offers you a very intuitive platform to deploy a serverless web application wherever you need it. For this tutorial, we will go over the steps to deploy a Ruby on Rails. Before checking for anything else, make sure you have Ruby installed.

If you’re running on a Linux/UNIX machine, you can use the pre-installed package manager on your machine and install Ruby through your terminal. You can refer to [Ruby’s official documentation](https://www.ruby-lang.org/en/documentation/installation/) to find the precise command that corresponds to your Linux/UNIX distribution.

On Windows machines, you will have to [download RubyInstaller](https://rubyinstaller.org) to get Ruby.

At the end of the installation, you can check Ruby is installed properly with the following command:

```
ruby -v
```

Now with Ruby installed, you will obviously have to install Rails. This is rather straightforward and can be done with this single command:

```
gem install rails
```

You can easily confirm that Rails was installed correctly in a similar way as what we used to verify Ruby’s installation.

```
rails -v
```

### Requirements

To follow this tutorial, you will need to have the following besides Ruby and Ruby on Rails:

* [Docker Desktop](https://www.docker.com/get-started/)
* [An account on Edgegap](https://app.edgegap.com/auth/login)
* Access to [Edgegap's private container repository](/learn/advanced-features/edgegap-container-registry) or another registry like [Docker's container registry](https://hub.docker.com/)

### Getting Started with Ruby on Rails

To get started with ruby on rails, you will have to create a folder for your new project and run the following command on it:

```
rails new edgegap-ruby-rails-demo
```

Once the process finishes installing, you will have to modify your routes.rb file, which you will find in the config folder. Your routes file should contain the following:

```ruby
Rails.application.routes.draw do
  root 'hello_world#index'
end
```

You may notice there is no ‘hello\_world’ page in your project in the ‘views’ folder. You can easily generate it with its corresponding controller. First, make sure you’re in the folder Rails generated for you when you ran the `rails new` command.

If you’re using a Linux distribution, use the following command to create the HelloWorld controller:

```
rails generate controller HelloWorld index --skip-routes
```

The skip-routes option ensures this command won’t generate a new route to this controller, which is what we want since we already created one manually.

For windows, you will have to add some keywords to your command as follows:

```
ruby bin/rails generate controller HelloWorld index –skip-routes
```

Once the command executes successfully, you will see the HelloWorld page in the ‘views’ folder. You may have to correct the HTML syntax to get a proper section before you deploy it. The file should look like this:

<figure><img src="/files/hPPNexZbKZdLVIljo4Nq" alt=""><figcaption></figcaption></figure>

The only step left to do to finish your project is deploying your Ruby on Rails app locally. Once again, the command to do so varies between Linux and Windows as follows:

Linux:

```sh
rails server
```

Windows:

```sh
ruby bin/rails server
```

### Containerize your application

Containerizing most applications is straightforward. You must create a Dockerfile and then run that file on Docker to create the image. Visual Studio Code is a good platform to create your Dockerfile since, by default, it does not put an extension to every new file that we create. That is exactly what we need. Simply create a new file and name it Dockerfile. You will see the little docker logo appear next to it. Now, you can copy and paste this script into it to get the main dependencies. The image this dockerfile will create is around 700 MB. Of course, you can optimize it, but a container this size will run very smoothly on Edgegap:

```dockerfile
FROM ruby:3-alpine
WORKDIR /app
COPY . .
RUN apk add --no-cache build-base tzdata nodejs yarn sqlite-dev postgresql-dev mysql-dev
RUN gem install bundler
RUN bundle install
ENV RAILS_ENV=production
RUN bundle exec rails assets:precompile
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]
```

After saving your Dockerfile, it is time to create the image. You can easily do so by running the following command:

{% hint style="warning" %}
For ARM CPU (Mac M1, M2, etc.) users, add `--platform linux/amd64`  option to your build command.
{% endhint %}

```sh
docker build -t registry.edgegap.com/<YOUR_REPO>/ruby-rails-demo:<YOUR_VERSION_NUMBER> .
```

It is preferable you put a version number following semantic versioning, but if you don’t plan on updating your build on Edgegap after your first deployment, you can omit the version number entirely. But before that, you need to push your newly created docker image to the image repository of your choice. Remember that we offer private repositories through Edgegap Registry that offer you a series of security advantages you would not necessarily have by using the default Docker repository. To push your image to our repository, you will have to first log in with your credentials by using this command:

```sh
docker login registry.edgegap.com
```

Now, the only thing left is to push your image like this:

```sh
docker push registry.edgegap.com/<YOUR_REPO>/ruby-rails-demo:<YOUR_VERSION_NUMBER>
```

Great! Now, you are only one step away from deploying your Nuxt app on Edgegap’s platform.

### Deploy your application on Edgegap

There are two ways of deploying an application on Edgegap. For both, you will need to have an account with us, which you can create for free by clicking the link right here. First, let’s go over on how to deploy our containerized application through our website.

#### Deploying an application through Edgegap’s dashboard

You will have to head over to the [Edgegap Platform website](https://console.edgegap.com/) and enter your credentials. Once you do so, you will be automatically redirected to your dashboard. If you have just created your account, you will see an option to launch your first application right away. If not, simply head to the “Applications & Games” tab on the menu on the left of your screen. You will see an option to create a new application on the right corner:

<figure><img src="/files/SV4PrbrEhixe5I0M3vQC" alt=""><figcaption></figcaption></figure>

Once you click on it, the following form should appear:

<figure><img src="/files/FWokQQ8A6Qsp8rvmy1Lw" alt=""><figcaption></figcaption></figure>

You should fill all the required fields and, once you’re done, there is a few things you will need to modify from the defaults:

* Port number: This application needs to have port 3000 open, so please add it in the “ports” section of the creation form. Uncheck the option to verify the port.

Once you’ve finished filling out the form with the adequate information, you simply must submit it, and you will be ready for deployment. Simply select the region and the number of players you wish to emulate.

Once you’ve finished filling out the form with the adequate information, you simply must submit it, and you will be ready for deployment. Simply select the region and the number of players you wish to emulate.

<figure><img src="/files/XJn65vYr1pjZrrkPv0br" alt=""><figcaption></figcaption></figure>

Once you confirm your choices, your application will be online shortly. Here’s what you will see once it’s been deployed. You can access it yourself by clicking on the icon we’ve circled in this image.

<figure><img src="/files/Y5RpVwQKQg1d8BKW6F43" alt=""><figcaption></figcaption></figure>

This will open a new tab where you will find the default landing page of your Nuxt application.

#### Deploying an application through Edgegap’s API

While our web dashboard is the most user-friendly way of deploying an application, you can also do so through a very simple API request. For this, you can use postman. It is important you visit the dashboard to [create an API token](broken://pages/F4vwDNrmVSppwWjl8uzv) through the options on your profile.

Once you have the API token in your hands, the only thing left to do is formulate a deployment request with your application name, its version, the IP addresses, the region you want to make your deployment in and, of course, your token as part of the authorization parameters. You can see what a full request looks like on our documentation here.

Once you send the request, you should see a response on your command line or on Postman should look like this:

<figure><img src="/files/emoghHPjAPyOeY1RxuoJ" alt=""><figcaption></figcaption></figure>

That is all you must do to deploy an application through a CLI. To confirm your app was deployed, you can head over to the dashboard again and see your active deployment at work by clicking on the “Active deployments” tab.


# Tools & Integrations


# Container


# What is Docker

At Edgegap, we work with containers to distribute the load evenly across the globe. We need to have your game started within milliseconds, so the containers need to have speed in mind and build accordingly. We will walk you through creating a docker image, also known as a container.

{% hint style="info" %}
If you want to familiarize yourself with containers quickly, we strongly recommend [this quick video](https://www.youtube.com/watch?v=J0NuOlA2xDc\&ab_channel=Coderized).
{% endhint %}

<figure><img src="/files/I56RmIgHqa3BBaW8aC63" alt=""><figcaption></figcaption></figure>

### **What is Docker?**

* Docker is a PaaS (Platform as a Service) that allows OS-level virtualization.

### **What is the Docker Run command?**

* The docker run command allows a container to be created from an image so that the Docker desktop engine can run the Container.

### **Give it a try**

Let's start by adding the tools you will need on your computer to create your first Container.

You can follow the installation procedure as stated on Docker desktop.

if you are running [Windows 10 Pro](https://docs.docker.com/docker-for-windows/install/) If you are running [Windows 10 Home Edition](https://docs.docker.com/docker-for-windows/install-windows-home/) If you are running [macOS](https://docs.docker.com/docker-for-mac/install/)

Now that you have your local machine running Docker, we can start with a simple hello world.

```sh
docker run -d -p 80:80 --name speedtest edgegap/speedtest-edge
```

You can go to your command prompt and type the command above.

The steps will proceed automatically.

1. Download the Container from the Edgegap Repository (Only the first time)
2. Start the Container
3. The name will be speedtest (--name)
4. To expose your Container, you need to do it in the docker run command. In this example, we will expose port 80 on the outside, pointing to the exposed 80 in the Container (-p)
5. You can try it by going on your Web browser and hitting [http://localhost](http://localhost/)
6. Voila, you have run your first Container.

Now that you have your Container running, you can interact with it. We will restart the Container, stop it, and see its network in the next session. First, you will need to locally see what is running on your docker-engine at the command line type.

```sh
docker ps
```

You should see something similar too.

```
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                NAMES
dbf4d3734111        edgegap/speedtest-edge   "docker-php-entrypoi…"   29 minutes ago      Up 29 minutes       0.0.0.0:80->80/tcp   speedtest
```

You visually see the Container information running on your computer; you can have more than one running on this list.

`docker ps` is a simple command; you will learn to use it a lot.

Let's say that you need to have more information about your running container. You can ask the docker engine to give you all the details about your Container by running the following command:

```sh
docker container inspect speedtest
```

If you wish to see the internal network created by the docker engine, you can do so by running the following command.

```sh
docker network ls
```

You will get something similar to this:

```
NETWORK ID          NAME                DRIVER              SCOPE
67b24038b9ca        bridge              bridge              local
6f7fe43a489d        host                host                local
3d80cdb86a7e        none                null                local
```

If you wish to dig deeper into the network configuration, you can run the following command:

```sh
docker network inspect 67b24038b9ca
# where 678b24038b9ca is your network connected to your Container
```

Docker Stop

Now that you start your Container, you can stop it using:

```sh
docker container stop speedtest
```


# Your First Docker

Now that you have docker running on your computer let's create your first Dockerfile.

Before creating a container, you need to understand what a container is. A container runs natively on Linux (or in Docker desktop) and shares the host machine's kernel with other containers. It runs a discrete process, taking no more memory than any further executable, making it lightweight.

<figure><img src="/files/kpknD6SqkELAiTz59rMA" alt=""><figcaption></figcaption></figure>

Now that you've set up your environment, you can begin to develop containerized applications.

Since the documentation on how to create a container is already readily available online, we suggest you follow this excellent guide from Docker on Building and Running your image:

[Docker - Get Started Guide](https://docs.docker.com/get-started/02_our_app/)

{% hint style="info" %}
We recommend using the base image that you are comfortable ([CentOS](https://hub.docker.com/_/centos), [Ubuntu](https://hub.docker.com/_/ubuntu), [Debian](https://hub.docker.com/_/debian) or [Alpine](https://hub.docker.com//alpine)). We can run all Linux-based kernels without issues.
{% endhint %}


# The Good Practices

We strongly recommend you to go through [the good Practices of Docker](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) to help you learn the best way to optimize your container and its security.

Here are some tips to optimize your container to work perfectly with Edgegap.

## Versioning

Versioning on your container build is an essential aspect of a container's life since a lot of environments will not pull the image again if the tag is already present on the machine.

### The Tag *latest*

By default, when you run the build command:

{% hint style="warning" %}
For ARM CPU (Mac M1, M2, etc.) users, add `--platform linux/amd64`  option to your build command.
{% endhint %}

```bash
docker build -t repo/example .
```

The output will give to this build the tag **latest**

The following does the same output:

```bash
docker build -t repo/example:latest .
```

But keep in mind, **latest** is only a tag like any other and does not always represent your latest build.

Example:

```bash
docker build -t repo/example:latest .
# do some modification to your code
docker build -t repo/example:0.1 .
```

Edgegap will NOT update your tag **latest** and version **0.1** will be ahead with the new code.

### Example for Tagging

We recommend bumping or changing your version on every image build to ensure proper usage and avoid caching the old version in the system. Suppose you are already using a system to do semantic versioning or bumping the version on every compilation. In that case, you can make the process of automatically tagging your Docker build pretty easy.

Example of Bash Script to build your Dockerfile with a matching version you pass the argument.

This example will build with the given version, tag to the repository, and push it (Do not forget to login first if you are using a private Repository).

```bash
#!/bin/bash

if [[ $# -ne 1 ]] ; then
    echo 'No Argument Provided, Please specify the version you want to build this Image. --> example: ./docker-build.sh 0.0.1'
    exit 1
fi

docker build -t my-repo/example:$1 .

docker tag my-repo/example:$1 registry.edgegap.com/my-repo/example:$1
docker push registry.edgegap.com/my-repo/example:$1
```

With this script, you can now use your App build version to call it correctly.

Example: You build your new Game Server Version **2021.01.29.1234**

```bash
# From your CI/CD Pipeline or manually

./docker-build.sh 2021.01.29.1234
```

### Why

The main reason to bump a tag every build is to prevent caching in the Edge.

To be able to boot the server in a matter of seconds, we pre-pull the image and only multiply the number of instances on demand. We can't guarantee the frequency of Pull Policy since we only pull it if the image is not already present on the Edge.

So basically using **latest** tag or any persistent tag might cause you trouble to fix or debug since you are going to update the build and deploy it on Edgegap but the Edge won't pull the new *version* your tag since it is the same.

If you never use [semantic versioning](https://semver.org/), Edgegap hardly suggest you read about it and apply this to your build versioning!


# SSH in Your Container

{% hint style="warning" %}
Edgegap does not recommend having SSH service accessible in your production deployments.
{% endhint %}

### Modifying your Dockerfile

We will build the docker container using a different Docker `Dockerfile` in this version.

We will need to install the OpenSSH service and enable it Create a User and a password to connect to your server.

In a container running on **Alpine**, merge the following:

```dockerfile
RUN apk --update add --no-cache openssh bash
RUN sed -i s/#PermitRootLogin.*/PermitRootLogin\ yes/ /etc/ssh/sshd_config
RUN echo "tester:$ecureP@ss" | chpasswd

EXPOSE 22
```

In a container running on **Ubuntu**, add the following:

```dockerfile
RUN apt-get update
RUN apt-get install openssh-server sudo -y

RUN useradd -rm -d /home/ubuntu -s /bin/bash -g root -G sudo -u 1000
RUN echo 'tester:$ecureP@ss' | chpasswd

EXPOSE 22
```

In your entrypoint, you will need to add the following command:

```sh
service ssh start
```

We suggest testing the deployment locally, using the docker run command to test the server start.

Once tested, you can push your new version on the container repository.

### On Edgegap

We suggest having a version called `-dev` or something that would make sense in your service. By doing so, you will have the option to do some tests and log into your container easily using the Edgegap infrastructure.

1. Create a new version of your app, adding the `Expose TCP Port 22`
2. Create a deployment using the new version you just created
3. Once you have the service running (Ready), look in your deployment information. You will see the external port mapping of port 22.
4. You can use Putty and connect using the FQDN of the deployment or Ip & the port mapping used
5. The username and password that you configured.


# External Registries


# Docker Hub

{% hint style="warning" %}
We strongly recommend upgrading to paid docker hub tier to prevent image download throttling.
{% endhint %}

### You need to have on hand

* [x] Your Docker Repository name
* [x] Your Docker Access token

### Access Token OR Deploy Token

{% hint style="success" %}
We recommend a Read-Only Token for production usage
{% endhint %}

You can follow this [**Tutorial**](https://docs.docker.com/docker-hub/access-tokens) to generate an Access Token

### Split the Repository Name

Given this Full Repository: **group/project**

Your **Repository** will always be: **docker.io**

and your **Image** will be: **group/project**

### Add the app on Edgegap

**API Example**

```json
{
    [...]
    "docker_repository": "docker.io",
    "docker_image": "group/project",
    "docker_tag": "v1",
    "private_username": "example@edgegap.com",
    "private_token": "<Access Token>",
    [...]
}
```

**From Dashboard**

From our Dashboard, The **Private registry token** will be the content of your **Access Token**

<figure><img src="/files/UgXfn7BwxbTJPEnyA5EO" alt=""><figcaption></figcaption></figure>


# AWS ECR

*Make sure that you have the latest version of the AWS CLI and Docker installed.*

### You need to have on hand

* [x] The AWS Region of your ECR
* [x] The Registry URI

### To get this information, go to AWS Console

1. Go to your ECR Region
2. Go to Elastic Container Registry
3. Then to Repositories
4. Click on the Repository you wish to get your image from
5. Click on the **View push commands**.

### Split the URI

By default the AWS **URI** contains the Registry and the Image, you will need to split those

Imagine having this **URI**: **597351113950.dkr.ecr.ca-central-1.amazonaws.com/example**

Your **Repository** will be: **597351113950.dkr.ecr.ca-central-1.amazonaws.com**

and your **Image** will be: **example**

### Get the password

To get your ECR Login Password

```bash
aws ecr get-login-password --region ca-central-1
```

{% hint style="success" %}
You can change to your region (e.g. us-east-1, us-west-1, etc).
{% endhint %}

#### More info on [**AWS Doc**](https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login-password.html)[**umentation**](https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login-password.html)

### Add the app to Edgegap

#### API Example

{% hint style="info" %}
The username will always be AWS
{% endhint %}

```json
{
    [...]
    "docker_repository": "597351113950.dkr.ecr.ca-central-1.amazonaws.com",
    "docker_image": "example",
    "docker_tag": "v1",
    "private_username": "AWS",
    "private_token": "<ecr login-password>",
    [...]
}
```

#### From Dashboard

From our Dashboard, The **Private registry token** will be your **ecr login-password**

<figure><img src="/files/Bpk3y99Qogim6PrM6hy7" alt=""><figcaption></figcaption></figure>


# GCP GCR

### Use Edgegap with Google Container Registry

### You need to have on hand

* [x] The GCP Region of your GCR
* [x] Your GCP Project Name
* [x] Your JSON Key for the IAM User

### To get this information, go to GCP Console

1. Go to your Project
2. Go to Container Registry
3. Copy the Full Repository Name

### Split the Full Repository Name

Given this Full Repository: **gcr.io/project-name/example**

Your **Repository** will be: **gcr.io**

and your **Image** will be: **project-name/example**

### Generate your JSON Key File

Follow [**GCP Steps**](https://cloud.google.com/container-registry/docs/advanced-authentication#json-key) to generate this file.

{% hint style="warning" %}
Please Verify that your `Google Service Account` has the right permissions to manage your container registry
{% endhint %}

You will end with a file that contains something similar

```json
{
  "type": "service_account",
  "project_id": "project-name",
  "private_key_id": "12345abcdef12345",
  "private_key": "-----BEGIN PRIVATE KEY-----\n[...]\n-----END PRIVATE KEY-----\n",
  "client_email": "example@project-name.iam.gserviceaccount.com",
  "client_id": "1234567890",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/example%40project-name.iam.gserviceaccount.com"
}
```

### Add the app on Edgegap

#### **API Example**

{% hint style="info" %}
The username will always be **\_json\_key**
{% endhint %}

```json
{
    [...]
    "docker_repository": "gcr.io",
    "docker_image": "project-name/repository-name",
    "docker_tag": "v1",
    "private_username": "_json_key",
    "private_token": "<The Content of JSON key file as JSON String>",
    [...]
}
```

The Private Token will look something like this

```json
{
    [...]
    "private_token": "{\"type\": \"service_account\", \"project_id\": \"project-name\", \"private_key_id\": \"12345abcdef12345\", \"private_key\": \"-----BEGIN PRIVATE KEY-----\\n[...]\\n-----END PRIVATE KEY-----\\n\", \"client_email\": \"example@project-name.iam.gserviceaccount.com\", \"client_id\": \"1234567890\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/example%40project-name.iam.gserviceaccount.com\"}",
    [...]
}
```

#### **From Dashboard**

From our Dashboard, The **Private registry token** will be the content of your **JSON key file**

**You can copy the content directly without Stringify it**

<figure><img src="/files/DRT3Gjf2I5Cn0lkXUnEw" alt=""><figcaption></figcaption></figure>


# Gitlab registry

### You need to have on hand

* [x] Your Gitlab Registry Full Repository name
* [x] Your Gitlab Personal access token OR a deploy token

### Access Token OR Deploy Token

You can follow this [**Tutorial**](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) to generate a Personal Access Token

OR

You can follow this [**Tutorial**](https://docs.gitlab.com/ee/user/project/deploy_tokens/index.html) to generate a Deploy Token

### Split the Full Repository Name

Given this Full Repository: **registry.gitlab.com/group/project**

Your **Repository** will be: **registry.gitlab.com**

and your **Image** will be: **group/project**

### Add the app on Edgegap

#### **API Example**

```json
{
    [...]
    "docker_repository": "registry.gitlab.com",
    "docker_image": "group/project",
    "docker_tag": "v1",
    "private_username": "example@edgegap.com",
    "private_token": "<Personal Access Token OR Deploy Token>",
    [...]
}
```

#### **From Dashboard**

From our Dashboard, The **Private registry token** will be the content of your **Personal Access Token OR Deploy Token**

<figure><img src="/files/wxWWkz0Rqm6UD7KTqSkf" alt=""><figcaption></figcaption></figure>


# Switch From Multiplay

**Take advantage of free integration support for Unity and Unreal projects** and migrate to our reliable hosting easily. If you're relying on Multiplay hosting services, the time to switch is now.

{% hint style="success" %}
[Orchestration](/learn/orchestration) and DevOps are at the core of Edgegap platform. **Focus on your core business** instead of building scaffolding. **Minimize vendor lock-in** with cloud native approach and loose service coupling.
{% endhint %}

## 🚨 Multiplay End of Life

{% hint style="danger" %}
Unity is beginning a phased exit from the Multiplay Game Server Hosting service. Unity will operate the service through **March 31, 2026. You must choose and complete a transition path before this date.**
{% endhint %}

## 🚀 Getting Started

With Multiplay end of life closing in, evaluating replacements is an urgent need to keep your business running and prevent negative impact on your player base. Edgegap engineers and our amazing dev community are available for integration support and navigating this stressful situation.

Try one of our quick start guides supported with game engine plugins:

* [Unreal Engine](/unreal-engine),
* [Unity](/unity),
* Custom Runtime (coming soon).

{% hint style="success" %}
[Browse our Game Samples](/docs/sample-projects) to get inspired and gain confidence to move forward with your project.
{% endhint %}

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

## 💡 Case Studies

{% hint style="success" %}
[Discover how Ghosts of Tabor, the #1 extraction shooter on VR, migrated to Edgegap from Multiplay.](https://edgegap.com/gaming/case-studies)
{% endhint %}

## 🗺️ Replace Features

Review and identify Multiplay and Unity services and features which you need to replace:

<table><thead><tr><th width="196" valign="top">Multiplay Feature</th><th width="552">Edgegap Features</th></tr></thead><tbody><tr><td valign="top">Authentication</td><td><p>Optionally, add fine-grained access controls:</p><ul><li><a href="/pages/kBCQHitUL4u9O0iTPk7x#authenticate">matchmaker API token,</a></li><li><a href="/pages/UkbJIQ8RFaAL5RDNZsvm#authenticate">server browser client/server API tokens + federated identity,</a></li><li><a href="https://app.edgegap.com/user-settings?tab=tokens">custom DIY session management (Edgegap API token)</a>.</li></ul></td></tr><tr><td valign="top">Hosting SDK</td><td><strong>No SDK required!</strong> ServerConfig replaced by <a data-mention href="/pages/o1QaIOSMNzSkg8Ggd8ab">/pages/o1QaIOSMNzSkg8Ggd8ab</a>.</td></tr><tr><td valign="top">Create a Build</td><td>Try our quickstart tools - <a data-mention href="/pages/qi7yUlK2g5j8zjMB2WIM">/pages/qi7yUlK2g5j8zjMB2WIM</a> / <a data-mention href="/pages/DbR6ihTNvNj5aBknA5wA">/pages/DbR6ihTNvNj5aBknA5wA</a>.</td></tr><tr><td valign="top">Build Configuration</td><td>Fully Automated. Optionally <a href="/pages/o1QaIOSMNzSkg8Ggd8ab#port-mapping">expose additional ports</a> or <a href="/pages/o1QaIOSMNzSkg8Ggd8ab#injected-variables">variables</a>.</td></tr><tr><td valign="top">Server Query Protocol</td><td><ul><li>DevOps and Testing - <a href="/pages/mZvxBMVbL6o86l76XdIa#get-v1-status-request_id">Deployment Status API</a>.</li><li>Backend Automation - <a href="/pages/0UXQAhtFuL0FkdoUmYTh#webhooks-and-postbacks">Deployment Webhook</a>.</li><li>Client Integration - <a href="/pages/UkbJIQ8RFaAL5RDNZsvm#search-and-browse">Server Browser / Search and Browse</a>.</li></ul></td></tr><tr><td valign="top">Fleet</td><td><a data-mention href="/pages/58CVGf4IKRrTIMgc5xr5">/pages/58CVGf4IKRrTIMgc5xr5</a> with <a data-mention href="/pages/VOlexArJSLKHpTJQOwSm">/pages/VOlexArJSLKHpTJQOwSm</a> and <a href="/pages/58CVGf4IKRrTIMgc5xr5#overflow-to-cloud">Cloud Overflow</a>. Optionally add later on, or default to <a data-mention href="/pages/0UXQAhtFuL0FkdoUmYTh#match-bound">/pages/0UXQAhtFuL0FkdoUmYTh#match-bound</a> cloud.</td></tr><tr><td valign="top">Test Allocation</td><td>With <a href="https://app.edgegap.com/deployment-management/deployments/list">dashboard web GUI</a>, <a href="/pages/0UXQAhtFuL0FkdoUmYTh#id-1.-start-a-deployment">Editor Plugins</a>, or <a href="/pages/mZvxBMVbL6o86l76XdIa#post-deployments">Deploy API</a>.</td></tr><tr><td valign="top">Lobby</td><td><a href="/pages/kBCQHitUL4u9O0iTPk7x#group-up">Group Up for Matchmaking</a> or <a href="/pages/UkbJIQ8RFaAL5RDNZsvm#reserve-seats">Reserve Seat in Server Browser</a>.</td></tr><tr><td valign="top">Matchmaking</td><td><a href="/pages/xxQpefaT7MKuSh6JHPrg">No-code matchmaker</a>, <a href="/pages/kBCQHitUL4u9O0iTPk7x#hosting-cluster">test for free</a>, <a href="/pages/xxQpefaT7MKuSh6JHPrg#id-5.-game-integration">integrate with lightweight SDKs</a>.<br>Alternatively, <a data-mention href="#start-deployments-from-ugs-matchmaker">#start-deployments-from-ugs-matchmaker</a>.</td></tr><tr><td valign="top">AB testing</td><td>Configurable <a data-mention href="/pages/kBCQHitUL4u9O0iTPk7x#matchmaking-profiles">/pages/kBCQHitUL4u9O0iTPk7x#matchmaking-profiles</a> and <a data-mention href="/pages/kBCQHitUL4u9O0iTPk7x#rolling-updates-and-ab-tests">/pages/kBCQHitUL4u9O0iTPk7x#rolling-updates-and-ab-tests</a>.</td></tr><tr><td valign="top">Cloud Save</td><td><a data-mention href="/pages/VOlexArJSLKHpTJQOwSm">/pages/VOlexArJSLKHpTJQOwSm</a> with <a data-mention href="/pages/1DeE111HMosK5AOCoZRE">/pages/1DeE111HMosK5AOCoZRE</a>.</td></tr><tr><td valign="top">Server Analytics</td><td><a href="/pages/0UXQAhtFuL0FkdoUmYTh#analytics">Deployment Analytics</a> and <a href="/pages/kBCQHitUL4u9O0iTPk7x#analytics">Matchmaking Analytics</a>.</td></tr></tbody></table>

## :star: Start Deployments from UGS Matchmaker

If you prefer not to migrate to Edgegap's [Matchmaking](/learn/matchmaking) and keep your UGS Matchmaker, see our [UGS hosting provider cloud script integration](https://github.com/Unity-Technologies/matchmaker-hosting-providers/blob/main/modules/EdgegapAllocator/CONFIGURATION.md).

### Deployment Placement

[Choosing the right location for your deployment can help reduce latency by up to 58% on average!](/learn/orchestration/deployments#server-placement)

To take advantage of low-latency edge deployments you will need to:

1. [include our `EdgegapAllocator`  cloud script module in your UGS matchmaker](https://github.com/Unity-Technologies/matchmaker-hosting-providers/blob/main/modules/EdgegapAllocator/CONFIGURATION.md),
   1. add your `EDGEGAP_API_TOKEN` to UGS Secrets,
   2. paste your Edgegap App name and Version in the cloud script code,
2. include player's public IP address when creating matchmaker tickets:
   1. when creating ticket with `POST- https://matchmaker.services.api.unity.com/v2/tickets` ,
   2. include public IP in custom data:

```json
{
    "queueName": "basic",
    "attributes": {},
    "players": [
        {
            "id": "Player 1",
            "customData": {
                "player_ip": "8.8.8.8"
            }
        },
        {
            "id": "Player 2",
            "customData": {
                "player_ip": "4.4.4.4"
            }
        }
    ]
}
```

<figure><img src="/files/Y9q9Ssfv1aVTsOiCoXlI" alt=""><figcaption><p>Storing Deployment Details for Player Connections</p></figcaption></figure>


# Switch From Gamelift

If you are looking to switch from using AWS Gamelift to Edgegap, the following simple steps will get you running in no time. Before getting started, we expect that:

* You currently use AWS Gamelift
* You currently have a working game server build on Gamelift
* You already [created](/learn/orchestration/application-and-versions) and [deployed](/learn/orchestration) an application on Edgegap

{% hint style="info" %}
You will benefit from way more locations with the Edgegap solution.
{% endhint %}

### Remove AWS Gamelift SDK

The first step to switch to Edgegap is to remove the code that initilaizes AWS Gamelift within your game server. This is to reduce overhead and prevent errors. The code you are looking to remove probably looks like the following:

#### Unity (C#)

```cs
using UnityEngine;
using Aws.GameLift.Server;
using System.Collections.Generic;

public class GameLiftServerExampleBehavior : MonoBehaviour
{
    //This is an example of a simple integration with GameLift server SDK that will make game server processes go active on GameLift!
    public void Start()
    {
        //Identify port number (hard coded here for simplicity) the game server is listening on for player connections
        var listeningPort = 7777;

        //InitSDK will establish a local connection with GameLift's agent to enable further communication.
        var initSDKOutcome = GameLiftServerAPI.InitSDK();
        if (initSDKOutcome.Success)
        {
            ProcessParameters processParameters = new ProcessParameters(
                (gameSession) => {
                    //When a game session is created, GameLift sends an activation request to the game server and passes along the game session object containing game properties and other settings.
                    //Here is where a game server should take action based on the game session object.
                    //Once the game server is ready to receive incoming player connections, it should invoke GameLiftServerAPI.ActivateGameSession()
                    GameLiftServerAPI.ActivateGameSession();
                },
                (updateGameSession) => {
                    //When a game session is updated (e.g. by FlexMatch backfill), GameLiftsends a request to the game
                    //server containing the updated game session object.  The game server can then examine the provided
                    //matchmakerData and handle new incoming players appropriately.
                    //updateReason is the reason this update is being supplied.
                },
                () => {
                    //OnProcessTerminate callback. GameLift will invoke this callback before shutting down an instance hosting this game server.
                    //It gives this game server a chance to save its state, communicate with services, etc., before being shut down.
                    //In this case, we simply tell GameLift we are indeed going to shutdown.
                    GameLiftServerAPI.ProcessEnding();
                },
                () => {
                    //This is the HealthCheck callback.
                    //GameLift will invoke this callback every 60 seconds or so.
                    //Here, a game server might want to check the health of dependencies and such.
                    //Simply return true if healthy, false otherwise.
                    //The game server has 60 seconds to respond with its health status. GameLift will default to 'false' if the game server doesn't respond in time.
                    //In this case, we're always healthy!
                    return true;
                },
                listeningPort, //This game server tells GameLift that it will listen on port 7777 for incoming player connections.
                new LogParameters(new List<string>()
                {
                    //Here, the game server tells GameLift what set of files to upload when the game session ends.
                    //GameLift will upload everything specified here for the developers to fetch later.
                    "/local/game/logs/myserver.log"
                }));

            //Calling ProcessReady tells GameLift this game server is ready to receive incoming game sessions!
            var processReadyOutcome = GameLiftServerAPI.ProcessReady(processParameters);
            if (processReadyOutcome.Success)
            {
                print("ProcessReady success.");
            }
            else
            {
                print("ProcessReady failure : " + processReadyOutcome.Error.ToString());
            }
        }
        else
        {
            print("InitSDK failure : " + initSDKOutcome.Error.ToString());
        }
    }

    void OnApplicationQuit()
    {
        //Make sure to call GameLiftServerAPI.Destroy() when the application quits. This resets the local connection with GameLift's agent.
        GameLiftServerAPI.Destroy();
    }
}
```

#### Unreal Engine (C++)

```cpp
using UnrealBuildTool;

public class MyAwesomeGame : ModuleRules
{
	public MyAwesomeGame(TargetInfo Target)
	{
		PublicDependencyModuleNames.AddRange(
            // Remove GameLiftServerSDK
            new string[] { "Core", "CoreUObject", "Engine", "InputCore", "GameLiftServerSDK" }
        );
        bEnableExceptions = true;
	}
}
```

```cpp
#include "GameLiftFPS.h"
#include "Engine.h"
#include "EngineGlobals.h"
#include "GameLiftFPSGameMode.h"
#include "GameLiftFPSHUD.h"
#include "GameLiftFPSCharacter.h"
#include "GameLiftServerSDK.h"

AGameLiftFPSGameMode::AGameLiftFPSGameMode()
	: Super()
{

//Let's run this code only if GAMELIFT is enabled. Only with Server targets!
#if WITH_GAMELIFT

	//Getting the module first.
	FGameLiftServerSDKModule* gameLiftSdkModule = &FModuleManager::LoadModuleChecked<FGameLiftServerSDKModule>(FName("GameLiftServerSDK"));

	//InitSDK will establish a local connection with GameLift's agent to enable further communication.
	gameLiftSdkModule->InitSDK();

	//When a game session is created, GameLift sends an activation request to the game server and passes along the game session object containing game properties and other settings.
	//Here is where a game server should take action based on the game session object.
	//Once the game server is ready to receive incoming player connections, it should invoke GameLiftServerAPI.ActivateGameSession()
	auto onGameSession = [=](Aws::GameLift::Server::Model::GameSession gameSession)
	{
		gameLiftSdkModule->ActivateGameSession();
	};

	FProcessParameters* params = new FProcessParameters();
	params->OnStartGameSession.BindLambda(onGameSession);

	//OnProcessTerminate callback. GameLift will invoke this callback before shutting down an instance hosting this game server.
	//It gives this game server a chance to save its state, communicate with services, etc., before being shut down.
	//In this case, we simply tell GameLift we are indeed going to shutdown.
	params->OnTerminate.BindLambda([=](){gameLiftSdkModule->ProcessEnding();});

	//This is the HealthCheck callback.
	//GameLift will invoke this callback every 60 seconds or so.
	//Here, a game server might want to check the health of dependencies and such.
	//Simply return true if healthy, false otherwise.
	//The game server has 60 seconds to respond with its health status. GameLift will default to 'false' if the game server doesn't respond in time.
	//In this case, we're always healthy!
	params->OnHealthCheck.BindLambda([](){return true; });

	//This game server tells GameLift that it will listen on port 7777 for incoming player connections.
	params->port = 7777;

	//Here, the game server tells GameLift what set of files to upload when the game session ends.
	//GameLift will upload everything specified here for the developers to fetch later.
	TArray<FString> logfiles;
	logfiles.Add(TEXT("aLogFile.txt"));
	params->logParameters = logfiles;

	//Calling ProcessReady tells GameLift this game server is ready to receive incoming game sessions!
	gameLiftSdkModule->ProcessReady(*params);
#endif
}
```

### Containerize your game server

Try one of our quick start guides supported with game engine plugins:

* [Unreal Engine](/unreal-engine),
* [Unity](/unity),
* Custom Runtime (coming soon).

### Push your container on a repository

You will have to push your container on a repository. You can use Edgegap's [private repository](/learn/advanced-features/edgegap-container-registry) or any other option.

### Create an Application on Edgegap

Now that your container is on a repository, you will have to [create an Application](/learn/orchestration/application-and-versions) on Edgegap. This Application will represent your game sever. With this application, you will be able to deploy your server.

You can now [deploy](/learn/orchestration/deployments) your server on demand for your players!


# Switch from Hathora

**Take advantage of free integration support for Unity and Unreal projects** and migrate to our reliable hosting easily. If you're relying on Hathora hosting services, the time to switch is now.

{% hint style="success" %}
[Orchestration](/learn/orchestration) and DevOps are at the core of Edgegap platform. **Focus on your core business** instead of building scaffolding. **Minimize vendor lock-in** with cloud native approach and loose service coupling.
{% endhint %}

## 🚨 Hathora End of Life

{% hint style="danger" %}
Hathora is officially shutting down their Game Hosting service on **May 5th, 2026.**\
**You must choose and complete a transition path before this date.**
{% endhint %}

## 🚀 Getting Started

With Hathora shutting down within weeks, evaluating replacements is an urgent need to keep your business running and prevent negative impact on your player base. Edgegap engineers and our amazing dev community are available for integration support and navigating this stressful situation.

Try one of our quick start guides supported with game engine plugins:

* [Unreal Engine](/unreal-engine),
* [Unity](/unity),
* Custom Runtime (coming soon).

{% hint style="success" %}
[Browse our Game Samples](/docs/sample-projects) to get inspired and gain confidence to move forward with your project.
{% endhint %}

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}

## 🗺️ Replace Features

Review and identify Hathora services and features which you need to replace:

<table><thead><tr><th width="272">Hathora Feature</th><th>Edgegap Feature</th></tr></thead><tbody><tr><td>Authentication</td><td><p></p><p>Optionally, add fine-grained access controls:</p><ul><li><a href="/pages/kBCQHitUL4u9O0iTPk7x#authenticate">matchmaker API token,</a></li><li><a href="/pages/UkbJIQ8RFaAL5RDNZsvm#authenticate">server browser API tokens + federated identity,</a></li><li><a href="https://app.edgegap.com/user-settings?tab=tokens">custom DIY session management (Edgegap API token)</a>.</li></ul></td></tr><tr><td>Hosting CLI</td><td><strong>No CLI required!</strong> Use our <a href="https://app.edgegap.com/">Dashboard</a> or automate builds with <a href="/pages/vSMqjKC2ZsqVRYaGCRcS">API</a>.</td></tr><tr><td>Create a Build</td><td>Try our quickstart tools - <a data-mention href="/pages/qi7yUlK2g5j8zjMB2WIM">/pages/qi7yUlK2g5j8zjMB2WIM</a> / <a data-mention href="/pages/DbR6ihTNvNj5aBknA5wA">/pages/DbR6ihTNvNj5aBknA5wA</a>.</td></tr><tr><td>Hathora Lobbies</td><td><ul><li>DevOps and Testing - <a href="/pages/mZvxBMVbL6o86l76XdIa#get-v1-status-request_id">Deployment Status API</a>.</li><li>Backend Automation - <a href="/pages/0UXQAhtFuL0FkdoUmYTh#webhooks-and-postbacks">Deployment Webhook</a>.</li><li>Client Integration - <a href="/pages/UkbJIQ8RFaAL5RDNZsvm#search-and-browse">Server Browser - Search and Browse</a>.</li></ul><p><a href="/pages/kBCQHitUL4u9O0iTPk7x#group-up">Group Up for Matchmaking</a> or <a href="/pages/UkbJIQ8RFaAL5RDNZsvm#reserve-seats">Reserve Seat in Server Browser</a>.</p></td></tr><tr><td>Fleets</td><td><a data-mention href="/pages/58CVGf4IKRrTIMgc5xr5">/pages/58CVGf4IKRrTIMgc5xr5</a> with <a data-mention href="/pages/VOlexArJSLKHpTJQOwSm">/pages/VOlexArJSLKHpTJQOwSm</a> and <a data-mention href="/pages/58CVGf4IKRrTIMgc5xr5#overflow-to-cloud">/pages/58CVGf4IKRrTIMgc5xr5#overflow-to-cloud</a>.<br>Optionally add later on, or default to <a data-mention href="/pages/0UXQAhtFuL0FkdoUmYTh#match-bound">/pages/0UXQAhtFuL0FkdoUmYTh#match-bound</a> cloud.</td></tr><tr><td><p>Eager Caching (Pro, Enterprise)</p><p>- 3s deployments</p></td><td><a data-mention href="/pages/o1QaIOSMNzSkg8Ggd8ab#active-caching">/pages/o1QaIOSMNzSkg8Ggd8ab#active-caching</a> (Pay as You Go)<br>- 0.5s deployments worldwide in 615+ locations</td></tr><tr><td>Ping Service (Regions)</td><td><a data-mention href="/pages/Q4L0FyoCbqlI4ELOSrUV">/pages/Q4L0FyoCbqlI4ELOSrUV</a> (automated in engine-specific SDKs)</td></tr><tr><td>Telemetry (Analytics)</td><td><a href="/pages/0UXQAhtFuL0FkdoUmYTh#analytics">Deployment Analytics</a> and <a href="/pages/kBCQHitUL4u9O0iTPk7x#analytics">Matchmaking Analytics</a>.</td></tr></tbody></table>

## ⭐ Backend Integrations

Ensure your current game backend will be fully supported after migration. Edgegap offers integration support for all game backends currently supported by Hathora platform:

* [Epic Online Services](/docs/tools-and-integrations/unreal-eos-lobby-integration), [featured post from Feb 2024](https://onlineservices.epicgames.com/en-US/learning/launch-and-scale-your-multiplayer-game-with-epic-online-services-and-edgegap).
* [Playfab Bridge](/docs/tools-and-integrations/playfab-bridge).
* [Unity Gaming Services](/docs/tools-and-integrations/switch-from-multiplay).
* [Nakama by Heroic Labs.](/docs/tools-and-integrations/deploy-from-nakama)
* [Pragma - joint case study.](https://pragma.gg/case-studies/soliton-backend)
* [Beamable.](https://edgegap.com/resources/partners/beamable)
* Bring your own custom backend.

Edgegap additionally supports integrations with platforms (incl. but not limited to) [BrainCloud](https://edgegap.com/resources/partners/braincloud), [Loot Locker](https://edgegap.com/resources/partners/loot-locker), [Invokation Games](https://edgegap.com/resources/partners/invokation-games), [XSOLLA backend](https://edgegap.com/resources/partners/xsolla), [Network Next](https://edgegap.com/resources/partners/network-next) and more.


# Deploy from Nakama

Players of multiplayer games expect the same high quality online experience from Indies to AAAs, whether they play in New York or Jakarta.

For game developers, this standard requires **scalable backend services that work in sync with their game server hosting** to ensure a seamless end-user experience that instantly helps users get online and play on game servers with real-time performance.

Deploy Dedicated Game Servers for popular game engines or custom engines, fully integrated with Nakama's open-source player data and game services for a convenient turnkey solution.

{% hint style="success" %}
This is a Verified Solution co-maintained with an independent partner - [Heroic Labs](https://heroiclabs.com).
{% endhint %}

## 🚀 Getting Started

The integration between Nakama (by Heroic Labs) and Edgegap is designed to streamline the process of deploying scalable, low-latency game servers. Here’s an overview of how it works:

1. **Matchmaking and Player Management with Nakama**: [Nakama handles user accounts, matchmaking, and player data management](https://heroiclabs.com/docs/nakama/getting-started/index.html), using a robust architecture to support real-time interactions between players. Once Nakama identifies that a match should start, it triggers a game instance on Edgegap.
2. **Dynamic Server Deployment with Edgegap**: Edgegap receives the request from Nakama and [deploys a server instance in the optimal location](/learn/orchestration/deployments) based on real-time data, such as the players’ locations and device information. This ensures that game servers are close to players, minimizing latency and creating a smooth in-game experience.
3. **Real-Time Communication**: The integration allows for seamless communication between Nakama’s game backend and Edgegap’s deployment network. Nakama provides the matchmaking, lobby system and general meta game play features, whilst Edgegap ensures that the core game loop (running with a Headless game engine such as Unity) runs as efficiently as possible, no matter where the players are. The connectivity between Edgegap and Nakama mean that players can matchmake better with each other and with existing or new matches running on Edgegap.
4. **Scalability and Load Management**: Edgegap’s system dynamically scales resources up or down depending on player demand, while Nakama scales to accommodate millions of users, even during peak times. This combination allows developers to maintain consistent performance during traffic spikes without over-committing resources.

<figure><img src="/files/N0Q5z0UoDhsECR4gSfBd" alt=""><figcaption></figcaption></figure>

## ⚡ Integration

☑️ **Install and Configure Nakama**: [Start by setting up Nakama as your game’s backend](https://heroiclabs.com/docs/nakama/getting-started/install/). Configure it to handle user accounts, matchmaking, multiplayer sessions, and other game logic:

* for [Heroic Cloud](https://heroiclabs.com/heroic-cloud/), [create a new Builder](https://heroiclabs.com/docs/heroic-cloud/concepts/builders/) using the [nakama-edgegap plugin repository](https://github.com/edgegap/nakama-edgegap),
* for [self-hosted Nakama](/learn/advanced-features/managed-clusters#nakama-by-heroic-labs), [follow plugin Usage instructions to build your image locally](https://github.com/edgegap/nakama-edgegap?tab=readme-ov-file#usage)

☑️ **Sign up for Edgegap**: [Sign up to Edgegap web platform](https://app.edgegap.com/auth/register) to upload your dedicated server image and retrieve API keys and secrets needed for the next step. Edgegap’s hosting is free during development with its free trial, and its no-commitment, highly competitive pay-per-minute pricing ensures studios only pay when players are active, with no upfront costs or fixed monthly fees:

* [Getting Started with Servers (Unity)](/unity),
* [Getting Started with Servers (Unreal Engine)](/unreal-engine).

☑️ **Connect Nakama to Edgegap**: Use Nakama’s FleetManager implementation to connect with Edgegap’s deployment service. Follow the [in-depth guide to configure both services](https://github.com/edgegap/nakama-edgegap?tab=readme-ov-file#nakama-setup).

🙌 Congratulations on integrating Nakama with Edgegap deployments!

{% hint style="success" %}
**Automate Game Server integration** using [Edgegap Server Nakama Plugin for Unity](https://github.com/edgegap/edgegap-server-nakama-plugin-unity).
{% endhint %}

## 🚨 Troubleshooting

{% hint style="info" %}
If you need help, [please reach out to us over Discord](https://discord.gg/MmJf8fWjnt). For live games support see our [ticketing system](https://edgegap.atlassian.net/servicedesk/customer/portal/3).
{% endhint %}


# EOS Lobby Integration

This guide will show you how to integrate Edgegap to work with EOS lobbies in your Unreal game project. Some steps will require you to send requests to our API using a token, and the `curl` commands provided below will allow you to preview each of their response so that you may determine how to manage them in your code. When using the token in your requests, make sure to keep the `token` keyword.

You will also need to have an application version already set up on our platform for our project. In this case, we recommend using the Edgegap Unreal plugin to help speed up the testing process. For more information on the plugin, you can read our documentation [here](broken://pages/5uNALoHBoHCGG1q965v5).

{% hint style="warning" %}
As a safety measure, it is not recommended to have the EOS Lobby Owner send requests directly to the Edgegap API in a production environment, due to the client getting access to the API token. You should instead use a separate service to manage these requests.
{% endhint %}

### 1. Get and Store the players' public IP

To begin with, each player that joins a lobby - including the Lobby Owner - needs to get their public IP and store it as a [Lobby Member property](https://dev.epicgames.com/docs/game-services/lobbies#lobby-and-lobby-member-properties).

```
curl --location --request GET 'https://api.edgegap.com/v1/ip' \
--header 'Authorization: [EDGEGAP_API_TOKEN]'
```

### 2. Create a deployment

Once the Lobby Owner decides that the match is ready to begin, a request needs to be sent to create a new deployment on Edgegap, using the stored public IPs.

```
curl --location --request POST 'https://api.edgegap.com/v1/deploy' \
--header 'Content-Type: application/json' \
--header 'Authorization: [EDGEGAP_API_TOKEN]' \
--data-raw '{
    "app_name": "[EDGEGAP_APP_NAME]",
    "version_name": "[EDGEGAP_APP_VERSION]",
    "ip_list": [
        "[LOBBY_MEMBER_IP]"
    ]
}'
```

Afterwards, the Lobby Owner will need to keep polling the deployment, using the request\_id provided in the response of the deployment creation request. Once the reply has the deployment's `current_status` set to `READY`, the Lobby Owner will need to set the deployment's IP and port values as [EOS Lobby Properties](https://dev.epicgames.com/docs/game-services/lobbies#lobby-and-lobby-member-properties).

```
curl --location --request GET 'https://api.edgegap.com/v1/status/[REQUEST_ID]' \
--header 'Authorization: [EDGEGAP_API_TOKEN]'
```

### 3. Connect to the deployment

When the players receive the notification that the IP and port values have been assigned, they can compare them against the client cache and then use them to connect to the server to play the match.

### 4. End the match and deployment

Once the match is finished, the Lobby Owner will unset the IP and port values from the EOS Lobby Properties, which will notify everyone else to disconnect gracefully from the server. If the application has the configuration to inject environment variables, they can be used to terminate the deployment from within at this time.

To terminate the deployment from outside the server, a request like the `curl` command shown below can be used instead.

```
curl --location --request DELETE 'https://api.edgegap.com/v1/stop/[REQUEST_ID]' \
--header 'Authorization: [EDGEGAP_API_TOKEN]'
```

If the players remain in the lobby, the Lobby Owner can then repeat the steps starting at requesting a new deployment in order to initiate a rematch with the same people.


# Playfab Bridge

This guide will show you how to set up a game with PlayFab so as to automatically deploy a server on Edgegap after players have been matched through the PlayFab matchmaker. The clients will access the information to connect to the server via the PlayFab Player Data, which will be updated by the server once it's ready. Before getting started, we expect that:

* You currently use Playfab;
* You have already [created](/learn/orchestration/application-and-versions) and [deployed](/learn/orchestration) an application with Edgegap before.

You can find the final sample project on our [GitHub](https://github.com/edgegap/sample-playfab-matchmaker-fishnet). This sample was tested using Unity v2021.3.10 and Fishnet v3.11.14 as its netcode.

### Setting Up PlayFab Cloud Script

#### Create New Function

Log on to your PlayFab account, then navigate to your game's overview page on the dashboard. In the `Automation` tab, create a new Cloud Script function that will send a request to the Edgegap API to deploy a new server.

You will need to pass a list of the players' latitude/longitude locations in the request, so the the server can be deployed in the best possible location for everyone.

You also need to pass some custom environment variables as key-value pairs, namely:

* the `Match ID` generated by PlayFab's matchmaker;
* a PlayFab `Title Secret Key`, which can be found in your title settings;
* a `list` of each player's `Master Player Account Id`.

These variables will be used to update the players' PlayFab `Player Data` with the information needed to connect to the server. Keep track of the keys you use to set these variables; in this sample, they are `"PLAYFAB_MATCH_ID"`, `"PLAYFAB_TITLE_SECRET"`, and `"PLAYFAB_MATCH_PLAYERS_ID_LIST"`.

{% hint style="info" %}
The simplest way to create this function is to deploy a new revision in the `Revisions (Legacy)` tab, adding the following code to it. Make sure to update the `Edgegap_AppName`, `Edgegap_AppVersion`, `Edgegap_ApiToken` and `PlayFab_TitleSecret` variables at the top with your own values first.

The app name and version will be used later on when containerizing the game server on Edgegap.

The list of player IDs is stored here as a single string, with each ID separated by a comma.
{% endhint %}

```javascript
var Edgegap_AppName = "APP_NAME";
var Edgegap_AppVersion = "APP_VERSION";
var Edgegap_ApiToken = "token XXXX"; //include the "token" keyword
var PlayFab_TitleSecret = "TITLE_SECRET";

handlers.StartEdgegapDeployment = function (args, context) {
  var QueueName = context.playStreamEvent.Payload.QueueName;
  var MatchId = context.playStreamEvent.Payload.MatchId;

  var GeoIpList = [];
  var PlayerIds = "";

  var MatchMember = multiplayer.GetMatch({
    QueueName: QueueName,
    MatchId: MatchId,
    EscapeObject: false,
    ReturnMemberAttributes: false,
  }).Members;

  MatchMember.forEach((Member) => {
    var Profile = entity.GetProfile({
      Entity: Member.Entity,
    });

    var Location = server.GetPlayerProfile({
      PlayFabId: Profile.Profile.Lineage.MasterPlayerAccountId,
      ProfileConstraints: {
        ShowLocations: true,
      },
    });

    GeoIpList.push({
      ip: "1.2.3.4",
      latitude: Location.PlayerProfile.Locations[0].Latitude,
      longitude: Location.PlayerProfile.Locations[0].Longitude,
    });

    PlayerIds += Profile.Profile.Lineage.MasterPlayerAccountId + ",";
  });

  PlayerIds = PlayerIds.slice(0, -1);

  var response = JSON.parse(
    http.request(
      "https://api.edgegap.com/v1/deploy",
      "post",
      JSON.stringify({
        app_name: Edgegap_AppName,
        version_name: Edgegap_AppVersion,
        geo_ip_list: [...GeoIpList],
        tags: [MatchId.slice(0, 20)],
        env_vars: [
          {
            key: "PLAYFAB_MATCH_ID",
            value: MatchId,
          },
          {
            key: "PLAYFAB_TITLE_SECRET",
            value: TitleSecret,
          },
          {
            key: "PLAYFAB_MATCH_PLAYERS_ID_LIST",
            value: PlayerIds,
          },
        ],
      }),
      "application/json",
      { authorization: Edgegap_ApiToken }
    )
  );

  log.info("response", response);
};
```

{% hint style="info" %}
This Cloud Script function creates an interface between the game client and the Edgegap API, which keeps your `Edgegap API token` and `PlayFab Title Secret` hidden from the players.
{% endhint %}

### Automation Rule

Still under `Automation`, navigate to the `Rules` tab. Create a new rule with the following settings:

* Set the `Event Type` to `playfab.matchmaking.match_found`;
* Add a new `Action`:
  * Set the `Type` to `Execute Entity Cloud Script`;
  * Set the `Cloud Script Function` to `StartEdgegapDeployment`.

<figure><img src="/files/xRBQkspMDlsp2wPbEjsP" alt=""><figcaption></figcaption></figure>

### Matchmaker Queue & Players

In the `Multiplayer` tab, under `Matchmaking`, make sure to have a queue available, otherwise create a new one. For quick testing, set it up to simply match any two players together.

You will need enough players registered to your Playfab title to satisfy the matchmaker's rules, so in this case only two will do. You can create new ones under the `Players` tab if needed. For this sample project, make sure to keep track of those players' `Custom ID` and `Title Player Account ID` for testing later.

### Editing The Game

#### Server

After opening the game sample in the Unity editor, we added a new empty gameObject to the `BattleScene` found under `Assets/SpaceEdge/Scenes`. We then attached a new C# script called `PlayerDataUpdateService` to it. It contains a callback function to be executed once the server is ready.

We also created a second script called `PlayfabRestApiService`, which contains the various requests that need to be sent to PlayFab's different APIs.

In `PlayerDataUpdateService.cs`, we retrieve a few of the environment variables injected in the deployment:

* Custom variables:
  * `PLAYFAB_MATCH_ID`;
  * `PLAYFAB_TITLE_SECRET`;
  * `PLAYFAB_MATCH_PLAYERS_ID_LIST`
* Default Edgegap variables:
  * `ARBITRIUM_PUBLIC_IP`;
  * `ARBITRIUM_PORTS_MAPPING`;

In `ARBITRIUM_PORTS_MAPPING`, we need to retrieve the specific external `port` value that will allow the client to connect to the server; in this sample, it is the one named `"Game Port"`.

Using the `PLAYFAB_TITLE_SECRET` as authentification, we send a request to the PlayFab Server API to update the `Player Data` of each corresponding ID in `PLAYFAB_MATCH_PLAYERS_ID_LIST` with the following key-value pair:

* Key: `PLAYFAB_MATCH_ID`;
* Value: `"{ARBITRIUM_PUBLIC_IP}:{port}"`;

#### PlayerDataUpdateService

```cs
public class PlayerDataUpdateService : NetworkBehaviour
{
    private PlayfabRestApiService _playfabService;

    [SerializeField] private string KeyMatchId = "PLAYFAB_MATCH_ID";
    [SerializeField] private string KeyTitleSecret = "PLAYFAB_TITLE_SECRET";
    [SerializeField] private string KeyPlayerIds = "PLAYFAB_MATCH_PLAYERS_ID_LIST";
    [SerializeField] private string ServerPortName = "Game Port";

    private string matchID;
    private string titleSecretKey;
    private string[] playerIdList;

    public override void OnStartServer()
    {
        _playfabService = FindObjectOfType<PlayfabRestApiService>();

        matchID = Environment.GetEnvironmentVariable(KeyMatchId);
        titleSecretKey = Environment.GetEnvironmentVariable(KeyTitleSecret);
        playerIdList = Environment.GetEnvironmentVariable(KeyPlayerIds)?.Split(',');

        string address = Environment.GetEnvironmentVariable("ARBITRIUM_PUBLIC_IP");
        string portsMapping = Environment.GetEnvironmentVariable("ARBITRIUM_PORTS_MAPPING");

        if (!string.IsNullOrEmpty(portsMapping))
        {
            int port = JSON.ParseString(portsMapping).GetJSON("ports").GetJSON(ServerPortName).GetInt("external");

            foreach (string playerId in playerIdList)
            {
                if (!string.IsNullOrEmpty(playerId))
                {
                    _playfabService.UpdatePlayerData(titleSecretKey, playerId, matchID, $"{address}:{port}");
                }
            }
        }
        else
        {
            Debug.Log("Port Mapping Unavailable");
        }
    }

    ...
}
```

#### PlayfabRestApiService

```cs
public class PlayfabRestApiService : MonoBehaviour
{
    ...

    [SerializeField] private string TitleID = "TITLE_ID";

    private const string TypeHeaderValue = "application/json";
    private readonly HttpClient _playfabHttpClient = new();
    private HttpResponseMessage _request;
    private StringContent _requestData;
    private string _response;

    ...

    public async void UpdatePlayerData(string secretKey, string playerID, string key, string value)
    {
        if (!_playfabHttpClient.DefaultRequestHeaders.Contains("X-SecretKey"))
        {
            _playfabHttpClient.DefaultRequestHeaders.Add("X-SecretKey", secretKey);
        }

        var dataProperty = new JSON();
        dataProperty.Add(key, value);

        var requestJson = new JSON();
        requestJson.Add("PlayFabId", playerID);
        requestJson.Add("Data", dataProperty);

        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);
        _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Server/UpdateUserData", _requestData);
        _response = await _request.Content.ReadAsStringAsync();

        if (!_request.IsSuccessStatusCode)
        {
            Debug.Log($"Could Not Update PlayerData of ID {playerID}, Error {(int)_request.StatusCode} With Message: \n{_response}");
        }
    }
}
```

#### Client

The client-side setup will be managed in the `MainMenu` scene found under `Assets/SpaceEdge/Scenes`. We created a new script called `MainMenuService` to manage the UI of the scene and display various messages to the player, and added new functions to `PlayfabRestApiService.cs`. We attached the `PlayfabRestApiService` script to a new gameObject in this scene as well.

The first thing the player needs to do is login with PlayFab, which will allow them to send additional requests to PlayFab's Client and Multiplayer APIs.

{% hint style="info" %}
We use the `LoginWithCustomID` endpoint for simplicity, however there are more suitable ways to implement the login depending on the situation (e.g.: with mobile games).
{% endhint %}

One the player has logged in and both the `X-Authorization` and `X-EntityToken` headers have been properly set up for later requests, they can create a new matchmaking ticket using the `Start Game` UI button. This will send a request to PlayFab's multiplayer API and store the `ticket ID` for later use.

Afterwards, the game client will periodically check the ticket's status every few seconds until a match has been found or the ticket gets cancelled. Once a match is found, the `match ID` gets stored as a variable.

The client will then periodically send requests to PlayFab's Client API in order to get the player's Title `Player Data`, using the `match ID` as the key for the specific data that needs to be retrieved. Once the data is found, it sets the server's `address` and external `port` value in the netcode's `transport` component, then connects to the server.

Once the client gets connected and started properly, a request gets sent to remove the connection information from that player's PlayFab `Player Data` thanks to a callback function.

{% hint style="info" %}
It is important to make sure these key-value pairs get deleted, preferably before the deployment gets terminated. This is to avoid cluttering the `Player Data` with too many unnecessary entries.
{% endhint %}

#### MainMenuService

```cs
public class MainMenuService : MonoBehaviour
{
    [SerializeField] private Button startGameButton;
    [SerializeField] private TMP_InputField playerNameInput;
    [SerializeField] private TMP_Text serverStatus;

    private PlayfabRestApiService _playfabService;

    private void Start()
    {
        if (!InstanceFinder.ServerManager.GetStartOnHeadless())
        {
            _playfabService = FindObjectOfType<PlayfabRestApiService>();
            _playfabService.OnStatusUpdate += ServerStatusUpdate;
            _playfabService.OnUpdateButton += UpdateButtonState;

            startGameButton.onClick.AddListener(StartGame);
            playerNameInput.onValueChanged.AddListener(NameChanged);
            if (PlayerPrefs.HasKey("PlayerName"))
                playerNameInput.text = PlayerPrefs.GetString("PlayerName");

            _playfabService.LoginPlayer();
        }
    }

    private void OnDestroy()
    {
        if (!InstanceFinder.ServerManager.GetStartOnHeadless())
        {
            _playfabService.OnStatusUpdate -= ServerStatusUpdate;
            _playfabService.OnUpdateButton -= UpdateButtonState;
        }
    }

    private void StartGame()
    {
        startGameButton.interactable = false;
        serverStatus.text = "";
        _playfabService.CreateTicket();
    }

    private void ServerStatusUpdate(string status, bool isError)
    {
        serverStatus.text += "\n" + status;
        serverStatus.color = isError ? Color.red : Color.green;
        Debug.Log(status);
    }

    private void UpdateButtonState(bool state)
    {
        startGameButton.interactable = state;
        serverStatus.text = "";
    }

    private void NameChanged(string text) => PlayerPrefs.SetString("PlayerName", text);
}
```

#### PlayfabRestApiService

```cs
public class PlayfabRestApiService : MonoBehaviour
{
    [SerializeField] private string PlayerCustomID = "PLAYER_CUSTOM_ID";
    [SerializeField] private string PlayerTitleID = "PLAYER_TITLE_ID";
    [SerializeField] private string TitleID = "TITLE_ID";
    [SerializeField] private string QueueName = "QUEUE_NAME";

    private const string TypeHeaderValue = "application/json";
    private readonly HttpClient _playfabHttpClient = new();
    private HttpResponseMessage _request;
    private StringContent _requestData;
    private string _response;
    private string _ticketID = "";
    private string _matchID = "";

    public Action<string, bool> OnStatusUpdate;
    public Action<bool> OnUpdateButton;

    private static PlayfabRestApiService _instance = null;

    private void Awake()
    {
        _playfabHttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(TypeHeaderValue));

        if (_instance is null)
        {
            DontDestroyOnLoad(this);
            _instance = this;
        }
        else
        {
            Destroy(this);
        }
    }

    public async void LoginPlayer()
    {
        var requestJson = new JSON();
        requestJson.Add("CustomID", PlayerCustomID);
        requestJson.Add("CreateAccount", false);
        requestJson.Add("TitleID", TitleID);
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Client/LoginWithCustomID", _requestData);
        _response = await _request.Content.ReadAsStringAsync();

        if (_request.IsSuccessStatusCode)
        {
            var responseJson = JSON.ParseString(_response);

            string token = responseJson.GetJSON("data").GetJSON("EntityToken").GetString("EntityToken");
            _playfabHttpClient.DefaultRequestHeaders.Add("X-EntityToken", token);

            string sessionTicket = responseJson.GetJSON("data").GetString("SessionTicket");
            _playfabHttpClient.DefaultRequestHeaders.Add("X-Authorization", sessionTicket);

            OnUpdateButton?.Invoke(true);
            OnStatusUpdate?.Invoke("Login Successful", false);
        }
        else
        {
            OnStatusUpdate?.Invoke($"Unable To Login Player, Error {(int)_request.StatusCode} With Message: \n{_response}", true);
        }
    }

    public async void CreateTicket()
    {
        var requestJson = new JSON();

        var entityProperty = new JSON();
        entityProperty.Add("Id", PlayerTitleID);
        entityProperty.Add("Type", "title_player_account");

        var creatorProperty = new JSON();
        creatorProperty.Add("Entity", entityProperty);

        requestJson.Add("Creator", creatorProperty);
        requestJson.Add("GiveUpAfterSeconds", 90);
        requestJson.Add("QueueName", QueueName);
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Match/CreateMatchmakingTicket", _requestData);
        _response = await _request.Content.ReadAsStringAsync();

        if (_request.IsSuccessStatusCode)
        {
            var responseJson = JSON.ParseString(_response);
            _ticketID = responseJson.GetJSON("data").GetString("TicketId");

            OnStatusUpdate?.Invoke($"Ticket Created, ID #{_ticketID}", false);

            GetMatchIdAttempt();
        }
        else
        {
            OnUpdateButton?.Invoke(true);
            OnStatusUpdate?.Invoke($"Unable To Create Ticket, Error {(int)_request.StatusCode} With Message: \n{_response}", true);
        }
    }

    private async void GetMatchIdAttempt()
    {
        bool isMatchFound = false;
        var requestJson = new JSON();
        requestJson.Add("EscapeObject", true);
        requestJson.Add("QueueName", QueueName);
        requestJson.Add("TicketId", _ticketID);
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        while (!isMatchFound)
        {
            _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Match/GetMatchmakingTicket", _requestData);
            _response = await _request.Content.ReadAsStringAsync();

            if (_request.IsSuccessStatusCode)
            {
                var responseJson = JSON.ParseString(_response);
                string ticketStatus = responseJson.GetJSON("data").GetString("Status");
                isMatchFound = ticketStatus == "Matched";

                if (isMatchFound)
                {
                    _matchID = responseJson.GetJSON("data").GetString("MatchId");
                    OnStatusUpdate?.Invoke($"Ticket Has Been Matched, ID #{_matchID}", false);

                    StartConnectionAttempt();
                }
                else if (ticketStatus == "Canceled")
                {
                    isMatchFound = true;
                    string reason = responseJson.GetJSON("data").GetString("CancellationReason");

                    OnUpdateButton?.Invoke(true);
                    OnStatusUpdate?.Invoke($"Ticket Has Been Cancelled Due To {reason}", false);
                }
                else
                {
                    OnStatusUpdate?.Invoke("Match Not Found, Retrying In 10 Seconds...", false);
                    await Task.Delay(10000);
                }

            }
            else
            {
                isMatchFound = true;
                OnUpdateButton?.Invoke(true);
                OnStatusUpdate?.Invoke($"Could Not Get Ticket Info, Error {(int)_request.StatusCode} With Message: \n{_response}", true);
            }
        }
    }

    private async void StartConnectionAttempt()
    {
        bool gotConnectionInfo = false;

        var requestJson = new JSON();
        requestJson.Add("Keys", new List<string> { _matchID });

        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        while (!gotConnectionInfo)
        {
            _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Client/GetUserData", _requestData);
            _response = await _request.Content.ReadAsStringAsync();

            if (_request.IsSuccessStatusCode)
            {
                var responseJson = JSON.ParseString(_response);
                var dataObject = responseJson.GetJSON("data").GetJSON("Data");

                if (dataObject.Count > 0)
                {
                    gotConnectionInfo = true;
                    string connectionInfo = dataObject.GetJSON(_matchID).GetString("Value");
                    string serverAddress = connectionInfo.Split(':')[0];

                    if (ushort.TryParse(connectionInfo.Split(':')[1], out ushort serverPort))
                    {
                        InstanceFinder.TransportManager.Transport.SetPort(serverPort);
                        InstanceFinder.TransportManager.Transport.SetClientAddress(serverAddress);
                        Debug.Log($"Connecting To Server Using Port {serverPort} And IP {serverAddress}");
                        InstanceFinder.ClientManager.StartConnection();
                    }
                    else
                    {
                        OnUpdateButton?.Invoke(true);
                        OnStatusUpdate?.Invoke($"Error While Parsing Server Port Value", true);
                    }
                }
                else
                {
                    OnStatusUpdate?.Invoke("No Connection Data Found, Retrying In 10 Seconds...", false);
                    await Task.Delay(10000);
                }
            }
            else
            {
                gotConnectionInfo = true;
                OnUpdateButton?.Invoke(true);
                OnStatusUpdate?.Invoke($"Could Not Get Player Data, Error {(int)_request.StatusCode} With Message: \n{_response}", true);
            }
        }
    }

    public async void RemovePlayerData()
    {
        var requestJson = new JSON();
        requestJson.Add("KeysToRemove", new string[] { _matchID });

        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);
        _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Client/UpdateUserData", _requestData);
        _response = await _request.Content.ReadAsStringAsync();

        if (!_request.IsSuccessStatusCode)
        {
            Debug.Log($"Could Not Update PlayerData, Error {(int)_request.StatusCode} With Message: \n{_response}");
        }
    }

    ...
}
```

#### PlayerDataUpdateService

```cs
public class PlayerDataUpdateService : NetworkBehaviour
{
    private PlayfabRestApiService _playfabService;

    ...

    public override void OnStartClient()
    {
        _playfabService = FindObjectOfType<PlayfabRestApiService>();
        _playfabService.RemovePlayerData();
    }
}
```

### Building Game Server & Containerizing

Before containerizing the server build, make sure to enable the `Start On Headless` option in the `NetworkManager` gameObject.

Open the Edgegap plugin with the `Tools/Edgegap Hosting` toolbar menu. Verify your `Edgegap API Token` and either create or load an application for the game. Make sure the `port` value matches that of the Tugboat component of the `NetworkManager` gameObject. Select the `UDP` protocol, then enter a `New Version Tag`. Make sure that both the app name and version match the values that were used when setting up the Cloud Script.

Once this is properly set up, click on `Build and Push`, which will automatically containerize your game server and create a new application version on the Edgegap dashboard after a short waiting period.

{% hint style="info" %}
When creating the app version, the plugin will name the port \`"Game Port"\` by default.

If you choose to manually containerize and create your app version instead of using the plugin, make sure to use that name when selecting which port to use.
{% endhint %}

### Testing

Make sure to first disable the `Start On Headless` option in the `NetworkManager` gameObject. Set the `PlayfabRestApiService` script with the `Custom ID` and `Title ID` of your first player, along with your game's `Title ID` and `Queue Name`. In the `Build Settings`, create a new client build with these settings.

Once the build is complete, change the `Custom ID` and `Title ID` values to that of your second player. You can create a separate client build if you want, although the Unity editor in play mode should work just as well.

If both clients login properly on launch, click on both instance's `Start Game` button. After a short while, they will be matched through PlayFab's matchmaker, then connect to the same deployment on Edgegap once the server has finished setting up.


# Endpoint Storage

Endpoint Storage allows you to register an S3 Bucket in our system. When you have an Endpoint Storage set in our environment, you will be able to store some data from your deployment like the container's log.

{% hint style="info" %}
If you have already set up an Endpoint Storage and want to know how to save your container logs, please refer to the next section [How to Save Deployment Container Logs](/docs/endpoint-storage/save-container-logs).
{% endhint %}

### How to Setup an Endpoint Storage

It is pretty simple to register your S3 Bucket if you follow these quick steps.

To see and set up a new Endpoint Storage, you need to go to the left menu and click on the `Log Storage` menu item.

<figure><img src="/files/qAPGg9cd3xWQ6SY6Ddra" alt=""><figcaption></figcaption></figure>

The first time you land on this page, you will not see any Endpoint storage in the list. Click on the create button in the upper right corner. You will be invited to complete this form. All fields are mandatory.

<figure><img src="/files/jJBE7jh7P4WOuHzER2nI" alt=""><figcaption></figcaption></figure>

**Name**

The name you want to give to your Endpoint Storage in our platform. It must be unique since you will be able to use it in some API calls.

**Access Key ID**

Pretty straightforward, the Access Key ID from your S3 Bucket.

**Secret Access Key**

The Secret Access Key from your S3 Bucket. This field will be encrypted and you will not be able to modify it once you saved your Endpoint Storage. Don't worry if you put the wrong Access Key, you will be able to completely delete the Endpoint Storage and create a new one.

**Endpoint URL**

Your S3 Bucket URL. Ensure that you include the full link (https\://, http\://, etc). You will be able to test it further in the process.

**Linode**

{% hint style="warning" %}
For Linode, the endpoint URL should NOT include the bucket name. For example: If your S3 bucket URL is <https://my-bucket.some-region.provider-name.com/>, the endpoint URL you should enter is <https://some-region.provider-name.com/>.
{% endhint %}

**AWS**

{% hint style="warning" %}
For AWS, the endpoint URL MUST include the bucket name. For example: If your S3 bucket name is my-test and your bucket URL is <https://my-test.s3.us-east.amazonaws.com>, the endpoint URL you should enter is <https://my-test.s3.us-east.amazonaws.com>.
{% endhint %}

**Bucket**

The name of your Bucket. Make sure to put the right name associated with the credential. Otherwise, we will not be able to access it correctly.

### Manage your Endpoint Storage

You can access all your Endpoint Storage available with the upper right menu. You can remove and test your Endpoint Storage. We are not deleting your S3 Bucket, we only remove the connection information from our system. If you want to update an Endpoint Storage, you must delete it and create a new one.

When you press "Test" button, it will upload a file named "connection\_test" under the "edgegap" directory in your S3 Bucket. You will have an alert box to notify you if it worked or not. If not, you might have something wrong in your Endpoint Storage configuration.

<figure><img src="/files/5DAsTCmBBxvGSqZbQyN2" alt=""><figcaption></figcaption></figure>


# How to Save Logs

When you create a deployment, your container generates logs. You can save these logs to an S3 Bucket of your choice once you terminate your deployment. By setting up an Endpoint Storage, you will be able to retrieve these logs at any time, even after the deployment has been terminated.

### Link your Endpoint Storage with your Application Version

To accomplish this, you will need to link your App Version to your Endpoint Storage. Navigate to the App Version details page, and look for the **Container Logs Storage** section. Enable the switch box and select your Endpoint Storage in the select box.

<figure><img src="/files/gMsyqGmCdHssJpscDymw" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
If you enable container logs storage without selecting a valid Endpoint Storage, you will not be able to retrieve your logs. To ensure you have a valid Endpoint Storage, please refer to the [setup documentation](/docs/endpoint-storage).
{% endhint %}

That's it! Every deployment with this application version will now store the container logs in your S3 Bucket.

#### Default Directory Path

The path in your S3 Bucket will look like this.

* edgegap/arbitrium
* The first part of your client email, most S3 browsers don't like "@"
* The name of your Endpoint Storage
* app / app version / container\_log
* The date YYYY-MM-DD

<figure><img src="/files/M7V3YON0CcGrXxPBI4Ks" alt=""><figcaption></figcaption></figure>

You can create multiple Endpoint Storage with the same S3 Bucket but with a different name. That way, you could have various directories for numerous environments if you wanted.

<figure><img src="/files/dxEqaDAGGBrzDNtM6O7P" alt=""><figcaption></figcaption></figure>

#### Additional Use Cases

{% hint style="success" %}
These use cases can be useful when testing, and you don't want all of your deployments logs sent to storage.
{% endhint %}

**Save container logs with your Deployment request**

When you create a deployment, you can specify if you want to store your container logs with your Deployment request. This independent of the application version settings, meaning that it is not necessary to have previously set up the Endpoint Storage with your application version, but simply to have a functional Endpoint Storage.

You can do this by adding the following JSON to your Deployment request. The `endpoint_storage` is the name of your Endpoint Storage.

```json
{
  "container_log_storage": {
    "enabled": true,
    "endpoint_storage": "demo-bucket"
  }
}
```

{% hint style="info" %}
If the `endpoint_storage` is not provided, we'll attempt to use the app version's endpoint storage. If no endpoint storage is found, container logs won't be stored.
{% endhint %}

**Save container logs with your Stop request**

Another convenient way to use it is when you terminate your deployment. If you have reason to believe something went wrong inside your container, you can request to save the container logs. This option is available with the Deployment stop and Self stop routes.

You can do it by adding the query parameter `container_log_storage` to your request. The param `container_log_storage` is the name of your Endpoint Storage. You can also put `true` to use the app version's endpoint storage.

You can check our [API Documentation](/docs/api) for more details on the deployment and stop request parameters.


# Upload File to Deployment

{% hint style="info" %}
This feature is currently only available through the [API](/docs/api).
{% endhint %}

It is possible to upload files from your S3 bucket to your deployment. Since the files are only downloaded when a deployment is created, you can modify the files in your bucket and see the changes in the next deployment you create.

### Pull profiles

Pull profiles define what files or folders should be downloaded from your S3 bucket and where to save them in your deployment.

Before creating a pull profile, ensure you have at least one [Endpoint Storage](/docs/endpoint-storage) configured.

You will then want to create a pull profile within this bucket with the following information:

* The **name** of the pull profile: This will be used to associate the pull profile to your app versions later on, so it should be unique and descriptive.
* The **source** path: The path to the file or folder in your S3 bucket. It should start with a slash, and its root is the root of your bucket.
* The **source\_type**: This can be either "file" or "folder" and must be set correctly for the download to succeed.
* The **destination** path: The path in your deployment where the file or folder will be saved. It should start with a slash, and its root is the root of your container image.

{% hint style="warning" %}
Make sure to avoid protected destinations, such as `/etc/`, as this will prevent the files from being copied to your deployment and will make your deployment fail. Make sure a normal user can write to the destination folder.
{% endhint %}

For example, if you wanted to copy the file `some-file.txt`, from your bucket, to the `custom-folder` folder in your deployment, with both file structures as described below:

```
s3://my-bucket/
├─ some-folder/
│  ├─ some-file.txt
├─ some-other-folder/
│  ├─ some-other-file.jpg
```

```
/
├─ app/
│  ├─ entrypoint.sh
│  ├─ custom-folder/

```

You would create your pull profile with the following information:

```json
{
  "name": "some-file-pull-profile",
  "source": "/some-folder/some-file.txt",
  "source_type": "file",
  "destination": "/app/custom-folder/"
}
```

For more details, [see the API documentation](/docs/api).

### Linking a pull profile to an app version

After creating a pull profile, you must link an app version to it, so the files are downloaded when a deployment is created. That way, you can use multiple pull profiles for a single app version and reuse pull profiles between app versions.

[See the API documentation](/docs/api)

### Limitations

The total size of the files downloaded to the deployment cannot exceed 5 MB.

If this limit is reached, you will be notified at deployment time so that you can promptly resolve the issue.


# DDoS Protection

Learn how edge computing prevents [Denial of Service](https://www.cloudflare.com/learning/ddos/what-is-a-ddos-attack/) attacks on your game servers.

**Server outages can easily ruin player experience and seriously harm your game's and studio's reputation.** Aside from dev team accidents, provider outages, or localized ISP issues, malicious Denial of Service attacks are amongst the top threats that every studio has to keep in mind.

Malicious parties attacking your infrastructure often try to flood your network with requests to a point of saturation where legitimate user's network packets can no longer reach your services, or are severely impacted in response times and availability.

{% hint style="success" %}
Edgegap goes beyond traditional measures and **brings protection to the edge instance infrastructure**.
{% endhint %}

To mitigate and recover from DDoS attacks automatically:

1. We detect all the usual attack vectors early - TCP SYN floods, UDP floods, and HTTP floods.
   1. Metrics include Port Connection Rate, Source Connection Rate, Total Connection Rate.
2. We declare the machine Under DoS and steer [Deployments](/learn/orchestration/deployments) away from the targeted site.
3. To reduce impact on running games we perform upstream scrubbing, and inject routing/firewall policies locally at the edge, adding a null route redirect for suspicious incoming traffic.
   1. Due to traffic filtering at the edge, the wasted upstream bandwidth from server is minimal.
4. Once players conclude their match on the targeted site and disconnect, we recycle the site's IP address to ensure subsequent matches will not be targeted by the same attacker.
   1. New matches will use a fresh server IP address and port assigned randomly.

{% hint style="info" %}
If you have particular questions or concerns don't hesitate to contact us over our [Community Discord](https://discord.gg/NgCnkHbsGp).
{% endhint %}


# Glossary

### A

#### Active User

* **Daily Active Users (DAU)**: a metric that measures the number of unique users who engage with Edgegap within a single day.
* **Monthly Active Users (MAU)**: a metric that measures the number of unique users who engage with Edgegap within a single month.

#### Application

An application serves as the foundational unit for deployments. While it contains only a minimal set of options, it acts as a container where versions can be created and managed. The majority of configurations and settings are not directly associated with the application but are defined in its versions.

#### Application Version

An application version functions as the blueprint for your deployments, dictating their behavior and characteristics. It offers a high degree of flexibility, allowing you to specify various settings and configurations. Any changes made to a version are automatically reflected in the deployments that use that version.

#### API Gateway

A server that acts as an API front-end, receiving API requests, enforcing throttling and security policies, passing requests to the back-end service, and then passing the response back to the requester.

### B

#### Bare-metal server

A type of server that provides dedicated and unshared physical resources, including CPU, RAM, and storage, to a single tenant.

### C

#### Client

A software application or device that requests services or resources from a server. In a client-server architecture, the client initiates communication. Relates to [Host](#host).

#### Client Agent

A software program that acts as a mediator between the gaming client and the edge computing infrastructure. It is responsible for collecting data from the client and transmitting it to the edge server for processing. The purpose of the Client Agent is to improve the performance of the gaming experience by offloading some of the computational load from the client device to the edge server. This allows for faster response times and a more immersive gaming experience for the end-user.

#### Cluster

A group of interconnected servers that work together as a single system to provide high availability, load balancing, and/or parallel processing.

#### Colocation center

A data center that provides a physical location for gaming companies to store their gaming infrastructure, such as servers and network equipment, close to the end-users to ensure low latency and high performance. This type of data center enables gaming companies to efficiently manage their gaming infrastructure and ensure optimal user experience for their players.

#### Containerization

A lightweight form of virtualization that helps to isolate applications from each other and improve their deployment and maintainability.

#### Containers

A technology used in edge computing for gaming that allows for the efficient and isolated deployment of gaming applications and services. They provide a standardized and lightweight solution for deploying and scaling gaming applications, while also ensuring compatibility and consistency in their execution environment. This helps to reduce downtime and improve performance for gamers, ultimately leading to a better gaming experience.

### D

#### Deployment

A deployment is an operational instance of a specific version of your application, encapsulated within an active container. Upon providing a list of IPs or a location, you receive a URL for connecting your players or users. Deployments are designed to be temporary; they are terminated upon request when no longer needed. This model is particularly effective for time-boxed game genres like FPS, fighting games, and MOBA, where the player roster remains constant throughout the server's lifecycle. For more dynamic player interactions, such as MMOs, a different approach like sessions is recommended. Real-time telemetry is collected for each deployment to optimize location and enhance user experience.

#### Distributed Relay Manager

The Distributed Relay Manager is a specialized service within Edgegap's distributed cloud network, designed to optimize relay selection for players. Utilizing Edgegap's patented real-time telemetry decision technology, it ensures that players are connected to the most suitable relay from among hundreds of available locations. The service aims to simplify the complex task of relay implementation and management, allowing for easier integration into games or applications without requiring extensive technical know-how.

#### Docker

A platform that uses containerization technology to package and run applications and their dependencies in isolated environments, ensuring consistency across multiple systems.

#### Dedicated Server

A server that is reserved for the exclusive use of a single tenant or application. Unlike shared servers, all the resources of a dedicated server are available to one user.

### E

#### Edge Computing

A distributed computing paradigm that brings computation and data storage closer to the location where it is needed, to improve response times and save bandwidth.

#### Egress

“the act of going out,” “the right to go out,” or “the means of going out.”

#### Elastic Cloud Computing

ECC is a technology that enables gaming companies to scale their infrastructure resources on-demand, providing high levels of performance and security in the process. ECC provides a comprehensive platform for game developers, delivering centralized storage and computing services, real-time data processing, and dynamic resource allocation for high-end gaming applications.

#### Endpoint

A specific URL where a web service or API can be accessed. Endpoints are the points of interaction between different software entities.

#### Endpoint Storage

A storage solution that uses Amazon S3 (Simple Storage Service) as a remote repository for storing data. This is often used for backup, archiving, or data sharing.

#### Environment Variables

Key-value pairs that are used to configure settings for applications. They can be set and accessed within the operating system or directly within a container.

### F

#### Fleet

A fleet is a management tool designed to automate the creation of session-based deployments. It operates based on configurable settings to ensure optimal server capacity. Fleets can also automatically terminate deployments based on more advanced configurations.

#### Fleet Functions

1. Automatically creates new deployments when existing ones reach a specified capacity threshold.
2. Ensures a minimum number of ready-to-use deployments.
3. Allows setting a maximum limit on simultaneous deployments.
4. Operates within a specific region to control server distribution.

#### Fleet Management

Manage a large number of servers in real-time for multiplayer gaming. It helps the technical product manager monitor and control the resources of these servers, ensuring optimal performance and minimizing downtime.

### H

#### Host

A computer or server that provides resources, services, or data to other computers (known as clients) over a network. Relates to [Client](#client).

#### Hosting Relay

A platform for hosting game servers closer to end-users. This improves the speed and reliability of game play by reducing latency and reducing the load on central servers.

### I

#### Ingress

“the act of entering,” “the right of entering,” or “the means of entering.”

### J

#### Jitter

The variability in the latency of data transmission. It refers to the variations in the delay of data packets that are transmitted over a network and affects the performance of real-time gaming. Jitter can impact the smoothness of the gaming experience and cause lag, delay, and other disruptions.

### K

#### Kubernetes

An open-source platform designed to automate deploying, scaling, and operating application containers.

### L

#### Latency

The amount of time it takes for data to be transmitted from a device to a server and back.

#### Log (Container Log)

A record of events and transactions that occur within a container. These logs are crucial for debugging and monitoring the performance of containerized applications.

### N

#### Netcode

A critical component of the gaming infrastructure that must be designed and optimized to meet the demands of modern gaming, including real-time interactivity, high-resolution graphics, and support for large numbers of simultaneous players. This requires a deep understanding of network protocols, data compression, and other technologies that can be used to optimize gaming performance, and an ability to collaborate with network engineers and other stakeholders to implement effective solutions.

#### Network Status

The quality and availability of the network connection between the gaming device and the edge computing server. This is important because it affects the player's experience with the game. The network status can impact the game's performance such as speed, responsiveness, and reliability. If the network connection is unreliable, players may experience lag, slowdowns, and even disconnections. A good network status is crucial for a seamless gaming experience and should be monitored and optimized for the best performance.

### M

#### Multi-Access Edge Computing (MEC)

A network architecture concept that enables cloud computing capabilities and an IT service environment at the edge of the cellular network.

### P

#### PaaS

A cloud-based platform that provides gaming developers with the necessary infrastructure and tools to build, deploy, and manage their gaming applications at the edge of the network. The Edgegap platform leverages the power of edge computing to deliver low latency and high-speed gaming experiences to users. It also offers scalability, security, and reliability, making it an ideal solution for gaming developers looking to enhance their gaming applications.

#### Packet Loss

The phenomenon of data packets being lost or discarded while being transmitted between different computing devices in a network. Packet loss is caused by various factors including:

1. Overloaded Network: A network can become congested due to high traffic, resulting in data packets being discarded.
2. Network Latency: Delays in the network can cause packets to arrive too late, resulting in their being discarded.
3. Data Corruption: Packets can become corrupted during transmission, making them unreadable and causing them to be discarded.
4. Technical Issues: Issues such as hardware failure, outdated hardware, or software bugs can cause packets to be lost.
5. Distance: Packets traveling long distances are more likely to be lost due to issues such as signal interference.
6. Outdated Protocols: Using outdated protocols can cause packets to be lost, as modern networks are optimized for newer technologies.
7. Routing Errors: Packets can be lost due to incorrect routing, causing them to be sent to the wrong destination."

#### Persistent Storage

The capability of storing game data and information on the edge device itself, instead of relying on the cloud or central servers. This ensures that the game data and progress is not lost, even when there is a temporary loss of connectivity or network disruption. This is essential for providing seamless gaming experiences, particularly for multi-player games where continuous connectivity is crucial.

#### Policy Engine

Managing and regulating the flow of game data and processing, as it directly impacts the overall performance and user experience of the gaming application.

#### Pulse Cloud

A cloud-based platform that provides edge computing solutions for gaming applications. This technology enables real-time processing of gaming data, improving performance, and reducing latency for end-users. Can play a crucial role in providing a seamless gaming experience by processing critical data at the edge of the network, closer to the user. This results in a reduced reliance on central servers, reducing server congestion and improving performance, making it an ideal solution for high-performance gaming applications.

### R

#### Rate Limit

A rate limit is the maximum number of API requests allowed (usually per second) either for a specific HTTP method, a specific HTTP resource, a defined group of HTTP methods or resources with closely related usage patterns, an entire product (such as [Matchmaking](/learn/matchmaking) or [Server Browser](/learn/server-browser)), or a global rate limit for an entire organization.

Rate limits help:

1. Prevent abuse, resource starvation, and Denial of Service attacks due to malicious attackers.
2. Distribute available compute and networking capacity across users or API resources fairly.

Once the rate limit for the given time period (usually 1 second) is reached, the user cannot complete any more requests until the next time period begins (next second). Rate limit restrictions are best handled on client side by detecting HTTP response `429 Too Many Requests`  and retrying requests several times after waiting for an [exponentially increasing jittered backoff period](https://dev.to/biomousavi/understanding-jitter-backoff-a-beginners-guide-2gc).

#### Relay

A relay is a network node that acts as an intermediary for routing data packets between the client and server. Edgegap Relays are strategically located across a distributed cloud network to optimize data transmission. They are selected based on real-time telemetry decision technology to ensure the best possible user experience, particularly in gaming scenarios.

#### Relay System

Technology that allows the distribution of computing tasks between different devices and servers in a network, reducing the load on a single device and ensuring smooth and fast performance. The Relay System acts as a bridge between the edge devices and the cloud servers, enabling real-time data exchange and ensuring low latency and high-quality gaming experience for the end-user.

#### Repository

A centralized storage location where code, data, or artifacts are stored and managed, often under version control.

### S

#### Server Image

A snapshot of a server's configuration, including the operating system, installed software, and settings, which can be used to create new server instances.

#### Session

A session functions as a sub-layer within a deployment, specifically designed to manage more dynamic elements like sockets or available space. Sessions are particularly useful in the following scenarios:

1. Managing the hop-in and hop-out of individual players or groups within a deployment, and
2. Facilitating multiple matches or games within a single deployment. Further details on sessions will be discussed separately

#### Session - Match Type

In a match-type session, each session occupies one socket space, regardless of the number of players in that session. For example, three sessions with ten players each would occupy only three out of five available sockets. Deleting a session reduces the used socket count accordingly.

#### Session - Seat Type

In a seat-type session, each player occupies one socket space within a deployment. For instance, if a deployment has ten sockets, a solo player and a party of four would occupy five sockets. Deleting a session frees up the corresponding number of sockets.

#### Serverless FaaS

Gaming applications and services can run without the need for a dedicated server, and can instead be deployed on a cloud platform like Edgegap that offers serverless computing capabilities. This allows for faster and more efficient processing of gaming data, and also eliminates the need for investment in hardware, maintenance, and scaling.

#### Sidecar

Enables games to run on a remote server instead of on a local device, which can reduce lag and improve performance.

#### Single Point of Presence

A single, centralized location in a network where all traffic is routed for specific services or applications. This is often used to simplify network architecture and improve performance.

### T

#### Telemetry

The collection and analysis of real-time data from the gaming environment, devices and players. This data is used to provide valuable insights into the performance and behavior of the game and its players.

#### Tick rate

The number of times per second that a game's server updates the game's state, including player positions, actions, and other events.

While higher tick rate can provide more responsive gameplay to players, it also increases the game server's CPU requirements in order to pack and unpack more messages in the same amount of time.

Ballpark tick rates by game category:

* cooperative and social games (latency tolerant) - 30 Hertz (30 updates per second),
* competitive and PvP games - 60 Hertz,
* tournaments, VR & XR games - 128 Hertz.

### U

#### Usage Parameter Control

The ability to monitor and manage the utilization of computing resources at the edge devices, specifically for gaming purposes. This includes setting and enforcing limits on parameters such as memory, storage, and processing power to optimize the gaming experience and prevent overloading the edge devices.


# SLA Terms

At Edgegap, we support our clients throughout the lifecycle of their games.

## Here is how we will support your game:

* Dedicated Slack/Discord channel support.
* ServiceDesk Tickets support.
* Active monitoring.
* A support representative will be available via email or phone 24/7 for `Priority1` incidents.
* When submitting an error, the client may indicate the severity of the initial response in accordance with the severity guidelines.
* The availability of the service for any given month will be 99.9%.
* Edgegap shall provide notice 72 hours in advance of any scheduled maintenance where downtime is reasonably possible or expected.

| Severity Level | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Initial Response & Acknowledgment | Targeted Best Effort Fix Date or Workaround                                                             | Continuous Effort & Updates                                            |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| 1              | Edgegap service or component thereof is completely inaccessible or the majority of its functionality is unusable and inaccessible.                                                                                                                                                                                                                                                                                                                                                                     | 15 minutes                        | 1 hour for a workaround4 hours for complete resolution of error                                         | Edgegap will commit full-time resources 24/7; updates provided hourly. |
| 2              | One or more key features/components of the Edgegap service are unusable.Unable to perform basic Edgegap service functions or there is a significant degradation in such basic functions or features.                                                                                                                                                                                                                                                                                                   | 15 minutes                        | 2 hours for a workaround1 calendar day for complete resolution of error                                 | Edgegap will commit full-time resources 24/7; updates provided hourly. |
| 3              | An Edgegap service feature/component is not operating in accordance with the documentation which does not fall into an error severity Level 2.Enhancements or defects in the Edgegap service that are targeted for updates, but do not result in the significant loss or degradation in functionality in a major Edgegap service feature/component. Functionality is noticeably impaired or degraded but clients use of the Edgegap service can continue; and in each case, a workaround is available. | 2 hours                           | Next new functionality of Edgegap service or prioritized to be fixed in a mutually agreeable timeframe. | Updates provided weekly.                                               |
| 4              | All enhancement and new functionality requests. Client requires information or assistance on capabilities, installation, or configuration of the Edgegap service.                                                                                                                                                                                                                                                                                                                                      | 1 business day                    | At Edgegap’s reasonable discretion                                                                      | N/A                                                                    |


