The self-hosted server is a single Go binary that idles at ~45 MB of RAM. The Docker image wraps it with a healthcheck, log rotation, and an auto-update flow that survives container recreates.
This guide walks through downloading the Docker zip from the app, bringing it up with docker compose, and what each file in the zip does.
Prerequisites
- Docker or Docker Desktop installed (Engine 20.10+ recommended)
- Two ports reachable from your users:
- TCP 8088: WebSocket (chat, signaling) and HTTP (file downloads)
- UDP 7070: Voice traffic (WebRTC)
- A GameVox account and your server’s download page open in the app
User accounts flow through the GameVox cloud for authentication. Everything else (messages, voice, files, emojis, soundboard clips, permissions) lives in your container’s data volume.
1. Download the Docker zip
From your server’s download page in the GameVox app, select the Docker option. You’ll get a gamevox-docker.zip containing four files:
gamevox-docker.zip
gamevox-docker/
gamevox-self-hosted-server (Linux binary with your server config embedded)
Dockerfile (Alpine image, reads TCP_PORT / UDP_PORT / DATA_DIR)
entrypoint.sh (seeds the binary into the data volume so auto-updates persist)
docker-compose.yml (host-networked, ports and PUBLIC_IP set near the top)
Your server’s configuration is baked into the binary, so there’s no separate token to paste. Keep the zip private; anyone with it can connect as your server.
2. Bring it up with Docker Compose
Extract the zip, open a terminal in the gamevox-docker/ folder, and run:
docker compose up -d --build
That builds the image and starts the container. The shipped Compose file uses host networking by default. The container shares the host’s network stack directly, which works best for voice/WebRTC: LAN clients reach the server at the host’s LAN IP without extra config, and WebRTC sees the real network interfaces so ICE candidate gathering doesn’t break behind Docker’s bridge NAT. Host networking is Linux-only. On Docker Desktop (macOS/Windows), see the bridge-mode note in §3.
Check that it’s running:
docker compose ps
docker compose logs -f gamevox
You should see the server connecting to the GameVox cloud. From the host, hit the health endpoint to confirm the internal server is up:
curl -k https://localhost:8088/health
The -k is needed because the server uses a self-signed cert for HTTPS on localhost.
3. What’s in the zip
The four files are small and commented. Here’s what each one does.
docker-compose.yml
Two things to know about the shipped Compose file:
- YAML anchors at the top. Ports are declared once in an
x-configblock and referenced from the environment and healthcheck blocks via*tcp_port/*udp_port. To change a port, edit one value at the top of the file. - Host networking by default. Set with
network_mode: host. A commented-outports:block underneath is the bridge-mode fallback for Docker Desktop, or hosts where you can’t use host mode.
# Edit these two values to change the server's ports. They flow into the
# environment: and healthcheck: blocks below via YAML anchors, so you only
# need to change them in this one place.
x-config:
tcp_port: &tcp_port 8088 # WebSocket (chat, signaling) + HTTP (file downloads)
udp_port: &udp_port 7070 # Voice traffic (WebRTC/UDP)
services:
gamevox:
build: .
container_name: gamevox
# Host networking. The container shares the host's network stack directly,
# so LAN clients reach the server at the host's LAN IP without extra config,
# WebRTC sees real network interfaces (no Docker bridge double-NAT), and
# the server's built-in LAN auto-discovery works correctly.
# Linux-only. On Docker Desktop, comment this out and uncomment ports:.
network_mode: host
# Bridge networking fallback. Only enable if `network_mode: host` is
# commented out above. Less reliable for voice; you may also need PUBLIC_IP.
#ports:
# - target: *tcp_port
# published: *tcp_port
# protocol: tcp
# - target: *udp_port
# published: *udp_port
# protocol: udp
volumes:
- ./data:/app/data
environment:
DATA_DIR: /app/data
TCP_PORT: *tcp_port
UDP_PORT: *udp_port
# Advanced. Leave commented out unless voice connections fail and
# you've confirmed auto-detection grabs the wrong address (multi-IP
# VPS hosts, asymmetric ingress/egress NAT, or bridge mode above).
#PUBLIC_IP: 1.2.3.4
restart: unless-stopped
healthcheck:
# $$TCP_PORT (double dollar) tells Compose "leave this alone"; the
# shell inside the container expands it from the env: block above.
test: ["CMD-SHELL", "curl -fsk https://localhost:$$TCP_PORT/health"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
logging:
driver: json-file
options:
max-size: "100m"
max-file: "3"
Log rotation (100 MB × 3 files) matters because, without it, a chatty server will eventually fill your host’s disk. restart: unless-stopped is what lets the auto-update flow work: when the server exits after swapping its own binary, Docker brings it back up.
Dockerfile
FROM alpine:latest
# Configurable settings. The server binary reads TCP_PORT / UDP_PORT / DATA_DIR
# directly, so these flow straight through to the running process.
ENV TCP_PORT=8088 \
UDP_PORT=7070 \
DATA_DIR=/app/data
# curl is used for the HTTPS healthcheck (BusyBox wget lacks SSL support)
RUN apk add --no-cache curl
RUN mkdir -p /app /app/data
WORKDIR /app
VOLUME /app/data
# The binary baked into the image is a "seed". entrypoint.sh copies it into
# the persistent data volume at $DATA_DIR/bin/ on first boot, then always
# execs from there. This makes auto-updates survive container recreates.
COPY ./gamevox-self-hosted-server /app/gamevox-self-hosted-server.seed
COPY ./entrypoint.sh /app/entrypoint.sh
# Set the executable bit inside the build container, so the host's filesystem
# mode is irrelevant. This avoids the "entrypoint.sh not executable" failure
# that hits Windows hosts and other build contexts where exec bits are
# stripped. Plain RUN chmod works without BuildKit (vs. COPY --chmod, which
# requires it).
RUN chmod +x /app/gamevox-self-hosted-server.seed /app/entrypoint.sh
EXPOSE ${TCP_PORT} ${UDP_PORT}/udp
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -fsk https://localhost:${TCP_PORT}/health || exit 1
ENTRYPOINT ["sh", "/app/entrypoint.sh"]
A few details worth knowing:
ENVblock at the top. Both port values and the data directory live in the image’s environment, soEXPOSEandHEALTHCHECKreference them directly. Override any of them via Compose’senvironment:block (the shipped file does this for you) and they flow through cleanly.RUN chmod +xafter the COPYs. Sets the executable bit during the build, inside the build container, independent of whatever filesystem mode the host has. That’s why the zip works on Windows hosts and Portainer setups that strip exec bits from the build context. Using a plainRUNline instead ofCOPY --chmod=755keeps the build working on Docker engines without BuildKit..seedrename. The baked-in binary is renamed to.seedinside the image so anyone shelling into the container can tell it apart from the actively-running binary at${DATA_DIR}/bin/gamevox-self-hosted-server. The file in the downloaded zip keeps its normal name.
Alpine keeps the image small (~15 MB), and only curl is installed on top for the healthcheck. The server binary is fully statically linked, so no extra runtime libraries are needed.
entrypoint.sh
#!/bin/sh
# Gamevox Self-Hosted Server: container entrypoint.
#
# The binary baked into the image is a "seed". On first boot we copy it into
# the persistent data volume at $DATA_DIR/bin/, then always exec from there.
# This way, when the server auto-updates itself (which replaces the binary
# in place on the persistent volume), the new binary survives container
# recreates. Without this, a `docker compose up -d --build` or host reboot
# would drop the updated binary along with the container's writable overlay
# and fall back to the (older) baked-in binary.
#
# If the admin intentionally rebuilds the image with a newer binary (e.g. to
# force a specific version), the seed will be newer than the cached copy and
# the entrypoint will re-seed, overwriting the cached binary.
set -e
BIN_DIR="${DATA_DIR}/bin"
RUN_BIN="${BIN_DIR}/gamevox-self-hosted-server"
SEED="/app/gamevox-self-hosted-server.seed"
mkdir -p "$BIN_DIR"
if [ ! -f "$RUN_BIN" ] || [ "$SEED" -nt "$RUN_BIN" ]; then
echo "[entrypoint] Seeding binary from image to $RUN_BIN"
cp "$SEED" "$RUN_BIN"
chmod +x "$RUN_BIN"
fi
exec "$RUN_BIN" "$@"
This tiny script is what makes auto-updates work reliably in Docker. More on that below.
How auto-updates work in Docker
GameVox servers auto-update themselves: the running process downloads a new binary, swaps it in, and restarts. On bare metal (systemd), this is a one-step process: replace the binary, restart the service.
In Docker it’s trickier. A running container has a writable overlay filesystem, but that overlay is discarded whenever the container is recreated (for example, docker compose up -d --build, or a host reboot in certain configurations). If the updated binary only lived in the overlay, every rebuild would silently roll you back to whatever was baked into the image.
The entrypoint pattern handles this:
- First boot. Entrypoint copies the image’s seed binary to
/app/data/bin/gamevox-self-hosted-serveron the persistent volume, then execs it. - Auto-update. The server writes the new binary directly to
/app/data/bin/gamevox-self-hosted-server(the same path it’s running from). On Linux, renaming over a running binary works cleanly. - Restart. The server exits with a special code, Docker’s
restart: unless-stoppedkicks in, entrypoint runs again, sees the cached binary exists and isn’t older than the seed, and execs it. - Image rebuild. If you
docker compose up -d --buildwith a newer seed binary in the folder, the seed is newer than the cached copy, so the entrypoint re-seeds. Useful for pinning to a specific version.
The takeaway: you don’t need to rebuild the image to stay current. The server updates itself, and the updated binary lives on your data volume.
Manual updates
Auto-updates handle this for you, but if you ever want to force-refresh to the latest version:
# Download a fresh zip from your server's download page, then:
docker compose down
# Replace gamevox-self-hosted-server in your folder with the one from the new zip
docker compose up -d --build
Your ./data/ folder is preserved across this. The entrypoint will see the new seed is newer than the cached binary and re-seed from it.
Directory layout
Before you start, your folder should look like this:
gamevox-docker/
docker-compose.yml
Dockerfile
entrypoint.sh (executable bit set by RUN chmod inside the image; nothing to do on the host)
gamevox-self-hosted-server (Linux binary from your download page)
data/ (created automatically on first run)
After the first run, the data/ folder will fill in with everything the server persists:
data/
bin/
gamevox-self-hosted-server (the actively running binary, seeded from the image)
community.db (chat, channels, permissions, emojis)
files/ (user uploads)
backups/ (automatic database snapshots)
logs/ (server-internal logs, separate from Docker logs)
Useful commands
# View live server logs
docker compose logs -f gamevox
# Restart (e.g. after changing environment variables)
docker compose restart
# Stop the container but keep it around
docker compose stop
# Fully tear down (preserves ./data/)
docker compose down
# Check health status and uptime
docker compose ps
Behind a reverse proxy
If you want to put GameVox behind nginx, Caddy, or Traefik so you can use your own cert on port 443, proxy all traffic on / to http://127.0.0.1:8088. The UDP voice port (7070) cannot go through an HTTP reverse proxy. Expose it directly on the firewall.
nginx example:
server {
listen 443 ssl http2;
server_name gamevox.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/gamevox.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/gamevox.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8088;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s;
}
}
Make sure UDP 7070 is still open on your firewall, since voice traffic bypasses the reverse proxy entirely.
Troubleshooting
The container keeps restarting
Check docker compose logs gamevox. The most common causes:
- Port already in use. With the default host networking, the server binds 8088/7070 directly on the host. If something else owns those ports, edit the
x-configblock at the top ofdocker-compose.ymlto pick free ones; the values flow through to both the environment and healthcheck via the YAML anchors. - Permission denied on
/app/data. The host’s./data/directory exists but isn’t writable by the container. If it’s empty, remove it and let Docker recreate it on first run. entrypoint.sh: not executable. Older shipped images usedCOPY --chmod=755, which requires BuildKit. The current image uses a plainRUN chmod +xline that works on any Docker engine. If you’re seeing this error, redownload the zip to pick up the new Dockerfile.- Corrupted seed. Rare, but if the
entrypointcopy fails, delete./data/bin/and restart. The entrypoint will re-seed from the image.
Voice connections fail or freeze on Docker Desktop
Docker Desktop on macOS/Windows runs containers inside a Linux VM, so network_mode: host maps to the VM’s network rather than the real host LAN. Switch the Compose file to bridge mode:
- Comment out
network_mode: host. - Uncomment the
ports:block underneath it. - If voice still fails, set
PUBLIC_IPin theenvironment:block to the address clients should reach you at.
Auto-updates aren’t happening
The server checks for updates periodically once connected to the GameVox cloud. If you’ve been running an older container from before the entrypoint pattern was added, download a fresh zip once to pick up the new flow. After that, updates are hands-off, with no need to rebuild the image manually.
”I rebuilt the image and lost my data”
You didn’t. Your data lives in ./data/ on the host, which is bind-mounted into the container. As long as that folder is still there, your messages, files, and config are intact. docker compose up -d will pick back up where it left off.
Going further
For a broader overview of what self-hosting includes and how it compares to running on the GameVox cloud, see Self-Hosting: Your Server, Your Hardware. The self-hosted landing page has the full reference for environment variables, firewall rules, and non-Docker install methods (Linux systemd, Windows service).