Skip to content
/ mc-router Public

Routes Minecraft client connections to backend servers based upon the requested server address


Notifications You must be signed in to change notification settings


Repository files navigation

GitHub issues Docker Pulls test GitHub release Discord Buy me a coffee

Routes Minecraft client connections to backend servers based upon the requested server address.


  -api-binding host:port
        The host:port bound for servicing API requests (env API_BINDING)
        Increase Kubernetes StatefulSet Replicas (only) from 0 to 1 on respective backend servers when accessed (env AUTO_SCALE_UP)
  -connection-rate-limit int
        Max number of connections to allow per second (env CONNECTION_RATE_LIMIT) (default 1)
  -cpu-profile string
        Enables CPU profiling and writes to given path (env CPU_PROFILE)
        Enable debug logs (env DEBUG)
  -default string
        host:port of a default Minecraft server to use when mapping not found (env DEFAULT)
  -docker-refresh-interval int
        Refresh interval in seconds for the Docker Swarm integration (env DOCKER_REFRESH_INTERVAL) (default 15)
  -docker-timeout int
        Timeout configuration in seconds for the Docker Swarm integration (env DOCKER_TIMEOUT)
        Use in-swarm Docker config (env IN_DOCKER_SWARM)
        Use in-cluster Kubernetes config (env IN_KUBE_CLUSTER)
  -kube-config string
        The path to a Kubernetes configuration file (env KUBE_CONFIG)
  -mapping value
        Comma-separated or repeated mappings of externalHostname=host:port (env MAPPING)
  -metrics-backend string
        Backend to use for metrics exposure/publishing: discard,expvar,influxdb (env METRICS_BACKEND) (default "discard")
  -metrics-backend-config-influxdb-addr string
  -metrics-backend-config-influxdb-database string
  -metrics-backend-config-influxdb-interval duration
  -metrics-backend-config-influxdb-password string
  -metrics-backend-config-influxdb-retention-policy string
  -metrics-backend-config-influxdb-tags value
        any extra tags to be included with all reported metrics (env METRICS_BACKEND_CONFIG_INFLUXDB_TAGS)
  -metrics-backend-config-influxdb-username string
  -ngrok-token string
        If set, an ngrok tunnel will be established. It is HIGHLY recommended to pass as an environment variable. (env NGROK_TOKEN)
  -port port
        The port bound to listen for Minecraft client connections (env PORT) (default 25565)
  -routes-config string
        Name or full path to routes config file (env ROUTES_CONFIG)
        Simplify fully qualified SRV records for mapping (env SIMPLIFY_SRV)
        Send PROXY protocol to backend servers (env USE_PROXY_PROTOCOL)
        Output version and exit (env VERSION)

Docker Multi-Architecture Image

The multi-architecture image published at Docker Hub supports amd64, arm64, and arm32v6 (i.e. RaspberryPi).

Docker Compose Usage

The diagram below shows how this docker-compose.yml configures two Minecraft server services named vanilla and forge, which also become the internal network aliases. Notice those services don't need their ports exposed since the internal networking allows for the inter-container access.

version: "3.8"

    image: itzg/minecraft-server
      EULA: "TRUE"
    image: itzg/minecraft-server
      EULA: "TRUE"
    image: ${MC_ROUTER_IMAGE:-itzg/mc-router}
      - forge
      - vanilla
      MAPPING: |
      - "25565:25565"

The router service is only one of the services that needs to exposed on the external network. The MAPPING declares how the hostname users will enter into their Minecraft client will map to the internal services.

To test out this example, add these two entries to my "hosts" file:

Routing Configuration

The routing configuration allows routing via a config file rather than a command. You need to set -routes-config or ROUTES_CONFIG env variable. The following shows a JSON file for routes config, where default-server can also be null or omitted:

  "default-server": "vanilla:25565",
  "mappings": {
    "": "vanilla:25565",
    "": "forge:25565"

Kubernetes Usage

Using Kubernetes Service auto-discovery

When running mc-router as a Kubernetes Pod and you pass the --in-kube-cluster command-line argument, then it will automatically watch for any services annotated with

  • : The value of the annotation will be registered as the external hostname Minecraft clients would used to connect to the routed service. The service's clusterIP and target port are used as the routed backend. You can use more hostnames by splitting them with comma.
  • : The service's clusterIP and target port are used as the default if no other externalServiceName annotations applies.

For example, start mc-router's container spec with

image: itzg/mc-router
name: mc-router
args: ["--in-kube-cluster"]

and configure the backend minecraft server's service with the annotation:

apiVersion: v1
kind: Service
  name: mc-forge
    "": ""

you can use multiple host names:

apiVersion: v1
kind: Service
  name: mc-forge
    "": ","

mc-router will pick the service port named either minecraft or mc-router. If neither port names exist, it will use port value 25565.

Example Kubernetes deployment

This example deployment

  • Declares an mc-router service that exposes a node port 25565
  • Declares a service account with access to watch and list services
  • Declares --in-kube-cluster in the mc-router container arguments
  • Two "backend" Minecraft servers are declared each with an "" annotation that declares their external server name(s)
kubectl apply -f

  • This deployment assumes two persistent volume claims: mc-stable and mc-snapshot
  • I extended the allowed node port range by adding --service-node-port-range=25000-32767 to /etc/kubernetes/manifests/kube-apiserver.yaml
Auto Scale Up

The -auto-scale-up flag argument makes the router "wake up" any stopped backend servers, by changing replicas: 0 to replicas: 1.

This requires using kind: StatefulSet instead of kind: Service for the Minecraft backend servers.

It also requires the ClusterRole to permit get + update for statefulsets & statefulsets/scale, e.g. like this (or some equivalent more fine-grained one to only watch/list services+statefulsets, and only get+update scale):

kind: ClusterRole
  name: services-watcher
- apiGroups: [""]
  resources: ["services"]
  verbs: ["watch","list"]
- apiGroups: ["apps"]
  resources: ["statefulsets", "statefulsets/scale"]
  verbs: ["watch","list","get","update"]

Docker Swarm Usage

Using Docker Swarm Service auto-discovery

When running mc-router in a Docker Swarm environment you can pass the --in-docker-swarm command-line argument and it will poll the Docker API periodically to find all the running services for minecraft instances. To enable discovery you have to set the label on the service. These are the labels scanned:

  • Used to configure the hostname the Minecraft clients would use to connect to the server. The service endpoint will be used as the routed backend. You can use more than one hostname by splitting it with a comma.
  • mc-router.port: This value must be set to the port the Minecraft server is listening on. The default value is 25565.
  • mc-router.default: Set this to a truthy value to make this server the deafult backend. Please note that is still required to be set.
  • Specify the network you are using for the router if multiple are present in the service. You can either use the network ID, it's full name or an alias.

Example Docker Swarm deployment

Refer to this example docker-compose.yml to see how to configure two different Minecraft servers and a mc-router instance. Notice how you don't have to expose the Minecraft instances ports, but all the containers are required to be in the same network.


  • GET /routes (with Accept: application/json)

    Retrieves the currently configured routes

  • POST /routes (with Content-Type: application/json)

    Registers a route given a JSON body structured like:

      "backend": "HOST:PORT"
  • POST /defaultRoute (with Content-Type: application/json)

    Registers a default route to the given backend. JSON body is structured as:

      "backend": "HOST:PORT"
  • DELETE /routes/{serverAddress}

    Deletes an existing route for the given serverAddress


mc-router has built-in support to run as an ngrok agent. To enable this support, pass an ngrok authtoken to the command-line argument or environment variable, shown above.

Ngrok Quick Start

Create/access an ngrok account and allocate an agent authtoken from the dashboard.

In a new directory, create a file called .env with the allocated token


In the same directory, create the following compose file:

version: "3.8"

    image: itzg/minecraft-server
      EULA: true
      - mc-data:/data
    # No port mapping since mc-router connects over compose network
    image: itzg/mc-router
      DEFAULT: mc:25565
    # No port mapping needed since it routes through ngrok tunnel

  mc-data: {}

Start the compose project:

docker compose up -d

Grab the mc-router logs using:

docker compose logs router

From those logs, locate the ngrokUrl parameter from the "Listening" info log message, such as tcp://

In the Minecraft client, the server address will be the part after the "tcp://" prefix, such as


Building locally with Docker

docker build -t mc-router .

Build locally without Docker

After installing Go and doing a go mod download to install all required prerequisites, just like the Dockerfile does, you can:

make test # go test -v ./...
go build ./cmd/mc-router/


For "in-cluster development" it's convenient to use Any changes to Go source code will trigger a go build, new container image pushed to registry with a new tag, and refresh in Kubernetes with the image tag used in the deployment transparently updated to the new tag and thus new pod created pulling new images, as configured by skaffold.yaml:

skaffold dev

When using Google Cloud (GCP), first create a Docker Artifact Registry, then add the Artifact Registry Reader Role to the Compute Engine default service account of your GKE clusterService Account (to avoid error like "container mc-router is waiting to start: can't be pulled"), then use e.g. gcloud auth configure-docker or equivalent one time (to create a ~/.docker/config.json), and then use e.g. option for skaffold dev.

Performing snapshot release with Docker

docker run -it --rm \
  -v ${PWD}:/build -w /build \
  -v /var/run/docker.sock:/var/run/docker.sock \
  goreleaser/goreleaser \
  release --snapshot --rm-dist

Related Projects