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:
| Variable | Default | Purpose |
|---|---|---|
TCP_PORT | 8088 | WebSocket (chat, signaling) and HTTPS (file downloads) |
UDP_PORT | 7070 | Voice 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:
| Variable | Default | Purpose |
|---|---|---|
DATA_DIR | ./data next to the binary | Where the SQLite databases, logs, and uploads live |
FILES_DIR | ${DATA_DIR}/files | Override the uploads directory only |
BACKUPS_DIR | ${DATA_DIR}/backups | Override 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:
- 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 explicitTCP_PORTandUDP_PORTso the two ports stay independent. - 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.
- 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.
Related reading
- Self-Hosting GameVox with Docker: A Complete Walkthrough covers the official Docker zip we ship, including the auto-update entrypoint pattern.
- Self-Hosting: Your Server, Your Hardware is the broader overview of what self-hosting includes.
- The self-hosted landing page has the full reference for environment variables and non-Docker install methods.