Tuesday 22 September 2015

Dockerizing Minecraft Forge

This is a post about how I used Docker to run a Minecraft server, then taking that as a base installed the Forge modding framework to create new containers with custom mods installed. It's a bit of a journey through learning Docker with a bit of Minecraft-specific detail (and frustration) thrown in.

A Basic Server

This started out with my daughter wanting a Minecraft server. Whilst pretending to be a DevOp, I thought that the best way to run services these days is with Docker, so that's what I did. Minecraft only requires Java to run, so naturally I used the library/java:latest container as a base. My Dockerfile looked a bit like this:

FROM library/java:latest
RUN mkdir /mc
WORKDIR /mc
ADD \
  banned-ips.json \
  banned-players.json \
  eula.txt \
  https://s3.amazonaws.com/Minecraft.Download/versions/1.8.8/minecraft_server.1.8.8.jar \
  ops.json \
  server.properties \
  whitelist.json \
  /mc/
EXPOSE 25565
CMD ["java", "-Xmx1024M","-Xms1024M","-jar","minecraft_server.1.8.8.jar","nogui"]

Most of the added files were simply empty files in the current directory, with the exception of eula.txt which needed to contain the line "eula=true" to indicate acceptance of the license agreement.

This could then be built and started with docker build and docker run and all was good. Until a new version of Minecraft came out and I needed to upgrade the container but keep the world data.

Data Volume Containers

In order to persist data across different containers, or their different forms, a separate container can be created who's only job is to look after a data volume. This volume can then be attached to another container. The data volume container does not run, but it does define the mount point for the volume in the consumer container. Since the container didn't need any setting up, this was possible to create without a Dockerfile, using a command like:

docker create -v /mc/world --name minecraft-data library/java /bin/true

This could then be attached to the "application" container by specifying the --volumes-from argument to docker run:

docker run -d --volumes-from minecraft-data minecraft

Docker Compose

This process worked fine but was a little fragile as it depended on the right docker commands being issued and I always forget which docker command does what! (Hence my above commands are an approximation of what I actually did). Enter docker-compose; a tool to provision an application stack comprising of many containers. This consumes a YAML file describing your application and can build, run and destroy your application's containers. In my case, I needed to define two containers; one containing the Minecraft server, the other for its data. It looks like this:

minecraft:
  build: .
  ports: 
    - 25565:25565
  volumes_from:
    - minecraft-data
minecraft-data:
  image: library/java:latest
  volumes:
    - /mc/world

This configuration creates a minecraft-data container from an image and attaches a volume to /mc/world. This is then used by the minecraft container, who also exposes port 25565 to the external network. To bring the stack up, simply run docker-compose up.

Layering

When modding Minecraft, it is necessary to install a modding library. The most popular is Forge. Installing this was quite tricky (perhaps I didn't follow the instructions closely enough) but I eventually got a build working. It turns out that Docker was a really good platform choice for this, as I needed to run a different version of Minecraft for the particular mod that my daughter wanted, and I could easily run several in isolation - each with a different combination of mods. I started by creating tags for my plain Minecraft server images:

docker build . --tag alanraison/minecraft:1.8.8

The mod I was trying to install needed Minecraft 1.7.10, so with a quick edit of the Dockerfile to download the code at that version, I could then run

docker build . --tag alanraison/minecraft:1.7.10

And I had working images for both versions of Minecraft.

This could then be used in the Forge container as the base filesystem:
FROM alanraison/minecraft:1.7.10

And with the necessary ADDs and RUNs I had a mod environment; this then got tagged as alanraison/minecraft-forge:1.7.10

Finally I was able to create a container for all this containing just the mod I wanted to load:

FROM alanraison/mincraft-forge:1.7.10
RUN mkdir -p /mc/mods
ADD <modfile>.jar /mc/mods

And this started up (using docker-compose) with the mod loaded. Unfortunately there are still some teething issues so I haven't been able to fully run the mod from a client, but this is most likely some deficiency in the way I have installed Forge.

Docker Hub

Of course, I stored all of this configuration code in GitHub, but I also thought I could put the images up for all to use in Docker Hub. This was initially as easy as running docker push alanraison/minecraft:1.8.8, which pushed my local tag of the same name to dockerhub. After a bit of fiddling around, however, I found that dockerhub would even build it straight out of my github project. To do this, log in to dockerhub and create an automated build. I had to delete the repos I'd created by pushing from my machine, since there would otherwise be a naming clash, but otherwise it was simple; you can chose to build from a branch or a tag and there are hooks in GitHub to start a dockerhub build when the code changes.

See the code! Use the code!

The source code can be found at https://github.com/alanraison/minecraft-docker; look especially at the different branches and tags.

The dockerhub builds can be found at https://hub.docker.com/u/alanraison/; please have a look and suggest improvements. They are not the only minecraft containers on dockerhub, but maybe they will be useful to someone.

Tuesday 2 December 2014

Integrating Javascript into a Maven build

I've recently set up a few Javascript components into a Java web application and I though that sharing my experiences would be helpful, so here goes!

Project Structure

I decided to follow maven conventions and use the src/[main|test]/[language] conventions, and to have all the project definition files (package.json, bower.json, gulpfile.js) in the project root. I soon found myself wanting to split up the gulp tasks into groups, so I added a gulp/ folder, resulting in a structure like this:

webapp-module
`--- bower_components/ : runtime and test JS dependencies
`--- node_modules/ : build dependencies
`--- gulp/ : JS build tasks
`--- src/
    `--- main/js : main javascript files
    `--- test/js : test javascript files
`--- bower.json : runtime and test dependency configuration
`--- package.json : npm configuration, including build dependencies
`--- karma.conf.js : test framework configuration
`--- gulpfile.js : root build file
`--- pom.xml : normal maven project configuration (packaging=war)
`--- target/gulp/ : output directory for JS files

Project Configuration

In the POM, I added an entry for target/gulp as a project resource

<build>
...
  <resources>
    <resource>
      <directory>${project.build.directory}/gulp</directory>
    </resource>
  </resources>
...
</build>

In addition, I used the maven-exec-plugin to include it by default in the build:

<plugins>
...
  <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <configuration>
      <executable>gulp</executable>
      <arguments>
        <argument>check</argument>
        <argument>build</argument>
      </arguments>
    </configuration>
    <executions>
      <execution>
        <id>build-javascript</id>
        <phase>generate-resources</phase>
        <goals>
          <goal>exec</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
...
</plugins>

This of course requires node & npm to be installed and on the path, and for the gulp module (and in our configuration, bower too) to be globally installed. Therefore on a clean checkout, a developer would run:

npm install -g gulp bower
npm install

They would then be free to mvn package or whatever lifecycle they chose (after generate-resources) and the javascript would be checked and built according to the gulp check and build tasks.

Next Steps

In my next post I'll share some of the structure of the gulp tasks, so you can get a feel for what processes are used to build the javascript into the target/gulp folder.