How to Run a Minecraft Server: Multiple Approaches

Photo by Growtika on Unsplash

How to Run a Minecraft Server: Multiple Approaches

Background

In An intro to Minecraft mods, I outline my reasons for choosing the Fabric framework over Forge. Continuing with that same reasoning, this post will be outlining multiple approaches for running a Minecraft server using Fabric (although at a high level, the concepts should still apply to Forge).

You may want to do some research beforehand to determine which mods you want to run on your server, since Fabric mods will only run on a Fabric server, and Forge mods will only run on a Forge server.

If you're not sure what a Minecraft server is, you can simply think of it as a place to house Minecraft mods. To get a better idea of how a server fits into modding in general, read the Fabric Server section of the aforementioned post.

Overview

You might be wondering where the "multiple approaches" bit comes in, since we are only focusing on Fabric. We'll be covering the following ways to host a Fabric server:

  • local machine

    • Windows

    • Linux/MacOS

  • VPS

Regardless of where the server is hosted, it can be invoked (or "spun up") in multiple ways:

  • raw commands in a shell

  • running a script file

  • running the server in a Docker container

Choosing a server location

In the Development Environment section of the first post, we covered the fact that your development environment matters, in terms of which computer you use. Conversely, the computer you choose to use as your server doesn't matter at all -- so long as it has the proper version of Java and enough RAM to run Minecraft.

Separate machine vs local machine

Speaking of RAM, that is one reason you might choose to run your server on a separate computer; the default amount of RAM allocated to the Minecraft server is 2GB, which your computer may not have to spare.

Even if RAM isn't an issue for you, you may still want a separate machine for other reasons -- a big one being availability; if you want your server to be accessible round-the-clock, you likely want a separate machine. Otherwise, your server goes down if your computer shuts off or loses network connectivity.

On the other hand, if your server needs are more casual and constant availability isn't a concern, then running the server locally (from your main machine) might be enough for you.

A new computer isn't necessary

You also might be thinking, "I can't afford to buy another computer to run a server on". If that's the case, you could explore these options:

  • using an old computer

  • buying a Raspberry Pi (can be found for less than $100)

  • using a Virtual Private Server (monthly cost depending on usage and server specs)

Docker

Docker might be a good option if you want to be able to reuse the same server configuration on multiple devices, or if you don't want to alter an existing Java configuration. Here are a few things to note about running a server in a Docker container:

  • it will still consume RAM on whatever computer is running the container

  • no Java configuration is required

    • Java is automatically installed in the container
  • Docker containers don't "care" where they are run from

    • you can bundle the container's configuration into a file on your local machine

    • that same file can be used to run an identical container on a separate machine (regardless of that machine's OS)

  • Docker can run from

    • your local machine

    • another physical machine

      • an old laptop

      • a Raspberry Pi

    • a Virtual Private Server

    • ...or any other hardware that supports Docker.

Here's an example of when you might want to use Docker:

You are testing out a Minecraft server configuration with multiple mods, and multiple custom game settings. You're testing this configuration out on your laptop, but you want to be able to recreate the configuration on another machine without having to install each mod and enable each custom game setting all over again.

This is a great reason to use Docker. At a high level, the configuration would look something like this:

  • find and download a Docker image that is preconfigured for running a Minecraft server

    • think of the image as the blueprint for the container
  • use the image to run a container

    • the image's blueprint is used to construct and "run" the container

    • think of a container as a self-contained environment for which to run software

  • load the mods you want to test into the container via a volume

    • a volume is a way to link file storage between the host machine and the container

      • the host machine is the computer that Docker is installed on
    • the volume is where the /mods folder will live

  • once you've decided on the game settings and mods you want to use, put instructions on how the container should be run in a docker-compose.yml file

    • Docker Compose files describe the specifics of how the server container be run, such as

      • the version of Minecraft to use

      • which user(s) to grant operator permissions

      • game settings (max players, spawn point, mobs, etc.)

We'll illustrate these concepts with examples later in the Running a Server with Docker section.

Running a Server

Java Prerequisite

If you plan on running a server on a machine other than your own, make sure to install Java on that machine, following the instructions provided by Fabric.

If you're using Docker, make sure the image you're using has the correct version of Java. The image likely has a way to specify a Java version with a tag for the image.

If you're using Linux for your server, the instructions Fabric provides for setting up Java are fairly basic. For more detailed instructions, see Installing Java on Ubuntu.

Loading a mod onto the server

For mods to work on the server, we need to make sure that our Minecraft client has those same mods. More specifically, that means:

  • you have an installation of the Fabric launcher (client) whose version matches the Minecraft version of your server

    • this is the launcher you select when you open the Minecraft application on your computer
  • the /mods folder for your Fabric launcher installation contains:

    • the .jar file for the proper version of the Fabric API

    • a .jar file for each mod

  • the /mods folder for your Fabric server must contain the same .jar files as the /mods folder in the Fabric launcher

In other words, the /mods folder within your Fabric launcher client should have the same contents as the /mods folder on your Fabric server. If you want to read more about the reasoning, check out the Fabric docs on this topic.

  • once the /mods folders match on the client and server, launch the server

  • once the server is running, grant op (operator) permissions to necessary player(s)

  • connect to the server using the Connecting to your server instructions below

  • verify that you can use the mod

Approach-specific instructions are detailed below.

Running a Server on Linux/MacOS

This is perhaps the most accessible option since it can be run from:

  • a machine running Linux

    • Raspberry Pi

    • VPS

  • a machine running MacOS

Here we'll follow instructions from the Fabric wiki, and walk through them as we go.

If you've been following along, you should already have Java installed. The next step is to go to the server download page and download the relevant version:

# make a directory for the server
mkdir fabric-server
cd fabric-server

# download the file to our fabric-server directory
curl -OJ https://meta.fabricmc.net/v2/versions/loader/1.19.4/0.14.19/0.11.2/server/jar

# confirm the download worked
ls
# output
fabric-server-mc.1.19.4-loader.0.14.19-launcher.0.11.2.jar

Now that we have the server's .jar file downloaded, we can use the command supplied on the download page to launch the server using that .jar file:

# launch the server, allocating 2GB RAM
java -Xmx2G -jar fabric-server-mc.1.19.4-loader.0.14.19-launcher.0.11.2.jar nogui

We get an error, which is expected because we need to accept the EULA:

[21:10:21] [main/ERROR]: Failed to load properties from file: server.properties
[21:10:21] [main/WARN]: Failed to load eula.txt
[21:10:21] [main/INFO]: You need to agree to the EULA in order to run the server. Go to eula.txt for more info.

If we inspect the contents of our server folder, we can see that files and directories have been generated as a result of attempting running the server. Among these is the file eula.txt:

# in fabric-server directory
ls -la # press enter

# output
drwxr-xr-x  4 idev idev   4096 Jun 12 21:10 .fabric
-rw-r--r--  1 idev idev    158 Jun 12 21:10 eula.txt
-rw-r--r--  1 idev idev 155095 Jun 12 21:09 fabric-server-mc.1.19.4-loader.0.14.19-launcher.0.11.2.jar
drwxr-xr-x  8 idev idev   4096 Jun 12 21:10 libraries
drwxr-xr-x  2 idev idev   4096 Jun 12 21:10 logs
drwxr-xr-x  2 idev idev   4096 Jun 12 21:10 mods
-rw-r--r--  1 idev idev   1272 Jun 12 21:10 server.properties
drwxr-xr-x  3 idev idev   4096 Jun 12 21:10 versions

Now we can open that file with our preferred text editor and change false to true:

vim eula.txt

# inside eula.txt
eula=true
# save and exit eula.txt

Now that we've agreed to the EULA, we can re-run the command to launch the server:

# launch the server, allocating 2GB RAM
java -Xmx2G -jar fabric-server-mc.1.19.4-loader.0.14.19-launcher.0.11.2.jar nogui

The server starts up this time with no errors:

[21:15:16] [Worker-Main-2/INFO]: Preparing spawn area: 91%
[21:15:16] [Worker-Main-2/INFO]: Preparing spawn area: 94%
[21:15:17] [Worker-Main-5/INFO]: Preparing spawn area: 97%
[21:15:17] [Server thread/INFO]: Time elapsed: 28858 ms
[21:15:17] [Server thread/INFO]: Done (35.063s)! For help, type "help"

Now that the server is running, we can click into the terminal and type op your_player_name

[14:24:08] [Server thread/INFO]: Time elapsed: 8357 ms
[14:24:08] [Server thread/INFO]: Done (10.491s)! For help, type "help"
op awesomeminecraftplayer42023
[14:25:01] [Server thread/INFO]: Made maddoxchunk a server operator

Running a Server on Windows

The Fabric instructions do a pretty good job of walking through this one, but we'll go through the steps here anyway (starting with STEP 3: Install Fabric in the Server Folder):

  • go here and click the 'download for Windows' button

  • run the file

  • select the 'Server' tab

  • select the version and install the location

    • the Minecraft version should match the Minecraft version of the mod(s) you want to use

    • note that this file location has nothing to do with your installation of Minecraft

  • press 'Install'

  • click 'Download server jar'

  • click 'Generate'

  • open the folder location where you saved the server to

  • run the start.bat file

  • you'll get an error about the EULA

  • close the terminal

  • open eula.txt in the server folder that has the start.bat file

  • change false to true in eula.txt

  • save and exit the file

  • run start.bat again

  • click into the terminal with the server running and type op your_player_name to grant operator permissions

Running a Server with Docker

I won't go over installing Docker, since their official docs should be enough to get you there.

It's worth mentioning that once you have Docker installed, the steps below will work the same (more or less), regardless of your operating system.

Run a Vanilla Minecraft server in a container

Once you've got Docker installed, it's time to download and run a Minecraft server image.

  • start by going to the documentation for the Docker image we're using

  • run the listed command

    • docker run -d -it -p 25565:25565 -e EULA=TRUE itzg/minecraft-server

    • this runs the container in the background so that our terminal is free to run other commands

    • confirm the container is running with docker ps

  • copy the CONTAINER ID from the docker ps command

  • run docker stop <CONTAINER ID> to stop the container

  • run docker ps again to confirm the container is not running

Run a Fabric Minecraft server in a container

The docker command above allowed us to run a "vanilla" Minecraft server. However, we are interested in running a Fabric server, so we consult the Types and platforms documentation.

From here we can select 'Fabric' from the menu on the left. We can see a command that instructs us on how to run a Fabric server by using the -e TYPE=FABRIC flag. However, there is something else that's different about this command: it's mounting a volume with -v /path/on/host:/data. Before we run this command, we need to set up that directory on our host machine (the one running Docker).

Mounting a data directory

If we head to the Data directory documentation, we get some instructions on how to do this.

Let's try running a container with an attached data volume, using the supplied command from the Fabric section of the image docs:

mkdir mc-data

docker run -d -v ./mc-data:/data \
    -e TYPE=FABRIC \
    -p 25565:25565 -e EULA=TRUE --name mc itzg/minecraft-server

Okay, so we've started a container and mounted the volume, but how do we use it?

It's a bit difficult to picture right now, since we're running the container as a background process with the -d flag. Let's try running it again without that flag and see what happens:

# first, remove/stop the container
docker rm mc

# run the same command without the -d flag
docker run -v ./mc-data:/data \
    -e TYPE=FABRIC \
    -p 25565:25565 -e EULA=TRUE --name mc itzg/minecraft-server

# output
[init] Running as uid=1000 gid=1000 with /data as 'drwxrwxr-x 2 1000 1000 4096 Jun 15 11:12 /data'
... # (more output)
[init] ERROR: version lookup failed:

This isn't an error with the volume. We would get the same error if we ran the command without the -v flag. The only reason we didn't see the error before is that we ran the process in the background, so the output wasn't visible.

This error is because we need to specify a Fabric/Minecraft version for the container to run.

Let's try it again, this time using the appropriate flags to specify the version:

docker rm mc

docker run -v ./mc-data:/data \
    -e TYPE=FABRIC \
    -e VERSION=1.19.4 \
    -p 25565:25565 -e EULA=TRUE --name mc itzg/minecraft-server

We still get an error, but this time it's different:

[init] ERROR failed to install Fabric launcher from 1.19.4, LATEST, LATEST

This is yet another misleading error message. In my case, it was happening due to an issue with WSL2. The issue is resolved by deleting the ~/.docker/config.json file.

If you encountered the error above, go ahead and delete the config.json file, then run the commands again to make sure it works:

docker rm mc

docker run -v ./mc-data:/data \
    -e TYPE=FABRIC \
    -e VERSION=1.19.4 \
    -p 25565:25565 -e EULA=TRUE --name mc itzg/minecraft-server

Now that we know we can get the server running, it's time to add our mod.

Loading mods onto the server

Looking again at the Data directory documentation, we can see that the /mods folder lives inside the /data directory, which we have created as mc-data on our host machine.

Speaking of the data directory, if we inspect the contents of mc-data, we can see that the folder is no longer empty since we have successfully run the server:

# stop the server if it's still running using CTRL + C
ls mc-data

#output
banned-ips.json      fabric-server-mc.1.19.4-loader.0.14.21-launcher.0.11.2.jar  ops.json           whitelist.json
banned-players.json  libraries                                                   server.properties  world
crash-reports        logs                                                        usercache.json
eula.txt             mods                                                        versions

Recall from Loading a mod onto the server that the /mods folder on our server should match the /mods folder in our Fabric launcher installation. As such, we copy the contents of that folder to our data directory for the container

# copy the contents of the Fabric 
# launcher's /mods folder into the mc-data/mods folder
cp -a /path/to/fabric/launcher/mods .mc-data/mods

Now we are ready to run our server again:

docker rm mc

docker run -v ./mc-data:/data \
    -e TYPE=FABRIC \
    -e VERSION=1.19.4 \
    -p 25565:25565 -e EULA=TRUE --name mc itzg/minecraft-server

Go ahead and connect to your server using the instructions below.

The game loads up when we connect, but we have a problem -- we aren't able to run commands. Normally, we could just go to our terminal and op our player, but things work a bit differently with Docker. Since we are running the server in a container, we don't have direct access to the terminal like we do when we run the server directly in the operating system.

So how do we issue commands to our Docker container? Enter RCON.

RCON

RCON is short for Remote Control, and allows Minecraft commands to be issued remotely.

Okay, but... how do we use it?

Thankfully, the Docker image we're using includes rcon-cli. Let's see it in action:

# stop the container (if running)
docker stop mc

# start the container again
docker start mc

Before we can run rcon-cli, we need to wait for server to completely load, since RCON does not become enabled until that point. When you see the following message in the terminal, you're ready to use rcon-cli:

[03:28:23] [Server thread/INFO]: Done (8.096s)! For help, type "help"
[03:28:23] [Server thread/INFO]: Starting remote control listener
[03:28:23] [Server thread/INFO]: Thread RCON Listener started
[03:28:23] [Server thread/INFO]: RCON running on 0.0.0.0:25575

Once the RCON service has started, we can open a new terminal window and use rcon-cli:

# replace your_player_name with the actual name of your player
docker exec mc rcon-cli op your_player_name

# output
Made your_player_name a server operator

Now that you are an operator, you can use commands in Minecraft. Go ahead and use the appropriate command(s) to test out your mod (if applicable).

Docker Compose

When I originally explained the use cases for Docker, a big selling point was the portability, and the ability to reuse configurations. So far, we haven't seen how Docker can achieve that. We ran a bunch of commands, and even hit some errors in the process.

How can a workflow like that be reusable?

The answer is that it can't, and that's where Docker Compose comes into play.

Now that we have a working configuration, we can package that configuration into a docker-compose.yml file so that we don't have to remember the commands. Instead, we can just point Docker to the docker-compose.yml file.

Let's go through the instructions:

  • first install Docker Compose by following the instructions here.

  • make a new directory and change into it

      mkdir mc-compose 
      cd mc-compose
    
  • create a docker-compose.yml file and put the following in it

note that the file below is slightly different from the file linked in the documentation (we added the VERSION and TYPE environment variables)

also note that indentation matters in a .yml file

  •       version: "3.8"
    
          services:
            mc:
              image: itzg/minecraft-server
              tty: true
              stdin_open: true
              ports:
                - "25565:25565"
              environment:
                EULA: "TRUE"
                VERSION: "1.19.4"
                TYPE: "FABRIC"
              volumes:
                # attach the relative directory 'data' to the container's /data path
                - ./data:/data
    

Using an .env file

To make things even more reusable, we can use an .env file located in the same folder as our docker-compose.yml file.

vim .env

# .env
VERSION=1.19.4
SERVER_TYPE=FABRIC
DATA_DIR=./mc-data

Now we can update our docker-compose.yml:

# docker-compose.yml
version: "3.8"

services:
  mc:
    image: itzg/minecraft-server
    container_name: mc
    tty: true
    stdin_open: true
    ports:
      - "25565:25565"
    environment:
      EULA: "TRUE"
      VERSION: "${VERSION}"
      TYPE: "${SERVER_TYPE}"
    volumes:
      # attach the relative directory 'data' to the container's /data path
      - "${DATA_DIR}:/data"

Run the server with docker compose up to make sure the new config works.

Automating RCON

Remember how we had to wait for the server to spin up before we could use rcon-cli? That was kind of annoying. It would be nice if there were a way to automate this.

Thankfully, someone made a pull request, and some new commands were added to the image. These commands allow rcon-cli to run automatically, either when the server starts, or when the game client connects to the server.

We'll keep things simple and just automate the provisioning of operator permissions. In the docker-compose.yml file below, you'll notice a new RCON_CMDS_STARTUP property under environment:

# docker-compose.yml
version: "3.8"

services:
  mc:
    image: itzg/minecraft-server
    container_name: mc
    tty: true
    stdin_open: true
    ports:
      - "25565:25565"
    environment:
      EULA: "TRUE"
      VERSION: "${VERSION}"
      TYPE: "${SERVER_TYPE}"
      RCON_CMDS_STARTUP:  |-
        /op your_username_goes_here
    volumes:
      # attach the relative directory 'data' to the container's /data path
      - "${DATA_DIR}:/data"

Go ahead and run the container with docker compose up. If you get an error like Additional property RCON_CMDS_STARTUP is not allowed, the issue is likely that something isn't indented properly. Go back into the file and make sure the new command is indented one level in from environment.

But remember, we want this docker-compose.yml file to be as reusable as possible. Once it's complete, any changes that we need to make to the configuration should be done via the .env file. Let's put our player name into the .env file:

vim .env

# .env
VERSION=1.19.4
SERVER_TYPE=FABRIC
DATA_DIR=/mnt/c/Users/iDev/fabric-1.19.4-client/
OP_PLAYER=your_username_goes_here

Now we can adjust our docker-compose.yml file accordingly:

# docker-compose.yml
version: "3.8"

services:
  mc:
    image: itzg/minecraft-server
    container_name: mc
    tty: true
    stdin_open: true
    ports:
      - "25565:25565"
    environment:
      EULA: "TRUE"
      VERSION: "${VERSION}"
      TYPE: "${SERVER_TYPE}"
      RCON_CMDS_STARTUP:  |-
        /op ${OP_PLAYER} # note that there are no quotes in this command
    volumes:
      # attach the relative directory 'data' to the container's /data path
      - "${DATA_DIR}:/data"

Note that the syntax is slightly different for this command, compared to the others. There are no quotes around the command. Using quotes will cause an error.

It's also possible to specify more than one player, but the error handling still needs to be worked out. This means that if you make a typo in one player's name, or one of the players doesn't exist, the entire command will fail.

If you're interested in experimenting with this, the syntax for specifying multiple players is as follows:

# .env
# OP_PLAYERS=player1:player2:player3 # this will fail; no valid players
OP_PLAYERS=your_user_name_goes_here:player2 # this will fail; at least one invalid player

Reusing a Docker config

So far, we've seen how using Docker Compose can save us from having to type long complicated commands. We can take it a step further and place our docker-compose.yml file on another machine so that it can run the same container with the same configuration.

First things first, we'll copy the file from our local machine over to a Virtual Private Server that has Docker installed. Also note that a user named mc-admin has already been created on the VPS:

# in mc-compose folder on local machine
# make sure to add the '/' at the end of vps-mc!
rsync -a ./docker-compose.yml digitalocean.minecraft:/home/mc-admin/vps-mc/

If you're unfamiliar with rsync, here is what the command above does:

  • takes the docker-compose.yml file in our current directory on the local machine

  • uses SSH behind the scenes to log into the VPS (digitalocean.minecraft)

    • an SSH config file obscures the username and IP of the Digital Ocean VPS we're logging into

    • to execute without a SSH config file:

      • replace digitalocean.minecraft with user@ip.address.of.vps
  • creates a directory vps-mc/ on the VPS, under the existing mc-admin user's /home directory

    • this is why the / is important at the end of the command

    • without the /, vps-mc will be created as a file instead of a folder

  • places the docker-compose.yml file in the /home/mc-admin/vps-mc directory of the VPS

Since I've previously configured ssh, I don't need to supply a username and password when I run the command above.

If you haven't configured ssh, it would look more like this:

# we need to specify username@server
rsync -a  ./docker-compose.yml mc-admin@docker.vps.ip:/home/mc-admin/vps-mc/

If you want to learn how to simply your ssh flow like I have, make sure to check out my Managing SSH Keys post.

Before we run the server, we need to transfer over a few more files and folders:

rsync ./.env digitalocean.minecraft:/home/mc-admin/vps-mc/
rsync -a --mkpath ./mc-data/mods/ digitalocean.minecraft:/home/mc-admin/vps-mc/mc-data/mods/

Now we're ready to run the server on the VPS:

ssh digitalocean.minecraft

# now in vps
cd vps-mc
docker compose up

# output
permission denied while trying to connect to the Docker daemon socket at 
unix:///var/run/docker.sock: 
Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json?all=1&filters=%7B%22label%22%3A%7B%22com.docker.compose.config-hash%22%3Atrue%2C%22com.docker.compose.project%3Dpi-mc%22%3Atrue%7D%7D": 
dial unix /var/run/docker.sock: connect: permission denied

Okay, so we got a permissions error. No biggie, this is a pretty common thing to run into with Docker. I had already configured it on my local machine, but we need to set it up on the VPS.

Enabling rootless Docker

Yes, prefixing Docker Compose command with sudo would fix our issue, but running Docker as root goes against best practices, so we're going to do it the proper way:

You can follow along with this step by viewing the Docker documentation.

  • first we'll start by updating our package manager

    • sudo apt-get update && sudo apt-get upgrade
  • next we'll install the package that Docker tells us we need

    • sudo apt-get install uidmap
  • follow the instructions under Distribution-specific hint

    • sudo apt-get install -y dbus-user-session

    • the package is already installed, so no need to log out/back in

  • follow the instructions under the Install section

    • dockerd-rootless-setuptool.sh install

    • receive command not found error

    • continue following instructions: sudo apt-get install -y docker-ce-rootless-extras

    • received another error: Unable to locate package docker-ce-rootless-extras

    • switch to the Without Packages tab and run curl -fsSL https://get.docker.com/rootless | sh

The installation was successful, but we have some output to read through:

# output from the install script
[INFO] Installed docker.service successfully.
[INFO] To control docker.service, run: `systemctl --user (start|stop|restart) docker.service`
[INFO] To run docker.service on system startup, run: `sudo loginctl enable-linger sharif`

[INFO] Creating CLI context "rootless"
Successfully created context "rootless"
[INFO] Using CLI context "rootless"
Current context is now "rootless"

[INFO] Make sure the following environment variable(s) are set (or add them to ~/.bashrc):
export PATH=/usr/bin:$PATH

[INFO] Some applications may require the following environment variable too:
export DOCKER_HOST=unix:///run/user/1000/docker.sock

Going through the sections of output one by one:

  • since I want Docker to run when the system starts, I run the provided command

    • sudo loginctl enable-linger sharif
  • I need to set both the PATH and DOCKER_HOST environment variables

    • I'm using zsh and not bash, so environment variables are set differently

    • I refer to a previous blog post of mine to set the environment variables at the profile level

Now we should be able to run docker compose up from our vps-mc directory again without getting a permissions error.

Connecting to your server

On our computer with Minecraft installed, we can open our Minecraft Launcher, and confirm that we can connect to the server.

  • select the Fabric Launcher installation

    • make sure the version of Minecraft for the launcher aligns with the version of Minecraft for your server
  • when the game loads, select 'Multiplayer'

  • accept the warning about third-party online play

  • select 'Direct Connection'

  • depending on how you set up your server, you'll need to enter the hostname accordingly

    • local server: localhost:25565

    • VPS: <IP of your server>:25565

  • click 'Join Server'

Resources