Everything about Docker I learned from Minecraft

A Minecraft ContainerI’ve been experimenting with Docker a LOT recently.  The container mechanisms and structure make a lot of sense to the DevOps side of me, and I wanted to put this knowledge to good use.  My daughter’s cyber-school wanted a Minecraft server and I offered to help.  With a little bit of work and my handy Azure subscription, we got a great Minecraft server running in the cloud that I can redeploy anywhere I need.  Here’s how I set it up.

Containers 101

Before I dive too far into containers and configuration, let me just touch on what containers are, how they function, and what makes them the new hotness.  An ‘Application Container’ or simply Container, is a virtualization technology that provides a consistent install and use experience in a very portable and source-controlled unit.  In a standard hypervisor managed virtual machine, you the operator of the machine need to deploy and manage a new version of the operating system for each instance of the virtual machine that is created.  Conversely, the container model is built off of adding and configuring features on top of a standard host that has hypervisor functionality built into the operating system kernel.  The features are defined in a configuration file and are applied to create a ‘container image’ that can be used as the basis of many copied instances of running containers.  This model allows the container model to scale very quickly with new container instances coming online in just a few seconds instead of the minutes it would take to provision and start a new virtual machine on a hypervisor.

A Minecraft Docker Image

The container system that I chose to work with, Docker, has a repository of available images at the Docker Hub. Think of the Docker Hub as the ‘app store of docker images’ that you can freely download and use in your applications.  For my Minecraft server, I found the itzg\minecraft-server image that fit my needs very well.  This image can be used to install a number of different Minecraft servers and different versions based on various configuration options.  To lock in my preferred configuration options for our Minecraft server, I decided to customize the image.  With docker you can customize an image in similar way that you would a class in code: you can inherit from another docker image.

To customize a docker image, you start by writing a file named Dockerfile and you write commands into it about the image to inherit from and the configuration to apply to it.  My Dockerfile for my new server looks like this:

Lets look at what each line in this file does:

  • The FROM command defines the Docker image that my image will inherit from.  The image will be fetched from the hub.docker.com website if it is not already present on my server.
  • The MAINTAINER command is a note that shows who wrote this configuration and is used as a reference for anyone who may use this configuration going forward.
  • The ENV statements are environment variables that will be set for all instances of this image.  The text to the left of the = is the name of the variable to set and to the right is the value to assign.  For the minecraft-server image, these environment variables trigger different features of the server.
    • EULA = true indicates that the Minecraft end-user license agreement is accepted
    • VERSION is the version of the Minecraft server to create
    • MODE is the game mode of the Minecraft server
    • MOTD is the message of the day to display on the server list
    • PVP is a boolean indicating whether player vs. player attacking is enabled
    • DIFFICULTY is a setting in Minecraft that dictates how quickly life is regenerated, how quickly you get hungry, and how much enemies will hurt you.
    • LEVEL_TYPE is a definition of how a new world will be created
    • OPS is a list of Minecraft usernames that are given administrator access.  You betcha: I’m an operator on my server.
    • JVM_OPTS is a configuration for the Java virtual machine to allocate between 3GB and 1GB of RAM to the Minecraft service.  Without this, the service will use up to 1GB of RAM
  • EXPOSE is the definition of the service network port inside of the container to make available outside of the container.  The port needs to be mapped on the hosting machine to be able to be connected to from across the network.  The value 25565 is the default Minecraft service port.

Start the server!

With the dockerfile written on disk, I can now build the image for my server locally by running the following:

docker build -t minecraft:1 -t minecraft:latest .

This creates an Docker image, I like to think of it as a template, of the server configuration defined in the Dockerfile.  The two -t switches create a tag for my image named “minecraft” with a version of “1” and also mark version 1 as “latest”.  I can now start my server with the following command:

docker run -d -p 25565:25565 --name live --restart=always minecraft

The ‘run’ command will create a new instance of a container and start the command embedded in the Dockerfile.  In our case, the command is to start the minecraft server and is embedded in the parent itzg\minecraft-server image’d Dockerfile with the CMD directive on line 37.

The -d switch indicates that the container should run in ‘detached mode’.  That is, it will start and run in the background, giving me back control of my command prompt.  The -p switch and numbers proceeding it are the port mapping.  We want to take the container exposed 25565 port and map it to the same port on the Docker host.

The –name switch defines the name of the container, ‘live’ in this case.  The –restart=always switch configures the container to restart if it should crash and when the Docker service restarts should the host machine reboot.  Other options for the restart switch include:

  • no – never restart this container
  • always – always try to start this container on any failure or service restart
  • on-failure[:max-retry] – only restart this container when it exists with a non-zero return code, with an optional number of retries defined
  • unless-stopped – will always restart the container except on Docker service startup if the container has previously been put into a stopped state by an operator

Obviously I want the ‘always’ option, so that my kids and their friends can ALWAYS play Minecraft.  🙂

This is nice, it runs the server… but there are config options in the server.properties file that I know about that I want to further customize.  Those values are INSIDE of the container.  How can I get at those?  I want to open a prompt in the container, and can use this command to launch a bash shell:

docker exec -i -t live bash

This executes a command inside of a running container, like our live container from above.  By passing the -i switch, I enter interactive mode and using the -t mode allocates a TTY terminal to my connection.  Live is the name of the container I am attaching to, and bash is the command to execute.

Bash in Container

Bash in Container

Once in my bash shell, I can navigate around and use the vi editor to modify my server properties.  I can exit the container with the ‘exit’ command, but like I would with an ssh session.  Not bad…  but there are easier ways to do this, and I want to be able to get backups as well. To do that, I’ll use a docker volume.

Introducing Docker Volumes

Docker volumes allow you to map a folder on the host machine to the container.  In my case, I want to map the /data folder of my minecraft server to my local ~/minecraftserver folder.  From my minecraftserver folder, I can copy the content and edit it while the container is operating.  To map this volume, I just expand the run command with an extra -v switch declaring the mapping:

docker run -d -v /home/jfritz/minecraftserver:/data -p 25565:25565 --name live --restart=always minecraft

Nice… now when I start the server with this command, I get all of my server configuration and world content written to my minecraftserver folder. I can easily update settings and restart the server with the following command:

docker restart live

This makes management of the server so much easier.

Summary

This was just a first pass at getting a server running, but I learned a lot in researching and adding all of these configuration options.  But now my kids are asking for server mods and resource packs.  Once I work through adding those to the server, I’ll write another post sharing my experience again.

Hey, I wrote a course on WintellectNow about using Docker with ASP.NET Core.  Check it out with your free WintellectNow hours in an MSDN Subscription.