Dockerizing Macaw

Recently I did something that I kept postponing for almost one year: I added a Docker image to my project.

There were a few reasons why I postponed this activity: first and most important is that my project was working fine for me, but secondly is that I really didn’t know how hard this would have been.

I had only accomplished trivial tasks previously and for this Docker image I’d had to mess around with the graphical interface, sound devices and other little things. Turns out it was simpler than I expected. Point for Docker for making a great tool and simple to use at the same time. There were a few problems with the documentation that I will explain below (ARG and ENV), but overall it was a positive experience.

A great and perhaps the most important lesson is that if your host machine can do something, your container can do it too. You just have to grant permission to your container; Just remember that by granting extra permissions, you are giving up the isolation, thus reducing the security.

So, all I had to do was to orchestrate the permissions and redirect the services I was interested in. Docker-compose was used here to mostly avoid a big command when using docker run. I’m not using docker-compose as I’d imagine most people would use it. Most people would use it to organize\orchestrate all the built services.

Let’s see some code… this is Macaw Dockerfile:

# build stage
FROM golang:stretch

# some information about the docker image
LABEL Name="Macaw" Version="0.7" Maintainer="Marcus Renno <me@rennomarcus.com>"

# install dependecies
RUN apt-get -qq update && apt-get install -y \
    alsa-utils \
    libgl1-mesa-dri \
    libsdl2-dev \
    libsdl2-image-dev \
    libsdl2-mixer-dev \
    libsdl2-ttf-dev
RUN go get -d -v github.com/veandco/go-sdl2/sdl && \
    go get -d -v github.com/veandco/go-sdl2/img && \
    go get -d -v github.com/veandco/go-sdl2/mix && \
    go get -d -v github.com/veandco/go-sdl2/ttf && \
    go get -v github.com/tubelz/macaw

# terminal
ENTRYPOINT [ "/bin/bash" ]

As you can see, there’s nothing special. Just declaring some dependencies as part of the project which will avoid some pitfalls such as ‘it works in my machine; how can it not work on yours?’. Then we move to the Dockerfile and docker-compose.yml of a real game. Here we are using the sample project, crazybird


# build stage
FROM rennomarcus/macaw:latest
# some information about the docker image
LABEL Name="Crazybird" Version="v0.1.1" Maintainer="Marcus Renno <me@rennomarcus.com>"

ARG appName
ARG repository
# pass appName to my_app, so we can execute in entrypoint
ENV my_app=${appName}

WORKDIR /go/src/${repository}/${appName}
COPY . .

# install the application (game) using our libraries
RUN CGO=1 GOARCH=amd64 GOOS=linux go build .

# execute the game
ENTRYPOINT ./${my_app}

In this Dockerfile, we start seeing some different commands. I had to use ARG here, so I could have access to the application and repository variables during the build process. Variables declared with ENV will hold their value after the build stage, but you won’t be able to pass the value to it in the build command. That’s why I’m declaring the appName with ARG, then I pass the value to my_app which can be executed later on.

Now to finish, the docker-compose part.


version: '3.7'

services:
  macaw:
    # make sure you have the display, repository and app_name variables in the .env file
    environment:
      - DISPLAY=$DISPLAY
    volumes: 
      - '/tmp/.X11-unix/:/tmp/.X11-unix/'
    build:
      context: .
      args:
        - appName=$APP_NAME
        - repository=$REPOSITORY
    working_dir: '/go/src/${REPOSITORY}/${APP_NAME}'
    stdin_open: true
    tty: true
    network_mode: "host"
    devices:
      - "/dev/snd:/dev/snd"

Here I’m just declaring the arguments I’ll pass during the build/run commands. I hope it’s clear enough what is happening, but just in case it is not let’s go line by line:

  • services: macaw: – here I’m declaring the services and one of the is named macaw
  • environment: – passing the variables DISPLAY, REPOSITORY and APP_NAME (the last two in the .env file).
  • volumes: – mounting the X11 volume of my host machine to my container, so they can share it
  • build: context: . – the context is being used from the path where you are executing the command
  • args – this will be used in the Dockerfile. I could get the values from the environment variables. This is the most confusing part; I wasn’t able to get the environment variables straight from there. I had to pass through this intermediate step (ARG). The variables declared in environment will eventually be available after the image is built, but not in the build stage
  • working_dir && tty – same thing as in docker run
  • network_mode – same as --net=host in docker run, which means you are sharing your host network now
  • devices - "/dev/snd:/dev/snd" – give the container access to the host sound device

I also declared one docker environment for dev where I’m mounting my current files to the container. I played around the docker-compose.override option and it worked quite well for me. This way I don’t have to keep rebuilding the image for every single change, and I also have on environment for prod where I have static code and I can push the image to the hub. You can see the files better in the repo https://github.com/tubelz/crazybird .


Leave a Reply

Your email address will not be published. Required fields are marked *