Skip to main content

Overview

The official image is published at ghcr.io/landsandboat/server:latest (Ubuntu-based) and ghcr.io/landsandboat/server:alpine (Alpine-based). The image contains the compiled server binaries, scripts, SQL, and tools — but not a database.

Prerequisites: mesh volumes

Navigation and line-of-sight meshes are shipped in a separate image. You must load them into named Docker volumes before starting the server:
docker run --rm \
  -v losmeshes:/losmeshes \
  -v navmeshes:/navmeshes \
  ghcr.io/landsandboat/ximeshes:latest
This is a one-time operation. The volumes (losmeshes, navmeshes) persist across container restarts.

Database configuration

A database is not included in the image. You must provide connection credentials via environment variables:
VariableAlso accepted asDescription
XI_NETWORK_SQL_HOSTDatabase hostname or IP
XI_NETWORK_SQL_PORTDatabase port (default 3306)
XI_NETWORK_SQL_DATABASEMARIADB_DATABASEDatabase name
XI_NETWORK_SQL_LOGINMARIADB_USERDatabase username
XI_NETWORK_SQL_PASSWORDMARIADB_PASSWORDDatabase password

Running the server

# Start a server with no database (install MariaDB inside or connect externally)
docker run --name some-lsb-server \
  -p 54001:54001 \
  -p 54002:54002 \
  -p 54230:54230 \
  -p 54231:54231 \
  -v losmeshes:/server/losmeshes \
  -v navmeshes:/server/navmeshes \
  -it ghcr.io/landsandboat/server:latest

Docker Compose

Docker Compose is the recommended approach. It manages all services, health checks, and startup order automatically.
x-dbcreds: &dbcreds
  # MARIADB_ROOT_PASSWORD required if setting up fresh database.
  # Or generate a random root password and print it to build log:
  # MARIADB_RANDOM_ROOT_PASSWORD: true
  MARIADB_ROOT_PASSWORD: 'root'
  MARIADB_DATABASE: xidb
  MARIADB_USER: xiadmin
  MARIADB_PASSWORD: 'password'

x-common: &common
  image: ghcr.io/landsandboat/server:latest
  environment:
    <<: *dbcreds
    XI_NETWORK_HTTP_HOST: 0.0.0.0
    XI_NETWORK_ZMQ_IP: world
    XI_NETWORK_SQL_HOST: database
    # XI_{file}_{setting}: value
  volumes:
    - losmeshes:/server/losmeshes
    - navmeshes:/server/navmeshes
    # - ./config.yaml:/server/tools/config.yaml
    # - ./map.lua:/server/settings/map.lua
    # - ./modules:/server/modules

services:
  database:
    image: mariadb:lts
    restart: always
    command: ['--character-set-server=utf8mb4', '--collation-server=utf8mb4_general_ci']
    environment:
      <<: *dbcreds
    volumes:
      - database:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      start_period: 10s
      interval: 10s
      timeout: 5s
      retries: 3

  database-update:
    <<: *common
    command: ["python", "/server/tools/dbtool.py", "update"]
    depends_on:
      database:
        condition: service_healthy

  connect:
    <<: *common
    command: ["/server/xi_connect"]
    restart: unless-stopped
    ports:
      - "54001:54001"
      - "54230:54230"
      - "54231:54231"
    depends_on:
      database:
        condition: service_healthy
        restart: true
      database-update:
        condition: service_completed_successfully

  search:
    <<: *common
    command: ["/server/xi_search"]
    restart: unless-stopped
    ports:
      - "54002:54002"
    depends_on:
      database:
        condition: service_healthy
        restart: true
      database-update:
        condition: service_completed_successfully

  world:
    <<: *common
    command: ["/server/xi_world"]
    restart: unless-stopped
    ports:
      - "8088:8088"
    depends_on:
      database:
        condition: service_healthy
        restart: true
      database-update:
        condition: service_completed_successfully

  map:
    <<: *common
    command: ["/server/xi_map"]
    restart: unless-stopped
    ports:
      - "54230:54230/udp"
    depends_on:
      database:
        condition: service_healthy
        restart: true
      database-update:
        condition: service_completed_successfully
      world:
        condition: service_started

volumes:
  database:
  losmeshes:
    external: true
  navmeshes:
    external: true

Volume mounts

Mount pathPurpose
/server/losmeshesLine-of-sight mesh data (load from ximeshes image)
/server/navmeshesNavigation mesh data (load from ximeshes image)
/server/tools/config.yamldbtool configuration (controls express updates, backups)
/server/settings/map.luaOverride individual settings files
/server/modulesLua and SQL runtime modules

Customization

Settings via environment variables

Any setting in a settings/*.lua file can be overridden with an environment variable using the format:
-e XI_{file}_{setting}=value
Examples:
-e XI_NETWORK_HTTP_HOST=0.0.0.0     # sets HTTP_HOST in settings/network.lua
-e XI_NETWORK_ZMQ_IP=world          # sets ZMQ_IP in settings/network.lua
-e XI_MAIN_SERVER_NAME=MyServer     # sets SERVER_NAME in settings/main.lua

Settings files

For more extensive changes, bind-mount an entire settings file:
--mount type=bind,src="$(pwd)"/settings/map.lua,dst=/server/settings/map.lua

Modules

Bind-mount a modules directory to load Lua and SQL runtime modules:
--mount type=bind,src="$(pwd)"/modules,dst=/server/modules
C++ modules require building a custom image. See Build from source for build-arg options.

Default user

The image runs as user xiadmin (UID 1000, GID 1000). Ensure that bind-mounted files and directories are readable by UID 1000.

Port reference

PortProtocolServicePurpose
54001TCPxi_connectLogin view
54002TCPxi_searchSearch / auction house
54230TCPxi_connectLogin data
54230UDPxi_mapZone data
54231TCPxi_connectLogin auth
8088TCPxi_worldHTTP API (disabled by default)

Build docs developers (and LLMs) love