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 CMD
and 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 ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT [“exec_entry”, “p1_entry”] | |
---|---|---|---|
No CMD | error, not allowed | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry |
CMD [“exec_cmd”, “p1_cmd”] | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd |
CMD [“p1_cmd”, “p2_cmd”] | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_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_entry | exec_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 |
…