A containerized FiveM server with automatic configuration and OneSync support.
- WebSocket Secure (WSS): Real-time console streaming over HTTPS with automatic Let's Encrypt certificates
- Traefik Integration: Built-in reverse proxy with automatic HTTPS/TLS termination
- TypeScript Control Layer: Modern Node.js WebSocket server for server management
- Graceful Shutdown: Event for resources to save data gracefully
- Environment-Based Config: Automatic
server.cfggeneration from env variables - OneSync Ready: Built-in OneSync support for improved multiplayer sync
- Alpine Linux: Minimal, fast image (~200MB base)
services:
traefik:
image: traefik:v3.6
restart: unless-stopped
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.fivem.address=:30120"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
- "8080:8080"
- "30120:30120"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./letsencrypt:/letsencrypt
fivem:
image: ghcr.io/skriptzip/fivem:26261
container_name: fivem
restart: on-failure
stop_grace_period: 46s
environment:
- LICENSE_KEY=${LICENSE_KEY}
- ENABLE_WEBSOCKET=1
- WEBSOCKET_API_KEY=${WEBSOCKET_API_KEY:-}
- WEBSOCKET_PORT=${WEBSOCKET_PORT:-30121}
- RCON_PASSWORD=${RCON_PASSWORD:-changeme}
- SERVER_NAME=${SERVER_NAME:-FiveM Server}
- SERVER_DESC=${SERVER_DESC:-A FiveM Server}
- SERVER_TAGS=${SERVER_TAGS:-default}
- MAX_CLIENTS=${MAX_CLIENTS:-32}
- STEAM_API_KEY=${STEAM_API_KEY:-none}
- MYSQL_CONNECTION=${MYSQL_CONNECTION:-mysql://user:password@localhost:3306/database}
- LOCALE=${LOCALE:-en-US}
ports:
- "30120:30120/udp"
volumes:
- ./fivem-data:/config
tty: true
labels:
- "traefik.enable=true"
# βββ FiveM TCP (play.example.com:30120) βββ
- "traefik.tcp.routers.fivem-tcp.rule=HostSNI(`*`)"
- "traefik.tcp.routers.fivem-tcp.entrypoints=fivem"
- "traefik.tcp.services.fivem-tcp.loadbalancer.server.port=30120"
# βββ WSS Public (wss.example.com) βββ
- "traefik.http.routers.fivem-wss-public.rule=Host(`${WSS_HOST}`)"
- "traefik.http.routers.fivem-wss-public.entrypoints=websecure"
- "traefik.http.routers.fivem-wss-public.tls.certresolver=letsencrypt"
- "traefik.http.routers.fivem-wss-public.service=fivem-wss"
# βββ WSS Local (wss.localhost) βββ
- "traefik.http.routers.fivem-wss-local.rule=Host(`wss.localhost`)"
- "traefik.http.routers.fivem-wss-local.entrypoints=web"
- "traefik.http.routers.fivem-wss-local.service=fivem-wss"
# βββ FiveM Local (fivem.localhost) βββ
- "traefik.http.routers.fivem-local.rule=Host(`fivem.localhost`)"
- "traefik.http.routers.fivem-local.entrypoints=web"
- "traefik.http.routers.fivem-local.service=fivem-wss"
# βββ Shared WSS Service βββ
- "traefik.http.services.fivem-wss.loadbalancer.server.port=${WEBSOCKET_PORT:-30121}"
- "traefik.http.services.fivem-wss.loadbalancer.server.scheme=http"
depends_on:
- traefikNote:
*.localhostdomains resolve automatically in modern browsers and Windows/WSL β no hosts file entry needed.
Note: Traefik reaches containers via the internal Docker network. Port
30121does not need to be exposed inports:β only UDP30120needs a direct mapping since Traefik cannot proxy UDP traffic.
| Variable | Description | Required |
|---|---|---|
LICENSE_KEY |
FiveM key from keymaster.fivem.net | β Yes |
WSS_HOST |
Hostname for WSS routing (e.g., wss.example.com) |
β Yes |
LETSENCRYPT_EMAIL |
Email for Let's Encrypt certificate notifications | β Yes |
WEBSOCKET_API_KEY |
API key for WebSocket authentication (Bearer token or query param) | β Optional |
WEBSOCKET_PORT |
Internal WebSocket server port (default: 30121) | β Optional |
RCON_PASSWORD |
RCON password (randomly generated if not set) | β Optional |
ENABLE_WEBSOCKET |
Enable WebSocket server (default: 1) | β Optional |
NO_DEFAULT_CONFIG |
Skip default config (for txAdmin) | β Optional |
NO_ONESYNC |
Disable OneSync | β Optional |
| Domain | Record | Purpose |
|---|---|---|
play.example.com |
A β your server IP | FiveM direct connect |
wss.example.com |
A β your server IP | WebSocket admin console |
*.localhost |
automatic β | Local development |
Connect via query parameter:
const ws = new WebSocket(`wss://wss.example.com?api_key=${apiKey}`);Or with Bearer token:
const ws = new WebSocket('wss://wss.example.com');
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'auth', token: apiKey }));
};ws.onopen = () => {
console.log('Connected to FiveM WebSocket');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Server message:', data);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('Disconnected from server');
};/config- Server configuration & resources/txData- txAdmin data (if using txAdmin)
When the container stops, it waits up to 45 seconds for resources to save data. Listen for the shutdown event in your resources:
-- server.lua
RegisterCommand("trigger_shutdown", function(source, args, rawCommand)
local reason = args[1] or "No Reason provided"
local secondsRemaining = tonumber(args[2]) or 45000
TriggerEvent("docker:shutdown", secondsRemaining, reason)
TriggerClientEvent("docker:shutdown", -1, secondsRemaining, reason)
end, true)Images are automatically built with the latest recommended FiveM version.
ghcr.io/skriptzip/fivem:latest- Latest versionghcr.io/skriptzip/fivem:26261- Specific version (pin for production)