3.5 Docker recipes

The Docker recipe contains a set of instructions and commands that will be used to create/build a Docker image.

3.5.1 Writing recipes and building images

All commands should be saved in a text file, named by default Dockerfile.

Instruction What it does
FROM Sets the base image.
RUN Commands to run.
MAINTAINER Docker image maintainer.
WORKDIR Sets the working directory for the container’s building instructions.
SHELL Changes the default shell.
ADD / COPY Copy files from source to destination.
ARG Sets variables that can be used as the image is built.
ENV Sets environment variables. Persists when container is run.
ENTRYPOINT Helps configure the container as an executable.
CMD Can provide a default executable or arguments to the ENTRYPOINT executable.
VOLUME Creates a mount point for an external volume.
EXPOSE Exposes network ports on the container.

Reference

3.5.1.1 Basic instructions

Each row in the recipe corresponds to a layer in the final image.

FROM: parent image. Typically, an operating system. This is the base layer.

FROM ubuntu:18.04

RUN: the command to execute inside the image filesystem.
Think about it this way: every RUN line is essentially what you would run to install programs on a freshly installed Ubuntu OS.

RUN apt install wget

This is a basic recipe that takes the ubuntu:18.04 image as a base layer, updates and upgrades Linux packages, and installs wget:

FROM ubuntu:18.04

RUN apt update && apt -y upgrade
RUN apt install -y wget

HANDS-ON

Explore this Dockerfile:

  • What is the base layer?
  • What is being installed in the image? How?
Answer

Base layer: biocontainers/biocontainers:v1.0.0_cv4
Tool blast is installed using conda.

3.5.1.2 Building images from recipes

docker build will create/build a Docker image from a Docker recipe.

Save the following commands:

FROM ubuntu:18.04

RUN apt update && apt -y upgrade
RUN apt install -y wget

in a file named Dockerfile

docker build implicitely looks for a file named Dockerfile in the current directory:

docker build .

Same as:

docker build --file Dockerfile .

Syntax: --file / -f

. stands for the context (in this case, current directory) of the build process. This makes sense if copying files from filesystem, for instance.

IMPORTANT: Avoid contexts (directories) over-populated with files (even if not actually used in the recipe).

In order to avoid that some directories or files are inspected or included (e.g, with COPY command in Dockerfile), you can use .dockerignore file to specify which paths should be avoided. More information at: https://codefresh.io/docker-tutorial/not-ignore-dockerignore-2/

You can define a specific name for the image during the build process.

Syntax: -t imagename:tag. If not defined :tag default is latest.

docker build -t mytestimage_${USER} .

# same as:
docker build -t mytestimage_${USER}:latest .

The last line of installation should be Successfully built …: then you are good to go.
Check with docker images that you see the newly built image in the list…

Then let’s check the ID of the image and run it!

# Get the ID with docker images
docker images

# Run/start a container using the ID or name
docker run f9f41698e2f8
docker run mytestimage

3.5.1.3 More instructions

MAINTAINER

Who is maintaining the container?

MAINTAINER Toni Hermoso Pulido <toni.hermoso@crg.eu>

WORKDIR: all subsequent actions will be executed in that working directory

WORKDIR ~

SHELL: allows the default shell used for the shell form of commands to be overridden.
Use bash as the default shell:

SHELL ["/bin/bash", "-c"]

ADD, COPY: add files to the image filesystem

Difference between ADD and COPY explained here and here

COPY: lets you copy a local file or directory from your host (the machine from which you are building the image)

ADD: same, but ADD works also for URLs, and for .tar archives that will be automatically extracted upon being copied.

# COPY source destination
COPY ~/.bashrc .

ENV, ARG: run and build environment variables

Difference between ARG and ENV explained here.

  • ARG values: available only while the image is built.
  • ENV values: available for the future running containers.

You can use ARG, for example, to specify the version of the base layer you want to use:

  • With a default value
ARG UbuntuVersion=18.04

FROM ubuntu:${UbuntuVersion}
  • Without a default value (i.e. the user is expected to provide it upon building)
ARG UbuntuVersion

FROM ubuntu:${UbuntuVersion}

Provide a value for UbuntuVersion as you build the image with --build-arg:

docker build --build-arg UbuntuVersion=20.04 .

You can also use ARG to build a specific software version in the image. Let’s try it!

HANDS-ON

Modify the following recipe so you can decide in the command line which version of Python to install.
The default version should be 2.7. Pass the argument to install the version 3.8.
Build and run first with the default version, and then with the version 3.8.

FROM ubuntu:18.04

RUN apt update && apt upgrade -y
RUN apt install -y python2.7
Answer

Recipe is saved in Dockerfile_ARG

FROM ubuntu:18.04

# Argument PyVersion with default value 2.7
ARG PyVersion=2.7

RUN apt update && apt upgrade -y
RUN apt install -y python${PyVersion}

Build the image to get the default version of Python:

docker build -t py27_${USER} -f Dockerfile_ARG .

Build the image to install the version 3.8 of Python instead (via the --build-arg option):

docker build --build-arg PyVersion=3.8 -t py38_${USER} -f Dockerfile_ARG .

Run the image and check if Python of the correct version is installed:

docker run py27_${USER} python2.7 --help
docker run py38_${USER} python3.8 --help


CMD, ENTRYPOINT: command to execute when generated container starts

The ENTRYPOINT specifies a command that will always be executed when the container starts.
The CMD specifies arguments that will be fed to the ENTRYPOINT.


In the example below, when the container is run without an argument, it will execute echo "hello world" (default).
If it is run with the argument nice it will execute echo "nice". The argument given in CMD will be overridden.

FROM ubuntu:18.04

ENTRYPOINT ["/bin/echo"]
CMD ["hello world"]

Save the above recipe in Dockerfile_hello.
Build:

docker build -f Dockerfile_hello -t helloimage_${USER} .

Run without an argument:

docker run helloimage_${USER}

Now run with an argument:

docker run helloimage_${USER} "nice"


Here is a more complex recipe (save it in a text file named Dockerfile_ubuntu):

FROM ubuntu:18.04

MAINTAINER Toni Hermoso Pulido <toni.hermoso@crg.eu>

SHELL ["/bin/bash", "-c"]

WORKDIR ~

RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y wget

ENTRYPOINT ["/usr/bin/wget"]
CMD ["https://cdn.wp.nginx.com/wp-content/uploads/2016/07/docker-swarm-hero2.png"]

Build the image:

docker build -f Dockerfile_ubuntu .

Try to run it without and with an argument:

# Remember to check the image ID with `docker images`
docker run f9f41698e2f8

# with an argument
docker run f9f41698e2f8 https://cdn-images-1.medium.com/max/1600/1*_NQN6_YnxS29m8vFzWYlEg.png

3.5.2 docker tag

Use docker tag to tag a local image that has, for example, ID “f9f41698e2f8” in the “ubuntu_wget” image name repository with version/tag “1.0”:

docker tag f9f41698e2f8 ubuntu_wget:1.0

If the version/tag is not specified, the default is latest.

3.5.3 Build cache

Every line in a Dockerfile is an image/layer by itself (you can see all images with docker images --all).

Let’s modify the last line in the previous image recipe (Dockerfile_ubuntu) (let’s change the image URL) and rebuild it (even with a different name/tag):

FROM ubuntu:18.04

MAINTAINER Toni Hermoso Pulido <toni.hermoso@crg.eu>

WORKDIR ~

RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y wget

ENTRYPOINT ["/usr/bin/wget"]
CMD ["https://cdn-images-1.medium.com/max/1600/1*_NQN6_YnxS29m8vFzWYlEg.png"]
docker build -t mytestimage2_${USER} -f Dockerfile_ubuntu .

It will start from the last line, and will not re-run commands before the modified line as they were already successfully built.

It is very convenient for testing and trying new steps, but it may lead to errors when versions are updated (either FROM image or included packages).
It is therefore recommended to start from scratch with --no-cache option, when building the image.

docker build --no-cache -t mytestimage2_${USER} -f Dockerfile_ubuntu .