Running the server in Docker

The server comes as a container image, ghcr.io/burningstormstudios/odyssey-server, for anywhere Docker runs: a server of your own, a rented one, a NAS, or Docker Desktop on Windows or a Mac. It is the same server as the download, and it keeps everything it writes, the world, its settings, its art and its logs, in one folder you give it.

The image comes for 64-bit Intel and AMD processors and for 64-bit ARM ones, such as an Apple Silicon Mac. Docker picks the right one by itself.

Which image#

TagWhat it is
latestThe newest stable release.
betaThe newest test release, or a stable one if it is newer.
13, 13.0The newest stable release of 13, or of 13.0.
13.0.0That release exactly, and never anything else.

latest is the usual choice. Pin an exact version if you would rather choose when to update.

Running it#

Make a folder for the server's data, and start it there:

mkdir odyssey && cd odyssey
mkdir data
docker run -d --name odyssey \
  --user "$(id -u):$(id -g)" \
  -v "$PWD/data:/data" \
  -p 2597-2599:2597-2599/udp \
  --restart unless-stopped \
  --stop-timeout 60 \
  ghcr.io/burningstormstudios/odyssey-server:latest
  • --user runs the server as you, so the files in data are yours to open and edit.
  • -v puts the server's data in the data folder.
  • -p opens the three game ports. They are UDP; see ports, routers and firewalls.
  • --stop-timeout 60 gives the server time to save when the container is stopped.

The first start makes a new, empty world and a server.yaml in data. docker logs odyssey shows what the server is doing.

To run a world you already have, such as one converted from the original Odyssey, put its .db file and its assets folder in data before the first start, and a server.yaml that names the file:

database:
  provider: sqlite
  file: discordia.db

Everything else in server.yaml is filled in on the first start. Settings in server.yaml has the rest.

On a Mac#

Docker on a Mac runs Linux in a virtual machine, and the server runs inside that. A port opened with -p is opened on the virtual machine, not on the Mac, and whether players reach it depends on how the Docker app passes ports from the Mac across. These steps are for Colima.

--user is your own user and group, as anywhere else. On a Mac id -u is usually 501 and id -g is 20, not the 1000:1000 in the examples here.

Out of the box, Colima passes TCP across and not UDP, and the game's ports are UDP. The server starts and says it is listening, but the game cannot reach it, even from the same Mac: connecting fails with "No route to host".

Give the virtual machine its own address on your network instead. Stop Colima with colima stop, and in ~/.colima/default/colima.yaml, under network, set:

network:
  address: true
  mode: bridged
  interface: en0

interface is the Mac's connection to your network: route get default names it, after interface:. Then colima start. It may ask for your password, to install what this mode needs.

The virtual machine is now a machine of its own on your network, and colima list shows its address. That address is the server's:

  • In the game, connect to that address, not to localhost.
  • In Odyssey Server Admin, give that address too.
  • On the router, forward the game's ports to that address, not to the Mac's. Give it a fixed address in the router's settings, as for any server; see ports, routers and firewalls.

Managing it#

Odyssey Server Admin manages a server in a container from your own computer, over the network, as it would any server somewhere else:

  1. Stop the container: docker stop odyssey.
  2. In data/server.yaml, under adminApi, set enableNetwork: true.
  3. Start it again with the admin port too. Add -p 5297:5297 to the docker run above, or to ports in a Compose file.
  4. Read the setup code from docker logs odyssey, and in Odyssey Server Admin choose Somewhere else, give the server's address and port 5297, and Claim this server. See staff, roles and rights.

With Docker Compose#

A compose.yaml holds the same settings, and is easier to keep. In the odyssey folder:

services:
  server:
    image: ghcr.io/burningstormstudios/odyssey-server:latest
    # Your own user and group: run "id -u" and "id -g".
    user: "1000:1000"
    restart: unless-stopped
    stop_grace_period: 1m
    ports:
      - "2597-2599:2597-2599/udp"
      # - "5297:5297"   # for Odyssey Server Admin, once enableNetwork is on
    volumes:
      - ./data:/data

Then docker compose up -d starts it, and docker compose logs -f server follows its log.

With a database#

The world can be kept in PostgreSQL, MySQL or MariaDB instead of a file. Compose can run the database beside the server. Before the first start, put a server.yaml in data that points at it:

database:
  provider: postgres
  host: db
  name: odyssey
  user: odyssey

For MySQL or MariaDB, the provider is mysql. The password is given to the server in ODYSSEY_DB_PASSWORD, so server.yaml holds nothing secret.

PostgreSQL:

services:
  server:
    image: ghcr.io/burningstormstudios/odyssey-server:latest
    user: "1000:1000"
    restart: unless-stopped
    stop_grace_period: 1m
    depends_on:
      db:
        condition: service_healthy
    environment:
      ODYSSEY_DB_PASSWORD: choose-a-password
    ports:
      - "2597-2599:2597-2599/udp"
    volumes:
      - ./data:/data

  db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_USER: odyssey
      POSTGRES_PASSWORD: choose-a-password
      POSTGRES_DB: odyssey
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U odyssey -d odyssey"]
      interval: 5s
      retries: 10
    volumes:
      - db:/var/lib/postgresql/data

volumes:
  db:

MySQL: the same server service, with this db:

  db:
    image: mysql:8.4
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: odyssey
      MYSQL_USER: odyssey
      MYSQL_PASSWORD: choose-a-password
      MYSQL_RANDOM_ROOT_PASSWORD: "yes"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      retries: 20
    volumes:
      - db:/var/lib/mysql

MariaDB: the same, with this db:

  db:
    image: mariadb:11
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: odyssey
      MARIADB_USER: odyssey
      MARIADB_PASSWORD: choose-a-password
      MARIADB_RANDOM_ROOT_PASSWORD: "yes"
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 5s
      retries: 20
    volumes:
      - db:/var/lib/mysql

Choose your own password, the same in both places. The database's port is not opened to the outside: only the server reaches it.

depends_on holds the server back until the database answers. A world already kept in a file can be moved into the database with import-world: see putting a world in, running the command with docker compose run --rm server import-world /data/discordia.db.

Updating#

The server in a container does not update itself, and Odyssey Server Admin says so. A new version upgrades the world when it first starts, so back it up first: Back up now on the Database page of Odyssey Server Admin, or, with the server stopped, a copy of the data folder (and of the database, if the world is kept in one). Then pull the new image and start a new container from it:

docker compose pull
docker compose up -d

Or, without Compose, docker pull the image, then docker stop odyssey, docker rm odyssey, and the same docker run as before. The world is in data, so a new container carries on where the old one stopped.

Checking an image is ours#

Every image is signed by the workflow that builds it. With cosign:

cosign verify ghcr.io/burningstormstudios/odyssey-server:latest \
  --certificate-identity-regexp '^https://github.com/BurningstormStudios/Odyssey/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

It prints the signature's details if the image came from us, and fails if it did not.

Running the server in Docker · Odyssey Online Classic