A docker image for FiveM servers.
  • Shell 89.9%
  • Dockerfile 7.1%
  • Makefile 3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
kennyrkun 1abded237f
Some checks failed
ci / lint (push) Successful in 4s
ci / commitlint (push) Failing after 13s
ci / test (push) Successful in 9s
ci / build (push) Successful in 9s
use certain actions from github and use ubuntu-latest-docker in all jobs
2026-08-31 09:46:05 -04:00
.github/workflows use certain actions from github and use ubuntu-latest-docker in all jobs 2026-08-31 09:46:05 -04:00
lib ci: silence false-positive SC2016 in git askpass 2026-06-04 21:23:14 +08:00
test chore: initial push 2026-06-04 20:46:46 +08:00
.commitlintrc.yml chore: initial push 2026-06-04 20:46:46 +08:00
.dockerignore chore: initial push 2026-06-04 20:46:46 +08:00
.env.example chore: initial push 2026-06-04 20:46:46 +08:00
.gitignore chore: initial push 2026-06-04 20:46:46 +08:00
database.docker.cfg.example chore: initial push 2026-06-04 20:46:46 +08:00
docker-compose.dev.yml chore: initial push 2026-06-04 20:46:46 +08:00
docker-compose.prod.yml chore: initial push 2026-06-04 20:46:46 +08:00
Dockerfile chore: initial push 2026-06-04 20:46:46 +08:00
entrypoint.sh chore: initial push 2026-06-04 20:46:46 +08:00
healthcheck.sh chore: initial push 2026-06-04 20:46:46 +08:00
LICENSE chore: initial push 2026-06-04 20:46:46 +08:00
Makefile chore: initial push 2026-06-04 20:46:46 +08:00
README.md chore: initial push 2026-06-04 20:46:46 +08:00

fivem-docker

A generic, reusable Docker image for running a FiveM (cfx) server. The image ships runtime only — the fxserver artifact version, the resources directory, and the config directory are all chosen at container run time. Content is supplied by host bind-mount or pulled from git on boot.

Launches via txAdmin. Alpine base, runs the server as a non-root user.

Layout

Everything persistent lives under the /data volume. txAdmin runs in host-config mode, which requires the server content to sit under the txData path — so the server base defaults to /data/txData/server:

/data/                          # the volume
  artifacts/<id>/               # downloaded fxserver builds (cache)
  txData/                       # txAdmin state
    default/                    # txAdmin profile (created on first run)
    server/                     # SERVER_ROOT — your server base folder
      resources/                # RESOURCES_DIR
      cfg/                      # CFG_DIR
      server.cfg                # SERVER_CFG
      cache/                    # fxserver cache (persists across restarts)

Quick start

Bind-mount mode (host owns content)

docker build -t fivem-docker .

docker run -d --name fivem \
  -p 30120:30120/tcp -p 30120:30120/udp -p 47291:47291/tcp \
  -e FXSERVER_VERSION=recommended \
  -e LICENSE_KEY=cfxk_xxx \
  -v /path/to/resources:/data/txData/server/resources \
  -v /path/to/cfg:/data/txData/server/cfg \
  -v /path/to/server.cfg:/data/txData/server/server.cfg:ro \
  -v fivem-data:/data \
  fivem-docker

If your server.cfg execs configs by a relative path (e.g. exec .cfg/x.cfg), mount that dir to match the relative name and set CFG_DIR to it, e.g. -v /path/to/.cfg:/data/txData/server/.cfg -e CFG_DIR=/data/txData/server/.cfg.

Git monorepo mode (container pulls on boot)

docker run -d --name fivem \
  -p 30120:30120/tcp -p 30120:30120/udp -p 47291:47291/tcp \
  -e FXSERVER_VERSION=25770 \
  -e GIT_REPO=https://github.com/you/your-server.git \
  -e GIT_REF=main \
  -e GIT_TOKEN=ghp_xxx \
  -e LICENSE_KEY=cfxk_xxx \
  -v fivem-data:/data \
  fivem-docker

Copy .env.example to .env and fill it in, then:

docker compose -f docker-compose.dev.yml up -d    # dev  (bind mounts)
docker compose -f docker-compose.prod.yml up -d   # prod (git monorepo)

Dev binds the txAdmin panel to 127.0.0.1 only; prod publishes it (firewall it).

Configuration (.env)

Compose reads .env (gitignored) for both container env and ${VAR} substitution. See .env.example for every setting — including secrets (LICENSE_KEY, GIT_TOKEN, DB creds). .env stays out of git; on a private host that's the simple, sufficient option.

Advanced: each sensitive var also accepts a _FILE variant (LICENSE_KEY_FILE=/run/secrets/...) for docker/k8s secret mounts, if you'd rather not keep values in .env.

First run

txAdmin starts on :47291. On first boot open the panel, register with the PIN printed in the logs, then choose Existing Server Data and point it at the server base:

  • Server Data / Base Folder: /data/txData/server
  • CFG File: /data/txData/server/server.cfg

Setup is persisted in the /data volume (txData/default), so restarts skip it.

Launch modes

  • txAdmin (default, TXADMIN=1) — run.sh boots the txAdmin panel on :47291. First run: point it at the cfg (see First run). Gives a web console, scheduled restarts, admin management. The cfg path is stored in txData.
  • Direct (TXADMIN=0) — no panel. fxserver execs SERVER_CFG directly from SERVER_ROOT, fully env-driven, zero-click boot. Healthcheck switches to the game port. Use when you don't want txAdmin and manage via RCON/server console. Needs a tty for fxserver's console: the compose files set tty: true + stdin_open: true; with raw docker run use -dit instead of -d.

Environment

Var Default Purpose
FXSERVER_VERSION recommended build number, NUM-HASH id, or channel: latest / recommended / critical / optional
SERVER_ROOT /data/txData/server server base folder (under txData per txAdmin host mode)
RESOURCES_DIR $SERVER_ROOT/resources in-container resources path
CFG_DIR $SERVER_ROOT/cfg in-container cfg path
SERVER_CFG $SERVER_ROOT/server.cfg entry cfg pointed to in txAdmin
GIT_REPO — monorepo; if set, clones/pulls all of SERVER_ROOT on boot
GIT_RESOURCES_REPO — split mode: resources source repo
GIT_CFG_REPO — split mode: cfg source repo
GIT_REF main branch / tag / sha checked out
GIT_AUTOPULL 1 0/false/no/off → never fetch/reset an existing checkout (clone-if-missing only); protects a dev's local branch
GIT_TOKEN / GIT_TOKEN_FILE — https auth token
GIT_USERNAME x-access-token https auth username
LICENSE_KEY / LICENSE_KEY_FILE — (required) cfx license key
DB_PASSWORD / DB_PASSWORD_FILE — resolved into env for your cfg to read
TXADMIN 1 1 = txAdmin panel; 0/false/no/off = direct mode (fxserver execs SERVER_CFG, no panel)
TXADMIN_PORT 47291 txAdmin panel port (TXHOST_TXA_PORT)
TXADMIN_INTERFACE 0.0.0.0 bind interface (TXHOST_INTERFACE)
ARTIFACT_KEEP — keep only N newest cached artifacts
SECRETS_STRICT_PERMS — if set, reject group/world-readable *_FILE secrets
LOG_LEVEL info debug / info / warn / error

Any VAR with a sensitive value supports a VAR_FILE variant pointing at a file (docker/k8s secret). The file wins if both are set.

Mount matrix

A managed dir is bind-mounted (host owns) XOR git-managed (container clones) — never both. The mode is chosen per dir by whether a GIT_* repo env is set.

Dir Bind mode Git mode
SERVER_ROOT (whole) bind subdirs individually set GIT_REPO
RESOURCES_DIR -v host:/data/txData/server/resources set GIT_RESOURCES_REPO
CFG_DIR -v host:/data/txData/server/cfg set GIT_CFG_REPO
/data always a volume —

Setting GIT_REPO (monorepo) together with GIT_RESOURCES_REPO/GIT_CFG_REPO (split) is rejected at boot. Pointing a git repo at a populated bind mount is also rejected.

Artifact versions

  • Channel keyword (recommended, latest, critical, optional) → resolved off the cfx changelog at boot.
  • Bare build number (e.g. 25770) → hash resolved off the artifact listing.
  • Full NUM-HASH id → used directly (reproducible pin).

Downloaded builds are cached under /data/artifacts/<id>; switching versions keeps old builds for fast rollback (cap with ARTIFACT_KEEP).

Ports

Port Proto Use
30120 tcp + udp game traffic
47291 tcp txAdmin panel

Updates

Git pull happens on boot only — update content with docker restart. This keeps host edits and pulls from colliding mid-session.

For prod, leave GIT_AUTOPULL=1 (default): each boot resets SERVER_ROOT to GIT_REF. For dev working on a personal branch, set GIT_AUTOPULL=0 so the container clones once but never fetch/resets — your local branch + uncommitted work survive restarts, and you sync from master yourself. (Pure bind-mount dev sets no GIT_* at all and skips git entirely.)

External database (host MySQL)

The container reaches a database running on the host via host.docker.internal (Docker Desktop resolves it; on a Linux engine add --add-host=host.docker.internal:host-gateway). Two things are required:

  1. A user grant for the docker network. The connection arrives from the docker net (e.g. 172.17.x), not localhost, so root@localhost won't match:
    CREATE USER 'fivem'@'%' IDENTIFIED BY 'your-strong-pass';
    GRANT ALL PRIVILEGES ON your_db.* TO 'fivem'@'%';
    FLUSH PRIVILEGES;
    
    Scope '%' or tighter ('172.17.%'). Use a dedicated user, not root.
  2. MySQL bound to all interfaces. Set bind-address = 0.0.0.0 in the server config and restart MySQL, otherwise it refuses non-local connections.

Point your server's DB config at host.docker.internal:3306 with that user. If the same config file is shared with a bare-metal setup that uses 127.0.0.1, keep both: bind a docker-specific config over the in-repo one (see the commented override in docker-compose.dev.yml).

Security / ownership

The container starts as root only to chown the /data volume and the root-owned bind-mount parent dirs, then drops to the unprivileged fivem user (uid 30120) via su-exec for boot and the server process. The uid/gid are pinned so a persisted volume keeps working across image rebuilds.

Tests

Shell unit tests use bats:

make lint     # shellcheck all scripts
make test     # bats test/
make check    # both
make build    # docker build

CI (.github/workflows/ci.yml) runs lint + test, then builds the image on every push/PR.