From b0841562ea5cf80e872e365c7439eab0e43fc485 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Fri, 25 May 2018 09:31:44 -0500 Subject: [PATCH] Add a bunch of Dockerfile examples --- slides/intro/Dockerfile_Tips.md | 263 ++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) diff --git a/slides/intro/Dockerfile_Tips.md b/slides/intro/Dockerfile_Tips.md index 2ea68133..3fc63116 100644 --- a/slides/intro/Dockerfile_Tips.md +++ b/slides/intro/Dockerfile_Tips.md @@ -96,3 +96,266 @@ CMD, EXPOSE ... * The build fails as soon as an instruction fails * If `RUN ` fails, the build doesn't produce an image * If it succeeds, it produces a clean image (without test libraries and data) + +--- + +# Dockerfile examples + +There are a number of tips, tricks, and techniques that we can use in Dockerfiles. + +But sometimes, we have to use different (and even opposed) practices depending on: + +- the complexity of our project, + +- the programming language or framework that we are using, + +- the stage of our project (early MVP vs. super-stable production), + +- whether we're building a final image or a base for further images, + +- etc. + +We are going to show a few examples using very different techniques. + +--- + +## When to optimize an image + +When authoring official images, it is a good idea to reduce as much as possible: + +- the number of layers, + +- the size of the final image. + +This is often done at the expense of build time and convenience for the image maintainer; +but when an image is downloaded millions of time, saving even a few seconds of pull time +can be worth it. + +.small[ +```dockerfile +RUN apt-get update && apt-get install -y libpng12-dev libjpeg-dev && rm -rf /var/lib/apt/lists/* \ + && docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr \ + && docker-php-ext-install gd +... +RUN curl -o wordpress.tar.gz -SL https://wordpress.org/wordpress-${WORDPRESS_UPSTREAM_VERSION}.tar.gz \ + && echo "$WORDPRESS_SHA1 *wordpress.tar.gz" | sha1sum -c - \ + && tar -xzf wordpress.tar.gz -C /usr/src/ \ + && rm wordpress.tar.gz \ + && chown -R www-data:www-data /usr/src/wordpress +``` +] + +(Source: [Wordpress official image](https://github.com/docker-library/wordpress/blob/618490d4bdff6c5774b84b717979bfe3d6ba8ad1/apache/Dockerfile)) + +--- + +## When to *not* optimize an image + +Sometimes, it is better to prioritize *maintainer convenience*. + +In particular, if: + +- the image changes a lot, + +- the image has very few users (e.g. only 1, the maintainer!), + +- the image is built and run on the same machine, + +- the image is built and run on machines with a very fast link ... + +In these cases, just keep things simple! + +(Next slide: a Dockerfile that can be used to preview a Jekyll / github pages site.) + +--- + +```dockerfile +FROM debian:sid + +RUN apt-get update -q +RUN apt-get install -yq build-essential make +RUN apt-get install -yq zlib1g-dev +RUN apt-get install -yq ruby ruby-dev +RUN apt-get install -yq python-pygments +RUN apt-get install -yq nodejs +RUN apt-get install -yq cmake +RUN gem install --no-rdoc --no-ri github-pages + +COPY . /blog +WORKDIR /blog + +VOLUME /blog/_site + +EXPOSE 4000 +CMD ["jekyll", "serve", "--host", "0.0.0.0", "--incremental"] +``` + +--- + +## Multi-dimensional versioning systems + +Images can have a tag, indicating the version of the image. + +But sometimes, there are multiple important components, and we need to indicate the versions +for all of them. + +This can be done with environment variables: + +```dockerfile +ENV PIP=9.0.3 \ + ZC_BUILDOUT=2.11.2 \ + SETUPTOOLS=38.7.0 \ + PLONE_MAJOR=5.1 \ + PLONE_VERSION=5.1.0 \ + PLONE_MD5=76dc6cfc1c749d763c32fff3a9870d8d +``` + +(Source: [Plone official image](https://github.com/plone/plone.docker/blob/master/5.1/5.1.0/alpine/Dockerfile)) + +--- + +## Entrypoints and wrappers + +It is very common to define a custom entrypoint. + +That entrypoint will generally be a script, performing any combination of: + +- pre-flights checks (if a required dependency is not available, display + a nice error message early instead of an obscure one in a deep log file), + +- generation or validation of configuration files, + +- dropping privileges (with e.g. `su` or `gosu`, sometimes combined with `chown`), + +- and more. + +--- + +## A typical entrypoint script + +```dockerfile + #!/bin/sh + set -e + + # first arg is `-f` or `--some-option` + # or first arg is `something.conf` + if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then + set -- redis-server "$@" + fi + + # allow the container to be started with `--user` + if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then + chown -R redis . + exec su-exec redis "$0" "$@" + fi + + exec "$@" +``` + +(Source: [Redis official image](https://github.com/docker-library/redis/blob/d24f2be82673ccef6957210cc985e392ebdc65e4/4.0/alpine/docker-entrypoint.sh)) + +--- + +## Factoring information + +To facilitate maintenance (and avoid human errors), avoid to repeat information like: + +- version numbers, + +- remote asset URLs (e.g. source tarballs) ... + +Instead, use environment variables. + +.small[ +```dockerfile +ENV NODE_VERSION 10.2.1 +... +RUN ... + && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION.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.tar.xz\$" SHASUMS256.txt | sha256sum -c - \ + && tar -xf "node-v$NODE_VERSION.tar.xz" \ + && cd "node-v$NODE_VERSION" \ +... +``` +] + +(Source: [Nodejs official image](https://github.com/nodejs/docker-node/blob/master/10/alpine/Dockerfile)) + +--- + +## Overrides + +In theory, development and production images should be the same. + +In practice, we often need to enable specific behaviors in development (e.g. debug statements). + +One way to reconcile both needs is to use Compose to enable these behaviors. + +Let's look at the [trainingwheels](https://github.com/jpetazzo/trainingwheels) demo app for an example. + +--- + +## Production image + +This Dockerfile builds an image leveraging gunicorn: + +```dockerfile +FROM python +RUN pip install flask +RUN pip install gunicorn +RUN pip install redis +COPY . /src +WORKDIR /src +CMD gunicorn --bind 0.0.0.0:5000 --workers 10 counter:app +EXPOSE 5000 +``` + +(Source: [traininghweels Dockerfile](https://github.com/jpetazzo/trainingwheels/blob/master/www/Dockerfile)) + +--- + +## Development Compose file + +This Compose file uses the same image, but with a few overrides for development: + +- the Flask development serve ris used (overriding `CMD`), + +- the `DEBUG` environment variable is set, + +- a volume is used to provide a faster local development workflow. + +.small[ +```yaml +services: + www: + build: www + ports: + - 8000:5000 + user: nobody + environment: + DEBUG: 1 + command: python counter.py + volumes: + - ./www:/src +``` +] + +(Source: [trainingwheels Compose file](https://github.com/jpetazzo/trainingwheels/blob/master/docker-compose.yml)) + +--- + +## How to know which best practices are better? + +- The main goal of containers is to make our lives easier. + +- In this chapter, we showed many ways to write Dockerfiles. + +- These Dockerfiles use sometimes diametrally opposed techniques. + +- Yet, they were the "right" ones *for a specific situation.* + +- It's OK (and even encouraged) to start simple and evolve as needed. + +- Feel free to review this chapter later (after writing a few Dockerfiles) for inspiration!