Docker Compose
This page walks through a complete docker-compose.yml that runs Infrarust as a reverse proxy in front of multiple Minecraft servers. The servers register themselves through Docker labels, so you don't need static config files for each backend.
Prerequisites
You need Docker and Docker Compose v2 installed. The examples use the itzg/minecraft-server image for backend servers, but any Minecraft server image works.
Project layout
my-network/
├── docker-compose.yml
└── config/
└── infrarust.toml2
3
4
The config/ directory is mounted into the Infrarust container. If you also want file-based server definitions, add a servers/ subdirectory there.
The infrarust.toml
A minimal config that enables Docker discovery:
bind = "0.0.0.0:25565"
[docker]
network = "minecraft"2
3
4
The [docker] section activates the Docker provider. Setting network = "minecraft" tells Infrarust to resolve container IPs from that specific Docker network. Without it, Infrarust picks the first available network IP from each container, which can be wrong if containers are attached to multiple networks.
Full docker-compose.yml
networks:
minecraft:
driver: bridge
services:
# ── Infrarust reverse proxy ──────────────────────
infrarust:
image: ghcr.io/shadowner/infrarust:latest
container_name: infrarust
command: ["--config", "/app/config/infrarust.toml"]
ports:
- "25565:25565"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./config:/app/config
networks:
- minecraft
restart: unless-stopped
# ── Lobby server ─────────────────────────────────
lobby:
image: itzg/minecraft-server
container_name: lobby
environment:
EULA: "TRUE"
TYPE: "PAPER"
MEMORY: "1G"
labels:
infrarust.enable: "true"
infrarust.domains: "mc.example.com"
infrarust.proxy_mode: "client_only"
infrarust.network: "main"
networks:
- minecraft
restart: unless-stopped
# ── Survival server ──────────────────────────────
survival:
image: itzg/minecraft-server
container_name: survival
environment:
EULA: "TRUE"
TYPE: "PAPER"
DIFFICULTY: "hard"
MEMORY: "2G"
labels:
infrarust.enable: "true"
infrarust.domains: "survival.mc.example.com"
infrarust.proxy_mode: "client_only"
infrarust.network: "main"
infrarust.motd.text: "§aSurvival §7— §fWelcome!"
networks:
- minecraft
restart: unless-stopped
# ── Creative server ──────────────────────────────
creative:
image: itzg/minecraft-server
container_name: creative
environment:
EULA: "TRUE"
TYPE: "PAPER"
GAMEMODE: "creative"
MEMORY: "1G"
labels:
infrarust.enable: "true"
infrarust.domains: "creative.mc.example.com"
infrarust.proxy_mode: "client_only"
infrarust.network: "main"
networks:
- minecraft
restart: unless-stopped2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
Start everything with:
docker compose up -dPlayers connecting to mc.example.com reach the lobby. survival.mc.example.com and creative.mc.example.com route to their respective servers. All of this happens without any per-server config files.
How the labels work
Every Minecraft container needs infrarust.enable: "true" for Infrarust to pick it up. The rest is optional:
| Label | Default | What it does |
|---|---|---|
infrarust.enable | required | Must be "true" for discovery. |
infrarust.domains | <container_name>.docker.local | Comma-separated domains that route to this server. |
infrarust.port | 25565 | Minecraft port inside the container. |
infrarust.proxy_mode | passthrough | One of: passthrough, client_only, offline, server_only, zero_copy. |
infrarust.send_proxy_protocol | false | Set to "true" or "1" to send PROXY protocol headers. |
infrarust.name | none | Human-readable name for server switching. |
infrarust.network | none | Network group for server switching. |
infrarust.motd.text | none | Custom MOTD text shown in the server list. |
If you skip infrarust.domains, the container name becomes the domain with a .docker.local suffix. A container named lobby gets lobby.docker.local.
Networking
All services share the minecraft bridge network. Backend servers don't publish any ports to the host. Players connect to 25565 on the host, Infrarust routes the traffic internally to the right container by its Docker network IP.
This is the recommended setup. Backend servers stay unreachable from outside Docker, and you avoid port conflicts between multiple servers.
TIP
Set network = "minecraft" in your infrarust.toml [docker] section to match the Compose network name. This tells Infrarust which network IP to use when resolving container addresses.
Adding a server at runtime
Add a new service to your docker-compose.yml and run:
docker compose up -d newserverInfrarust detects the new container within seconds through the Docker event stream. No restart needed.
Removing a server works the same way. Stop or remove the container and Infrarust drops it from the routing table automatically.
Wildcard domains
You can route all subdomains of a domain to a single server:
labels:
infrarust.enable: "true"
infrarust.domains: "*.mc.example.com"2
3
Or list multiple domains on one server:
labels:
infrarust.enable: "true"
infrarust.domains: "mc.example.com, play.example.com"2
3
Custom port
If your server runs on a non-standard port inside the container:
modded:
image: itzg/minecraft-server
environment:
EULA: "TRUE"
SERVER_PORT: "25566"
labels:
infrarust.enable: "true"
infrarust.domains: "modded.example.com"
infrarust.port: "25566"
networks:
- minecraft2
3
4
5
6
7
8
9
10
11
Mixing Docker discovery and file-based servers
Docker and file providers feed into the same routing table. You can discover some servers from containers and define others as .toml files:
# infrarust.toml
bind = "0.0.0.0:25565"
servers_dir = "/app/config/servers"
[docker]
network = "minecraft"2
3
4
5
6
# config/servers/external.toml
domains = ["external.example.com"]
addresses = ["192.168.1.50:25565"]2
3
Containers get a provider ID like docker@lobby. File-based servers get file@external.toml. Both are visible in logs.
Docker socket security
WARNING
Mounting /var/run/docker.sock gives Infrarust read access to all containers on the host. Always mount it read-only (:ro). On production systems, consider running Docker in rootless mode or using a socket proxy like Tecnativa/docker-socket-proxy to restrict API access.
Troubleshooting
Container not discovered: Check that infrarust.enable is set to exactly "true" (string, not boolean). In YAML, quote it: infrarust.enable: "true".
Wrong IP resolved: If Infrarust connects to the wrong container IP, set network = "minecraft" in your [docker] section to pin address resolution to the Compose network.
Connection refused after startup: Minecraft servers take time to start. Players connecting before the server is ready will get disconnected. The server container must be fully started before it can accept connections.
Logs: Set RUST_LOG=debug on the Infrarust container to see provider events:
infrarust:
environment:
RUST_LOG: "debug"2
3
Next steps
- Docker Provider reference for all provider options
- Docker Deployment for
docker runcommands and building the image - Proxy Modes to understand
passthroughvsclient_onlyand other modes