Skip to content
Documentation

Prerequisites

What has to be true before the stack in Deployment will come up. Work through it once; the deploy itself is then one file and one command.

The machine

Linux is recommended. Docker Desktop on Windows or macOS runs Dylaris, but the node drives the host Docker daemon to launch Minecraft containers as siblings, and on Desktop that "host" is a Linux VM with its own network. Fine to try, a poor place to run servers people play on.

RequirementMinimum
Docker Engine24.0 - install guide
RAM2 GB free, before any Minecraft server
Diska few GB, on the roomy volume

Docker, or Docker Swarm

One machine: nothing beyond Docker itself. Skip to the database below.

Several machines: you want Swarm, which is Docker's own clustering mode and is already in the Docker you just installed. On the machine that will be the manager:

shell
docker swarm init --advertise-addr <this-host-ip>

It prints a join command. Run that on every other machine, then check they arrived:

shell
docker swarm join --token <token> <manager-ip>:2377
docker node ls

Label the hosts

Swarm decides where a service runs, and for most of a stack that is exactly what you want. Not for all of it: a database whose files are on one host's disk must never be moved, and which machines run Minecraft servers is a decision you make, not one you discover afterwards.

So every service in the stack is constrained to a label rather than to node.role or to nothing. You decide what goes where by labelling hosts:

shell
# Exactly ONE host. Postgres and Valkey live here, and Postgres has files.
docker node update --label-add dylaris.data=true  host-a

# Hosts that may run Core. Two or more if you want Core to survive a reboot.
docker node update --label-add dylaris.core=true  host-a
docker node update --label-add dylaris.core=true  host-b

# Hosts that may serve the panel. It is a web UI and holds nothing.
docker node update --label-add dylaris.panel=true host-a
docker node update --label-add dylaris.panel=true host-b

# Hosts that run Minecraft servers. Label a machine to turn it into a node.
docker node update --label-add dylaris.node=true  host-b
docker node update --label-add dylaris.node=true  host-c

A host can carry several labels; a small fleet might put all four on one machine. Check what you have set with:

shell
docker node inspect host-a --format '{{ json .Spec.Labels }}'

Label before you deploy. A service whose label exists on no host does not fail - it sits in the task list forever with "no suitable node", which is easy to stare past.

dylaris.data is the one that has to be a single host and stay there. A reboot that reschedules Postgres onto a machine with none of its data brings up an empty database, and it looks like a working deploy.

Core file storage

Core keeps the library, ticket attachments and ticket backups as files, and this is the one piece of state that is not the database. It matters here because in a swarm there is more than one Core.

Two Cores do not share a local volume. Each one writes into the volume on its own host, and the effect is not an error - it is a library where half the files are missing depending on which replica answered. So pick one of these before you deploy:

  • S3-compatible object storage, recommended. Any provider works. Cloudflare R2 is the easy one to start with: 10 GB and a generous request allowance for free, and no egress fees. Backblaze B2 and AWS S3 are equally fine.
  • MinIO, if the fleet should not depend on anything outside itself. It is S3-compatible, runs as one more service in the stack, and is commented into the stack file ready to uncomment. It is a singleton on a disk, so it lives on the dylaris.data host like the database.
  • A shared filesystem - NFS or SMB - mounted at the same path on every host labelled dylaris.core. Workable, and the most ways to get subtly wrong: the mount has to be writable by uid 1000, and a share that appears on the host after the container started stays invisible inside it.

You configure it in the panel under Settings, Core File Storage, not in the stack file, so you can change your mind later without a redeploy. Core checks the choice before saving it: every online Core has to write, read and read each other's files, so a path that is only mounted on one host is refused instead of quietly splitting your library in two.

A single host needs none of this. One Core, one volume, nothing to share.

Database

TimescaleDB is recommended. Traffic and stats are time-series, and it keeps those tables from growing forever on their own. Plain PostgreSQL 16+ works too - set DB_TYPE=postgres and nothing else changes. Moving between them later is a dump, a restore and flipping that one value.

The single-host compose file brings its own; a swarm normally points at one you already run.

Secrets

Every setting is an environment variable. Anything sensitive also reads a <NAME>_FILE path, which is how Docker and Portainer secrets work (JWT_SECRET_FILE: /run/secrets/jwt_secret), and the file wins over the plain variable.

Generate the two long ones now:

shell
openssl rand -hex 32   # JWT_SECRET
openssl rand -hex 32   # CLUSTER_SECRET

Never the same value for both. Core refuses to boot on a placeholder - they are cluster-wide trust anchors, and a default one is worse than none.

Ports

Only the panel and the API need to be reachable from a browser. The rest can stay on the internal network.

PortWhat
25510Panel, web UI
25500Core REST API
25501Core gRPC, node to Core (internal)
25520SFTP, optional
25523Beam LAN fast path, optional
25600-25699One host port per Minecraft server

Full table, including the internal ones, in Networking and ports.

A reverse proxy

Nothing Dylaris serves is HTTPS on its own. Before it faces the internet, put Traefik or Nginx Proxy Manager in front of it - Traefik if you are already on Swarm and want labels to do the routing, Nginx Proxy Manager if you would rather click it together.

Reverse proxy and TLS has the configuration for both, including the one setting that makes the live console work.