March 22, 2020

Writing your first Dockerfile

Get started with some simple concepts to write your first Dockerfile and build a custom docker image

Writing your first Dockerfile

I have been been hearing about Docker for quite some time now and was curious to explore how it would be helpful for me.

Let's say you want to setup a CI pipeline for generating builds or testing the builds that are generated. One way would be to install all the required software in a single machine and you are done. But this brings in a lot of problems about how do you maintain something like this. How do we solve this?

Docker to your rescue!

I looked at many of the pre-defined images on DockerHub but did not find anything perfectly suitable for my needs. There were some which came close. Finally decided to create a custom image.

Initially looked at many of the Dockerfiles that are freely available to understand the syntax. I was surprised to the see that the syntax is quite simple if you want to do some basic stuff.

Lets look at a sample Dockerfile to understand.

We will try creating a Docker image with the following installed

  1. Android SDK tools
  2. Ruby 2.4 with Fastlane and Bundler
  3. NodeJS 12.x
FROM phusion/baseimage:0.10.0
LABEL maintainer=""

# Set any environment variables using ENV
ENV LC_ALL "en_US.UTF-8"
ENV LANGUAGE "en_US.UTF-8"
ENV LANG "en_US.UTF-8"

ENV VERSION_SDK_TOOLS "4333796"
ENV VERSION_BUILD_TOOLS "29.0.3"
ENV VERSION_TARGET_SDK "29"

ENV ANDROID_HOME "/sdk"

ENV PATH "$PATH:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools"

ENV HOME "/root"

RUN apt-add-repository ppa:brightbox/ruby-ng
RUN apt-get update
RUN apt-get -y install --no-install-recommends \
	curl \
	openjdk-8-jdk \
	unzip \
	zip \
	git \
	ruby2.4 \
	ruby2.4-dev \
	build-essential \
	file \
	ssh

ADD https://dl.google.com/android/repository/sdk-tools-linux-${VERSION_SDK_TOOLS}.zip /tools.zip
RUN unzip /tools.zip -d /sdk && rm -rf /tools.zip

RUN yes | ${ANDROID_HOME}/tools/bin/sdkmanager --licenses

RUN mkdir -p $HOME/.android && touch $HOME/.android/repositories.cfg
RUN ${ANDROID_HOME}/tools/bin/sdkmanager "platform-tools" "tools" "platforms;android-${VERSION_TARGET_SDK}" "build-tools;${VERSION_BUILD_TOOLS}"
RUN ${ANDROID_HOME}/tools/bin/sdkmanager "extras;android;m2repository" "extras;google;google_play_services" "extras;google;m2repository"

ENV NODE_VERSION 12.13.0

RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \
  && case "${dpkgArch##*-}" in \
    amd64) ARCH='x64';; \
    ppc64el) ARCH='ppc64le';; \
    s390x) ARCH='s390x';; \
    arm64) ARCH='arm64';; \
    armhf) ARCH='armv7l';; \
    i386) ARCH='x86';; \
    *) echo "unsupported architecture"; exit 1 ;; \
  esac \
  # gpg keys listed at https://github.com/nodejs/node#release-keys
  && set -ex \
  && for key in \
    94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
    FD3A5288F042B6850C66B31F09FE44734EB7990E \
    71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
    DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
    C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
    B9AE9905FFD7803F25714661B63B535A4C206CA9 \
    77984A986EBC2AA786BC0F66B01FBB92821C587A \
    8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 \
    4ED778F539E3634C779C87C6D7062848A1AB005C \
    A48C2BEE680E841632CD4E44F07496B3EB3C1762 \
  ; do \
    gpg --batch --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys "$key" || \
    gpg --batch --keyserver hkp://ipv4.pool.sks-keyservers.net --recv-keys "$key" || \
    gpg --batch --keyserver hkp://pgp.mit.edu:80 --recv-keys "$key" ; \
  done \
  && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH.tar.xz" \
  && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
  && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
  && grep " node-v$NODE_VERSION-linux-$ARCH.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
  && tar -xJf "node-v$NODE_VERSION-linux-$ARCH.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \
  && rm "node-v$NODE_VERSION-linux-$ARCH.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
  && ln -s /usr/local/bin/node /usr/local/bin/nodejs

RUN gem install fastlane
RUN gem install bundler -v 2.1.2

RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

There are a few keywords used here. Let's look at them one by one.

The FROM keyword allows us to specify the base image which you want start with. You can use an existing image which has some of the things that you want and build upon this or you can also start with a base image and install only what you need. It's really up to you.

LABEL specifies any meta data that you want to specify for the image.

ENV ANDROID_HOME "/sdk"

ENV can be used to specify any environment variables that you want to set. In this case, I am setting the ANDROID_HOME environment variable.

Finally, the most useful one RUN. This allows you to run any command within the docker image. All commands run as root so you don't need to add sudo.

Once you are done writing your Dockerfile. Just create an image using the following command:

docker build - < Dockerfile 

This runs all the commands specified in the Dockerfile and creates an image. The initial image that is created does not have a tag so you might want to tag the image.

Run this command to tag it

docker tag <HASH> <NAME>

The generated image has a hash identifying the image. You can check this with

docker image list

This lists all the images in your machine.

Happy coding!

For any advanced usages, you can refer to the Dockerfile reference documentation.

My recommendation would be to start off with some very simple to test the waters and then work your way up.