Production Ready Docker Image

Vinit Kumar

22 May 2017

Docker is a great tool to containerized an application(Containers, allow to package an application with its runtime dependencies). In HashedIn, we have been using docker for both internal & external projects and have learned good lessons from them. In this article, we will discuss strategy to create production-ready docker image taking intoCharchaaccount.

Important checklist for creating a docker image

  1. Lightweight Image: Application should be packaged with a minimal set of things which is required to run the application. We should avoid putting unnecessary build/dev dependencies.
  2. Never add secrets: Your application might need various secrets like credentials to talk to S3 / database etc. These are all runtime dependencies for the application and they should never be added to docker image.
  3. Leverage docker caching: Every statement(except few ones) in Dockerfile, creates a layer(intermediate image) and to make build faster docker tries to cache these layer. We should pay attention to arrange our docker statements in a way to maximize the uses of docker cache.

Note: As per documentation

  1. Except for ADD & COPY, usually, instruction in dockerfile will be used to see matches for existing images.
  2. For the ADD and COPY instructions, the contents of the file(s) in the image are examined and a checksum is calculated for each file. During the cache lookup, the checksum is compared against the checksum in the existing images.

Since a code is going to be changed very frequently than its dependencies, it is better to add requirements and install them before adding codebase in an image.

Dockerfile for Charcha

Let’s see dockerfile for charcha, which tries to adhere to the above-discussed checklist. Each instruction in dockerfile has been documented with inline comments which should describe the importance of the instruction.

# charcha is based on python3.6, let's choose the minimal base image for python. We will use Alipne Linux based image as they are much slimer than other linux images. python:3.6-alpine - is an official(developed /approved by docker team) python image.
FROM python:3.6-alpine
# Creating working directory as charcha. Here we will add charcha codebase.
WORKDIR /charcha
# Add your requirements first, so that we can install requirements first
# Why? Requirements are not going to change very often in comparison to code
# so, better to cache this statement and all dependencies in this layer.
ADD requirements.txt /charcha
ADD requirements /charcha/requirements
# Install system dependencies, which are required by python packages
# We are using WebPusher for push notification which uses pyelliptic OpenSSL which
# uses `ctypes.util.find_library`. `ctypes.util.find_library` seems to be broken with current version of alpine.
# `ctypes.util.find_library` make use of gcc to search for library, and hence we need this during
# runtime.
# https://github.com/docker-library/python/issues/111
RUN apk add --no-cache gcc
# Package all libraries installed as build-deps, as few of them might only be required during
# installation and during execution.
RUN apk add --no-cache --virtual build-deps \
      make \
      libc-dev \
      musl-dev \
      linux-headers \
      pcre-dev \
      postgresql-dev \
      libffi \
      libffi-dev \
      # Don't cache pip packages
      && pip install --no-cache-dir -r /charcha/requirements/production.txt \
      # Find all the library dependencies which are required by python packages.
      # This technique is being used in creation of python:alipne & slim images
      # https://github.com/docker-library/python/blob/master/3.6/alpine/Dockerfile
      && runDeps="$( \
      scanelf --needed --nobanner --recursive /usr/local \
              | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
              | sort -u \
              | xargs -r apk info --installed \
              | sort -u \
      )" \
      && apk add --virtual app-rundeps $runDeps \
      # Get rid of all unused libraries
      && apk del build-deps \
      # find_library is broken in alpine, looks like it doesn't take version of lib in consideration
      # and apk del seems to remove sim-link /usr/lib/libcrypto.so
      # Create sim-link again
      # TODO: Find a better way to do this more generically.
      && ln -s /usr/lib/$(ls /usr/lib/ | grep libcrypto | head -n1) /usr/lib/libcrypto.so
# Add charcha codebase in workdir
ADD . /charcha

Question: What will happen if we move our Add . /charcha statement up, just after WORKDIR /charcha. That way we didn’t need add requirements separately?
Ans: As discussed above, your code is going to be changed very frequently in comparison to requirements file. And since for ADD statement, docker tries to create checksum using content of files to match against its cache keys, there will be very high chance of cache miss(because of content change). Also, once the cache is invalidated, all subsequent Dockerfile commands will generate new images and the cache will not be used. And hence, even though we didn’t have updated our requirements, almost every build will end up in installing dependencies.

This dockerfile provides production-ready image, with a minimal set of dependencies. To play with this image locally you can try following steps;

  1. Build docker image: Create a docker image using above specified dockerfile.
    $ docker build --rm -t charcha:1.0 .
    

    Above command will create a docker image using the current directory as context and then tag the image as Charcha:1.0. Command also specifies to remove any intermediate images. For more information on docker build refer to this link.

    Note: docker build will be executed by docker daemon, and hence the first thing a build process does is, it sends the complete docker context(in our case,the entire content of the current directory) to the daemon. Your context path might contain some unnecessary files like .git folder, ide related files etc. which are not at all required to build the image. So, it is a best practice to add a .dockerignore file which is more like .gitignore and lists files & folders which needs to be ignored by the daemon.
    Following is the dockerignore file for charcha.

    .git
    Dockerfile
    docker-compose.yml
    Procfile
    
  2. Create a container from docker image:
    
       # This command will run shell in interactive mode for charcha container and will land you in
       # /charcha directory, because we have defined /charcha as our workdir in dockerfile.
       $ docker run -p8000:8000 -it charcha:1.0 /bin/sh
       # Running commands inside container
       /charcha $ python manage.py migrate
       /charcha $ python manage.py makemigrations charcha
       /charcha $ python manage.py runserver 0.0.0.0:8000
    

Now, charcha should be running(using local settings) in docker container and you can access charcha locally at http://localhost:8000. In coming blogs, we will discuss how to use docker-compose to do stuffs automatically, which we have done here manually and how to locally create production like environment.


Have a question?

Need Technology advice?

Connect

+1 669 253 9011

contact@hashedin.com

facebook twitter linkedIn youtube