Michael Crosby


Dockerfile Best Practices - take 2

Much has changed since my first Dockerfile best practices post. I'll leave the original post up for posterity and this post will include what has change and what you should do now.

1: Don't boot init

Containers model processes not machines. Even if you think that you need to do this you are probably wrong. Next...

2: Trusted builds

Even if you don't like the name it is an awesome feature. I have most of my github repositories added to trusted builds so that when I push a commit my new image is waiting on the index shortly after. Also, I don't have to have separate repositories with the Dockerfile to share with others, it is all in one place.

Just remember that this is not your playground for trying things out. Build locally first before you push. Docker ensures that what you build and run locally will build and run the same way when pushed elsewhere. Develop and test locally, commit and push, and wait for the official image on the index with trusted builds.

3: Don't upgrade in builds

Updates will be baked into the based images you don't need to apt-get upgrade your containers. Because of the isolation that happens this can often fail if something is trying to modify init or make device changes inside a container. It also produces inconsistent images because you no longer have one source of truth of how your application should run and what versions of dependencies are included in the image.

If there are security updates that a base image needs, let upstream know so that they can update it for everyone and ensure that your builds are consistent again.

4: Use small base images

Some images are more bloated than others. I suggest using debian:jessie as your base. If you are coming from ubuntu, you will find a more lightweight and happy home on debian. It's small and does not contain any unneeded bloat.

5: Use specific tags

This is important for your base images. Your FROM should always contain the full repository and tag for the base image that you are depending on. FROM debian:jessie not just FROM debian.

6: Group common operations

Your apt-get update should be grouped with your apt-get install. Also, you should take advantage of the \ to span multiple lines on your installs.

# Dockerfile for https://index.docker.io/u/crosbymichael/python/ 
FROM debian:jessie

RUN apt-get update && apt-get install -y \
    git \
    libxml2-dev \
    python \
    build-essential \
    make \
    gcc \
    python-dev \
    locales \
    python-pip

RUN dpkg-reconfigure locales && \
    locale-gen C.UTF-8 && \
    /usr/sbin/update-locale LANG=C.UTF-8

ENV LC_ALL C.UTF-8

Just remember that layers and the cache are good. Don't be scared of having many layers because the cache is a life saver. Also you should use upstream packages when you can.

7: Use your own base images

If you are running python applications have a python base image. The Dockerfile in the previous example is used to build crosbymichael/python that is used in many other images for python applications.

FROM crosbymichael/python

RUN pip install butterfly
RUN echo "root\nroot\n" | passwd root

EXPOSE 9191
ENTRYPOINT ["butterfly.server.py"]
CMD ["--port=9191", "--host=0.0.0.0"]

Or another:

FROM crosbymichael/python

RUN pip install --upgrade youtube_dl && mkdir /download
WORKDIR /download
ENTRYPOINT ["youtube-dl"]
CMD ["--help"]

As you can see this makes Dockerfiles that use your base very small and focused on the application that we are installing install.

Let me know what you think or if you have any other questions.

comments powered by Disqus