Mysore Linux Users' Group

A website for Linux users and geeks in general to express their thoughts and spread information


Building My First Docker Container

By Prithvi Vishak

Created January 12th, 2022


Docker containers are a pretty cool way to deploy programs. I've used other peoples' docker contairs quite a bit, but now wanted to make my own. Here's how that went.

I tried packaging an IRC bot I had written in go, the UpToDateBot. What it does isn't super relevant. Its needs are very simple, making it a good choice for my first container. It needs one config file, config.yaml in /etc/uptodatebot. It doesn't need any ports exposed to it.

To get a complete docker container from my go source code, I would need to first build the code, then copy it over to a container. The config file stuff can all be handled when running the container, since the config file resides on my host's filesystem anyway.

At first, I thought I would start easy by building the executable on my host machine, and copying the binary to the root of the container in my Dockerfile. I had heard of Alpine Linux being one of the lightest options for container OSs, so I went with that. Here's what I started with in my Dockerfile:

FROM alpine:latest COPY ./UpToDateBot / RUN mkdir /etc/uptodatebot WORKDIR /etc/uptodatebot CMD [ "/UpToDateBot" ]

I set the WORKDIR to /etc/uptodatebot because that's where I intended to put the config file, and the executable searches for it only in the directory in which it is run.

Building and running:

docker build -t uptodatebot . docker run -v $PWD/config.yaml:/etc/uptodatebot/config.yaml uptodatebot

Now of course, this didn't work at all. Alpine just complained that it couldn't find the executable.

exec /UpToDateBot: no such file or directory

When running with the interactive shell flag (docker run -v ... -it uptodatebot sh), I found out that the executable had indeed been copied correctly. I then remembered the memes about Alpine not being GNU/Linux. Of course!

I had build my executable on my openSUSE Tumbleweed machine, which uses glibc. Alpine doesn't use GNU software, so it has musl. It can't run executables that link to glibc like mine does. I'm guessing that the vague error was due to the lightweight nature of Alpine

I then tried the next best thing, by building the container FROM debian:latest instead. That too failed, this time because openSUSE's glibc version is newer than, and incompatible with, the version shipped with Debian. Building the container FROM opensuse/tumbleweed:latest, it worked!

But this was just the first step. I wanted a full build setup that built the executable binary in one container and copied the executables to a final one, so that things would largely be architecture-agnostic, and not dependent on the host machine's OS. I stubled upon the golang container, which contained a full environment for building, with tools like git preinstalled. All I had to do was create a directory for my build, clone the git repo, build the executable, and copy it over to a fresh container. This was done pretty easily, after some research. I took the opportunity to add a personal touch to the product too.

FROM golang:latest as buildcontainer WORKDIR /build RUN git clone https://[email protected]/pvpublic/uptodatebot.git WORKDIR /build/uptodatebot RUN go build -o UpToDateBot FROM debian:latest LABEL maintainer="Prithvi Vishak " COPY --from=buildcontainer /build/uptodatebot/UpToDateBot / WORKDIR /etc/uptodatebot CMD [ "/UpToDateBot" ]

As it turns out, the golang container's glibc version is compatible with Debian's. This was near ideal. The last thing I wanted was it to run in Alpine. After yet more research, I found that Alpine has a package called gcompat, which allows glibc programs to run properly. And indeed it did. Here's the final result:

FROM golang:latest as buildcontainer WORKDIR /build RUN git clone https://[email protected]/pvpublic/uptodatebot.git WORKDIR /build/uptodatebot RUN go build -o UpToDateBot FROM alpine:latest as uptodatebot LABEL maintainer="Prithvi Vishak <[email protected]>" COPY --from=buildcontainer /build/uptodatebot/UpToDateBot / RUN apk add --no-cache gcompat WORKDIR /etc/uptodatebot CMD [ "/UpToDateBot" ]

As usual, this article is mostly for my reference. If you landed here after your hundredth frenzied search of the internet for ways to solve an issue you're having, I hope this helped.