This is part 5 of an article series. You can read the previous one here

My friends and I were wanting to have our own Minecraft Java server to play on but we didn’t want to pay a hosting service to host it. So I decided to see if the S***box Laptop Server can run a Minecraft Java Server.The S***box Laptop Server was a gaming laptop so it has fairly decent resources that are just sitting there (because a file server barely uses any resources lol)

How do you run a Minecraft Server?

At first I was just going to download the server binary, create a systemd service and just run it. This takes alot of manual intervention and I would rather just have it run in the background no matter what.

Then I found: this, an all-in-one Docker container that runs a Minecraft server with tons of configuration options such as: what server binary to use (vanilla, paper, forge, etc.), how much RAM the server should have, what mods/plugins should be installed and so much more. There are so many configuration options, that there exists a website that creates a Docker compose file for you based on what you need, you can find it here.

This made setting up the server super easy and within a few minutes it was up and running.

How can we access it from the public internet

Now the next logical step would be to port forward so that others who don’t live in my building can play on the server. However, I live in a dorm where I can’t access the router to port forward the server, and I don’t think IT would even read an email asking them to do it.

What I needed is a way to tunnel the server to the public internet.

Can we use TailScale?

Short answer, no.

Long answer, yes, but everyone would have to install the TailScale client and I would need to give them my login which I do not want to do. It’s both a hassle for the players and a security risk for me.

So what can I use instead?

playit.gg

playit.gg is a service that is made for tunneling game servers, and they even have a configuration for Minecraft Java.

Setup was just as simple as TailScale, create account, install client, create tunnel to port 25565 (default port of MC Java) and you get a URL that can be accessed from the public internet.

One thing I highly reccommend when setting up playit: make sure to run every command to set it up with sudo, because this will save the authentication information in a place that the systemd service playit.gg creates can access it so the tunnel will always restart and always run in the background.

My server configuration

I didn’t need much for my server, all I setup was a whitelist and a few performance mods.

Here is my Docker compose file:

services:
  mc:
    image: itzg/minecraft-server:latest
    container_name: mc-server
    tty: true
    stdin_open: true
    restart: always
    ports:
      - "25565:25565"
    environment:
      EULA: "TRUE"
      TYPE: "FABRIC"
      MEMORY: "2048M"
      MAX_PLAYERS: "30"
      ONLINE_MODE: "true"
      MOTD: "My Server"
      TZ: "America/Vancouver"
      ENABLE_WHITELIST: "true"
      ICON: "/data/logo.png"
      WHITELIST: |-
        players here... 
      OPS: |-
        operators here...
      MODRINTH_PROJECTS: |-
        # Just a few performance/QOL mods
        P7dR8mSH
        WTzuSu8P
        Ps1zyz6x
        c7m1mi73
        VSNURh3q
        Wnxd13zP
        gvQqBUqZ
        uXXizFIs
    volumes:
      - "./data:/data"

Automatic Server Restarts

One thing I like to have with my MC servers is some way for the servers to automatically restart at midnight each night. I find this helps with performance by getting rid of entites, items on the ground etc. So I need someway to schedule a cron job that runs a script that notifies the players that the server is restarting and then restarts it.

I could just do that on the host system and run docker stop mc-server but then it would forcibly shutdown the server, causing some world data to be lost.

Luckily Minecraft supports the rcon (remote console) protocol so I can have another container run commands to safely shutdown the server and notify the players.

I wrote this basic bash script:

#!/bin/bash
docker exec -d mc-server rcon-cli say "Server Restarting in 15 Seconds"
sleep 15
docker exec -d mc-server rcon-cli say "Server Restarting..."
docker exec -d mc-server rcon-cli stop

Then I just ran it as a cron job in another Docker container, with the Docker Engine socket mounted to it and the Docker CLI installed.

The final Docker Compose script looks something like this:

services:
  mc:
    image: itzg/minecraft-server:latest
    container_name: mc-server
    tty: true
    stdin_open: true
    restart: always
    ports:
      - "25565:25565"
    environment:
      EULA: "TRUE"
      TYPE: "FABRIC"
      MEMORY: "2048M"
      MAX_PLAYERS: "30"
      ONLINE_MODE: "true"
      MOTD: "My Server"
      TZ: "America/Vancouver"
      ENABLE_WHITELIST: "true"
      ICON: "/data/logo.png"
      WHITELIST: |-
        players here... 
      OPS: |-
        operators here...
      MODRINTH_PROJECTS: |-
        # Just a few performance/QOL mods
        P7dR8mSH
        WTzuSu8P
        Ps1zyz6x
        c7m1mi73
        VSNURh3q
        Wnxd13zP
        gvQqBUqZ
        uXXizFIs
    volumes:
      - "./data:/data"
  # Daily Restarter Service
  restarter:
    image: docker:cli
    container_name: mc-restarter
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - "./scripts:/scripts"
    entrypoint: ["/bin/sh", "-c"]
    command:
      - |
        echo "0 0 * * * source /scripts/restart.sh" > /etc/crontabs/root
        crond -f -L /dev/stdout