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.
| Requirement | Minimum |
|---|---|
| Docker Engine | 24.0 - install guide |
| RAM | 2 GB free, before any Minecraft server |
| Disk | a 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:
docker swarm init --advertise-addr <this-host-ip>It prints a join command. Run that on every other machine, then check they arrived:
docker swarm join --token <token> <manager-ip>:2377
docker node lsLabel 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:
# 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-cA host can carry several labels; a small fleet might put all four on one machine. Check what you have set with:
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.datahost 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:
openssl rand -hex 32 # JWT_SECRET
openssl rand -hex 32 # CLUSTER_SECRETNever 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.
| Port | What |
|---|---|
25510 | Panel, web UI |
25500 | Core REST API |
25501 | Core gRPC, node to Core (internal) |
25520 | SFTP, optional |
25523 | Beam LAN fast path, optional |
25600-25699 | One 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.