Docker build

Restrictions on image

Image tag

– the regex for the letters is all lower case, no upper case letters.
– a hostname + optional port is allowed at the very beginning.
don’t use underscore because not more two underscores are allowed but multiple dashes are

Image flavors

Docker images (such as Node, JDK, Linux OS, …) distributed on public docker repositories
come very often with multiple favors.
We cannot say that there is an exact specification of these flavors followed by every public dockerized OS/application but some appears as very common. 

Alpine image 

– no bash installed, only sh is provided by default. It allows to reduce the image size.If required, we can add it : 
RUN apk add --no-cache bash

The Docker build concept

Helpful flags for docker build

--tag or -t : specify the name and optionally a tag such as « fooname » or « fooname:footag »
--target : set the target build stage to build. Very helpful for debugging a multiple stage build
--no-cache : Do not use cache when building the image
--progress string : set type of progress output (auto, plain, tty). Use plain to show container output (default « auto »)
--rm boolean : Remove intermediate containers after a successful build (default true)

The context of the build

The context represents the working directory on the host during the Dockerfile execution.
For example, that command COPY fooHostFolder/file.txt /app looks fine only if the build context is the parent of the fooHostFolder folder.

Beware : For security reasons, the execution of the Dockerfile build never accepts to perform a command that relies on a parent directory of the context such as COPY ../secrets.txt /target.
So as a rule of thumb, using the application root folder as context is generally the most appropriate.

Specifying the context of the build

If we build an image, that is mandatory.
With docker build command, that is so always needed. The context is conveyed by the last argument of the command :
docker build -t anyTag .

With docker-compose build command, that is needed only in the services declaration where we build an image. We define in the docker-compose template with the services>foo-service>build>context property such as :

services:
  spring-boot:
    build:
      context: ../quizz/quizz-backend

In cases of using an image such as for a service, we don’t need to define any context of course :

services:
  spring-boot:
    image: my-image:1.0

Specifying the Dockerfile location

With docker build

we need to specify the location in two cases :
– the Dockerfile is not at the location where the docker build command is run.
– the Dockerfile is not named Dockerfile.

We specify the Dockerfile location with -f fooDockerfile flag.
For example :
docker build -f docker/Dockerfile-with-smaller-layers -t quizz-smaller-layers-jar .

With docker compose

We need to specify the location in two cases :
– the Dockerfile is not directly located in the directory of the build context of the service.
– the Dockerfile is not named Dockerfile.

We specify the Dockerfile location of a composed service thanks to the services>foo-service>build>dockerfile property such as :

services:
  spring-boot:
    build:
      context: ../quizz/quizz-backend
      dockerfile: docker/Dockerfile-with-smaller-layers

Build context sent to the Docker daemon

By default, all files and directory defined in the build context are sent to the Docker daemon when we execute docker build. A larger context means more time for Docker to process the resources.
To prevent that, we could define a .dockerignore file similarly to the .gitignore file to indicate to Docker files/folder of the build context to not send to the daemon during the build

Working directory

Changing the current path during a RUN instruction doesn’t change the current path of the next instructions.
For example :

RUN cd /var/foo
RUN pwd

Here pwd doesn’t echo /var/foo but the current Dockerfile WORKDIR value.

What is the Dockerfile WORKDIR ?
The WORKDIR instruction :
– sets the working directory for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions that follow it in the Dockerfile.
– If the WORKDIR doesn’t exist on the layer, it will be created.
– The WORKDIR instruction can be used multiple times in a Dockerfile. If a relative path is provided, it will be relative to the path of the previous WORKDIR instruction.

Environment and argument instructions in DockerFile

Goals and differences

Environment and arguments are complementary things and docker and we can see them both in the DockerFile, the docker compose template, the OS env variables and the command line args passed to some docker commands.
But here I focus only on them from the DockerFile POV.
ENV and ARG are complementary.
Here is the key differences :
– scopes :
   – ENV variables are usable both during the build and the run of the image.
  – ARG variables are usable only during the build.

– way to define and value them :
   – ENV variables may be hardcoded in the dockerfile. These may be also be injected by Docker. In this case, we don’t need to declare them. For example, the env_file section of the docker-compose template allows env vars injection.
   – ARG variables may be hardcoded in the dockerfile. These may be also be injected by Docker. But here we need to declare them in any case. We can so specific a default value or not.
For example, the build:args section of the docker-compose template allows args injection.

– way to consider them : 
– ENV are more friendly as variables for container
– ARG are more friendly as  variables for image building

Some examples

Define an hardcoded env variable :
ENV MY_VAR=/usr/myPath

Define an arg with a default value if not passed :
ARG MY_VAR=/usr/myPath

Define an argument passed from the build with the same name, without default value :
ARG MY_VAR

Define an argument passed from the build with the same name, without default value and then store it in an ENV variable :
ARG MY_VAR
ENV MY_ENV = $MY_VAR

Run docker build with a build arg :
docker build -f Dockerfile-xxx -t tagName --build-arg JAR_FILE=MY_ARG=fooValue .

CMD and ENTRYPOINT instructions

Or how to make instructions goal not clear

Unfortunately, their roles are not clearly separated.
If in a specific configuration the CMD may enrich the ENTRYPOINT with some args, both of them may also overwrite that behavior and make things hard to understand.
That’s why the official documentation about CMDand ENTRYPOINT may be very confusing.

At last, their interaction may make one or the other helpless, which may also makes things unclear.
You would not have neither error, nor warning in the docker build process and that is rather annoying.

That table summarizes the different cases :

 No ENTRYPOINTENTRYPOINT exec_entry p1_entryENTRYPOINT [“exec_entry”, “p1_entry”]
No CMDerror, not allowed/bin/sh -c exec_entry p1_entryexec_entry p1_entry
CMD [“exec_cmd”, “p1_cmd”]exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry exec_cmd p1_cmd
CMD [“p1_cmd”, “p2_cmd”]p1_cmd p2_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry p1_cmd p2_cmd
CMD exec_cmd p1_cmd/bin/sh -c exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

Note: If CMD is defined from the base image, setting ENTRYPOINT will reset CMD to an empty value. In this scenario, CMD must be defined in the current image to have a value.

Main use cases with ENTRYPOINT and CMD

Indeed, rather than trying to identify and learn each case even corner cases, which sounds as annoying as not friendly for our memory seen the number of discrepancies of the instruction according to the context,  I prefer to identify main use cases I need to understand and to use.
But first, here are their different forms that we will refer then in the main use cases :
The ENTRYPOINT forms :
ENTRYPOINT ["executable", "param1", "param2"] (exec form, preferred)
ENTRYPOINT command param1 param2 (shell form)

The CMD forms:
CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param3 param4 (shell form)

Some common use cases :

Execute a command with default arguments that docker clients may override (in the DockerFiles child or from the command line) :

ENTRYPOINT ["executable"] 
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)

Usage :
Without override args :
docker run foo-image
Or
With override args :
docker run foo-image param1=... param2=...

Execute a command with no default argument and force clients to specify them (in the DockerFiles child or from the command line).
If the command requires param1 and param2 and fails fast if no present, very straight.
For example, suppose param1 and param2 are mandatory:
ENTRYPOINT ["executable.sh"]
The check of the param1 and param2 args is performed directly inside executable.sh

Same example : suppose that param1 and param2 are still mandatory but that the command doesn’t check the params. In that case, we need to perform the check ourselves in a custom script or in the command itself if makes sense and that it belongs to our source code.

# first  we copy the wrapper that does the args check in the image
ADD executable.sh /app
RUN chmod 755 /app/executable.sh
...
ENTRYPOINT ["/app/executable.sh"]

And here executable.sh source :

#!/bin/bash
set -e
args=$@
echo "args=$args"
if [[ -z $args  ]]; then
  echo "Error : you have to provide foo args"
  exit 255
fi
# execute the real program with args
foo-command $@

Usage :
Override args is mandatory here:
docker run foo-image param1=... param2=...

Prevent from specifying any CMD in the dockerfile or at runtime. We use the ENTRYPOINT shell form for that :
ENTRYPOINT command param1 param2

Allow variable substitutions in the CMD or the ENTRYPOINT command. We need shell processing for that, so we need to use the shell form and not the exec form of these commands.
For example :
ENTRYPOINT java -jar -c /app $MAIN_CLASS $BAR_PARAM
Or similarly with CMD :
CMD java -jar -c /app $MAIN_CLASS $BAR_PARAM
Using the one or the other way doesn’t have significant differences in that context.

No specific requirement : don’t declare ENTRYPOINT, just declare CMD to define the command to execute. Either use the shell form or the exec form. Under the hood the shell form executes the command with /bin/sh -c while the exec executes directly the  command.

Named volumes in DockerFile

Beware :
– after its creation, the named volume stored in the host overlays data defined in the volume directories during the Dockerfile execution. It means that if we add data in the volume during the dockerfile execution, the first time that is ok, the second, nothing is added because of the overlay. A workaround : removing the volume on the host associated before a new build.

Beware of changes in named volumes  in the Dockerfile after a COPY : 
– (not sure now) in Dockerfiles, files/folders copied with COPY must not be modified then with RUN. These changes will never be reflected in the running container.
If the rights on a file ha to be changed (chmod for example), do that instead of at the origin : on the file on the host.

Centos Base image tricks

To exec a command as root in a running container, we also have to specify the working directory in the command :
docker exec -ti -u root -w /root CONTAINER bash

To cleanup Yum data and cache not required any longer after package installations :

RUN yum install -y packageFoo packageBar && \
    yum clean all && \
    rm -rf /var/cache/yum

Ce contenu a été publié dans Non classé. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *