Smarkets Dockerfiles
At Smarkets we deploy all our Python and Node services in docker containers. Whilst we’ve spoken before about how we manage these images using docker-py, we’ve not previously discussed the Dockerfiles themselves.
First, some context. We currently run Docker-1.10 so we are limited to the features in that version (no multi stage builds or healthchecks). We also, by convention, place the service source code in /app
, and test code in /tests
. We also mount these directories in development in order to benefit from hot reloading. Finally we store all our images in an internal docker registry which I will refer to as registry
throughout.
Plain Image
We term the base image for all our containers the plain image as it is just the plain OS. When choosing the OS we considered Debian Jessie and Alpine before settling on Ubuntu 17.04/Zesty. Ubuntu Zesty was chosen over Debian Jessie as both it and associated package repositories are newer, thereby allowing us access to the latest Python and Node versions (among others). Ubuntu was chosen over Alpine as we are familiar with apt and Ubuntu, but have very little familiarity with apk or Alpine.
As we like to know the full providence of our images, we build the base image ourselves using the dockermkimage.sh
script on a CI machine with audited apt repository access. These two commands build the plain image and push it to our registry.
sudo /usr/share/docker-engine/contrib/mkimage.sh \
-t registry:5000/ubuntu-zesty-plain debootstrap \
-include=ubuntu-minimal \
-components=main,universe zesty
docker push registry:5000/ubuntu-zesty-plain
Base Image
Our base image extends the plain image with everything that is common to every container we use. This includes, our local CA certificates and apt utilities to use them, setting a UTF-8 locale (as we are an international company), and dumb-init to ensure that the children of the CMD process are properly reaped and signals reach them.
All our images have a GIT_COMMIT
environment variable present within, which is set at build time via a docker build-arg. This is very useful when inspecting containers from within.
This image is tagged as smarkets-zesty-base
when pushed to the registry.
FROM registry:5000/ubuntu-zesty-plain:latest
RUN apt-get update && apt-get upgrade -y
RUN apt-get update && apt-get install -y \
apt-transport-https \
apt-utils \
ca-certificates \
dumb-init \
locales \
&& rm -rf /var/lib/apt/lists/* && apt-get cleanCOPY smarkets.crt /usr/local/share/ca-certificates/
RUN update-ca-certificatesENV LANGUAGE en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8
RUN echo “${LANG} UTF-8” >> /etc/locale.gen \
&& locale-gen ${LANG} \
&& /usr/sbin/update-locale LANG=${LANG}
ENTRYPOINT [“/usr/bin/dumb-init”, “--“]
ARG _GIT_COMMIT=””
ENV GIT_COMMIT ${_GIT_COMMIT}
Python Image
Our Python image extends from the base image and adds Python3.6, which as of this month is our standard Python runtime. It includes a pip.conf and a CA bundle in order to use our internal devpi package repository, installed build requirements, notably pip, setuptools, wheel
, and sets the PATH
to ensure that Python3.6 instead of an older system Python.
The image is tagged as smarkets-python-3.6
when pushed to the registry.
FROM registry:5000/smarkets-zesty-base:latest
RUN apt-get update && apt-get install -y \
python3.6 \
python3.6-dev \
python3.6-venv \
&& rm -rf /var/lib/apt/lists/* && apt-get cleanCOPY pip.conf /etc/pip.conf
ENV REQUESTS_CA_BUNDLE /etc/ssl/certs/ca-certificates.crt
RUN python3.6 -m venv /ve
ENV PATH=/ve/bin:${PATH}
COPY build_requirements.txt /build_requirements.txt
RUN pip install --no-cache-dir -r /build_requirements.txt
ARG _GIT_COMMIT=""
ENV GIT_COMMIT ${_GIT_COMMIT}
Typical Python Service Image
Next we reach a Dockerfile used by a running Python service, this one extends the Python-3.6 image including the service requirements, test and app code. This is ready for production.
FROM registry:5000/smarkets-python-3.6:latestCOPY app/requirements.txt /requirements.txt
RUN pip install --no-cache-dir -r /requirements.txtEXPOSE 8282
CMD python run.pyCOPY app /app
WORKDIR /app
VOLUME /appCOPY tests /tests
VOLUME /testsARG _GIT_COMMIT=""
ENV GIT_COMMIT ${_GIT_COMMIT}
Node Image
Our Node image extends from the base image and adds node 7, which as of this month is our standard runtime. Node 7 is sourced from deb https://deb.nodesource.com/node_7.x zesty main
which is the contents of node_7.list
. This image also includes an npmrc in order to use our internal verdaccio repository.
FROM registry:5000/smarkets-zesty-base:latest
COPY https://deb.nodesource.com/gpgkey/nodesource.gpg.key /etc/apt
RUN apt-key add /etc/apt/nodesource.gpg.key
COPY node_7.list /etc/apt/sources.list.d/node_7.list
RUN apt-get update && apt-get install -y \
nodejs \
&& rm -rf /var/lib/apt/lists/* && apt-get clean
COPY npmrc /root/.npmrc
ARG _GIT_COMMIT=""
ENV GIT_COMMIT ${_GIT_COMMIT}
Typical Node Service Image
Finally we reach a Dockerfile used by a running Node service, this one extends the Node-7 image including the service requirements, and app code. This is then ready for production.
Note that we install the node modules to the root directory (parent of the app code). This ensures there is no conflict between the installed modules and the code when copied or mounted, however it requires the PATH environment variable to be set.
FROM registry:5000/smarkets-node-7:latest
COPY app/package.json /package.json
RUN npm install --prefix / && npm cache clean
ENV PATH=/node_modules/.bin:$PATHEXPOSE 8012
CMD ["npm", "start"]WORKDIR /app
COPY app /app
VOLUME /appCOPY tests /tests
VOLUME /testsARG _GIT_COMMIT=""
ENV GIT_COMMIT ${_GIT_COMMIT}