Pelican Panel (and its predecessor Pterodactyl) launches game servers inside generic Docker images, then injects egg-defined startup variables as container environment variables. This is a different deployment model from the official Docker zip we ship, so a few questions come up regularly. The most common one: what env vars does the GameVox binary read, and is anything else required to make Pelican work?

Short answer: nothing else is required. The binary reads its configuration from environment variables at startup. As long as Pelican passes them through (which is what egg startup variables do), the server picks them up. No custom Docker image, no custom entrypoint, no container modifications.

Environment variables

The self-hosted binary reads three networking-related variables at startup:

VariableDefaultPurpose
TCP_PORT8088WebSocket (chat, signaling) and HTTPS (file downloads)
UDP_PORT7070Voice traffic (WebRTC/UDP)
PUBLIC_IP(auto)Override egress IP autodetection. Only set this if voice fails and you’ve confirmed the wrong address is being advertised.

There are also a few path-related variables you usually don’t need to touch on Pelican, since the panel manages the working directory for you:

VariableDefaultPurpose
DATA_DIR./data next to the binaryWhere the SQLite databases, logs, and uploads live
FILES_DIR${DATA_DIR}/filesOverride the uploads directory only
BACKUPS_DIR${DATA_DIR}/backupsOverride the backup directory only

That’s the entire surface area for runtime configuration. Everything else (server identity, owner, JWT secret, cloud coordination endpoint) is baked into the binary at download time.

Configuring it in Pelican

Pelican eggs define startup variables in the panel UI. Each variable has a display name (shown to the operator) and an env_variable field (the actual name passed into the container’s environment). The env_variable field is the one that matters to GameVox.

For each port you want to override, add a startup variable with:

  • Env variable: TCP_PORT (case-sensitive, exactly as written)
  • Default value: 8088
  • User-editable / viewable: as you prefer

Then the same again for UDP_PORT (default 7070). If you’re behind a NAT setup where egress autodetection picks the wrong address, add PUBLIC_IP as a third variable.

A couple of things to double-check on the Pelican side:

  1. Allocations must match. Pelican needs an allocation for the TCP port and an allocation (or additional allocation) for the UDP port, and both have to be reachable from the internet. The egg can reference {{server.build.default.port}} for the primary allocation, but you’ll usually want explicit TCP_PORT and UDP_PORT so the two ports stay independent.
  2. One instance per allocation pair. If you’re running multiple GameVox servers on the same node, give each one a unique TCP/UDP pair. The binary refuses to start if a port is already in use.
  3. The startup command must launch the binary. Pelican eggs define a startup command like ./gamevox-self-hosted-server. If you’ve inherited an egg that starts something else (a wrapper script, a different binary), the env vars won’t reach our process.

A note on the Docker image you’re using

The ghcr.io/pelican-eggs/yolks:debian image is a generic Debian environment. It does not contain the GameVox binary. The egg you’re using has to either:

  • Bundle the binary into the server’s working directory at install time (Pelican install scripts handle this), or
  • Download the binary on first start.

Once the binary is in place and the startup command launches it, the runtime configuration is just the env vars listed above. There is no separate config file, no flag to pass, and no container-level modification needed.

Verifying it worked

Once the server starts, you should see lines like this near the top of the log output:

[SelfHosted] Config loaded from embedded binary
  TCP_PORT      [env]  8088
  UDP_PORT      [env]  7070
  PUBLIC_IP     [--]   (auto-detected from egress)

The [env] tag confirms the value came from the environment (i.e. your Pelican variable was passed through). If you see [--] next to TCP_PORT or UDP_PORT, the variable isn’t reaching the process and the binary fell back to the default.

When to reach for PUBLIC_IP

Auto-detection works for most setups. The two cases where it fails:

  • Multi-IP hosts. If your node has several public IPs, the binary picks one based on the default route, which may not be the address clients reach you at.
  • Asymmetric NAT. If outbound traffic exits one IP but inbound arrives on another, ICE candidate gathering will advertise the wrong address and voice connections will time out.

In both cases, set PUBLIC_IP to the address clients should connect to. The TCP server doesn’t care, but the WebRTC SDP needs to advertise an address clients can actually reach.