A Docker image is created at build time and a Docker container is created when the application is launched.
The Docker file is the heart of Docker. It tells Docker how to build the image to be used when creating the container.
Each Docker image contains a file named Dockerfile (it has no extension). When calling docker build, it is assumed that the Dockerfile will be in the current working directory. But a different location can be specified with the -f flag. You can also also tag the resulting image with docker
build -t command.
The container consists of a number of layers. All layers are read-only, except the last one - it is located above the others. The Docker file specifies the order in which the layers are added.
Each layer is simply a file with a modification of the previous layer. In Unix, almost everything is a file. The base layer, also called parent layer, is the starting layer.
When you download a Docker image from a remote repository, only the layers you are missing are downloaded. Docker saves space and time by reusing existing layers.
A Docker file instruction is an uppercase word that precedes the argument of any command. Each line in a Docker file can contain an instruction, all of which are handled top-down. The instructions look like this:
COPY . /app
Only the FROM, RUN, COPY and ADD instructions create the layers in the final image. The other instructions do configuration, add metadata or simply tell Docker to do something at run time (like open a port or execute a command).
Tag an image (-t)
docker build -t vieux/apache:2.0 .
That command will build the same way as the previous example, but it will also tag the resulting image (docker
build -t). The repository name will be vieux/apache and the tag will be 2.0. Read more about valid tags. You can apply multiple tags to an image. For example, you can apply the latest tag to a newly built image and add another tag that references a specific version. For example, to tag an image both as
whenry/fedora-jboss:v2.1, use the following:
docker build -t whenry/fedora-jboss:latest -t whenry/fedora-jboss:v2.1 .
Specify a Dockerfile (-f)
docker build -f Dockerfile.debug .
This will use a file called
Dockerfile.debug for the build instructions instead of
curl example.com/remote/Dockerfile | docker build -f - .
The above command will use the current directory as the build context and read a Dockerfile from
docker build -f dockerfiles/Dockerfile.debug -t myapp_debug .
docker build -f dockerfiles/Dockerfile.prod -t myapp_prod .
The above commands will build the current build context (as specified by the .) twice, once using a debug version of a Dockerfile and once using a production version.
docker build -f /home/me/myapp/dockerfiles/debug /home/me/myapp
docker build -f ../../../../dockerfiles/debug /home/me/myapp
These two docker build commands do the exact same thing. They both use the contents of the debug file instead of looking for a Dockerfile and will use
/home/me/myapp as the root of the build context. Note that debug is in the directory structure of the build context, regardless of how you refer to it on the command line.
This article assumes using a Unix Docker image. You can of course use a Windows Docker image but this is slower, less convenient and generally not much used. So, use Unix whenever possible.
Docker build with -
docker build - < Dockerfile
This will read a Dockerfile from STDIN without context. Due to the lack of a context, no contents of any local directory will be sent to the Docker daemon. Since there is no context, a Dockerfile ADD only works if it refers to a remote URL.
docker build - < context.tar.gz
This will build an image for a compressed context read from STDIN. Supported formats are: bzip2, gzip and xz.
Some Docker instructions
- FROM - Specifies the parent (main) image;
- LABEL - adds metadata to the image. This is a good place to put author information;
- ENV - creates an environment variable;
- RUN - launches commands and creates an image layer. Used to install packages and libraries inside the container;
- COPY - copies files and directories into the container;
- ADD - does the same as the COPY instruction. But it may also decompress local .tar files;
- CMD - specifies the command and arguments to be executed in the container. Parameters may be overridden. Only one CMD instruction may be used;
- WORKDIR - specifies the working directory for CMD and ENTRYPOINT instructions;
- ARG - defines a variable to pass to Docker at build time;
- ENTRYPOINT - provides commands and arguments for the container being executed. It's a little different from CMD, which we'll talk about below;
- EXPOSE-opens a port;
- VOLUME - creates a directory connection point for adding and storing persistent data.
- Instructions and examples
A Docker file can, theoretically, contain only one line:
A Docker file must begin with a FROM or ARG instruction followed by a FROM. The FROM command tells Docker to use the base image which corresponds to the repository and tag.
In this example, the image repository is Ubuntu. Ubuntu is the name of the official Docker repository that contains this OS.
Note that this Docker file contains a tag for the base image: 18.04, which tells Docker which version of the image to use. If this tag is not specified, the latest version of the image is used by default. It's best to specify the tag of the base image though. When the Docker file above is used to create a local Docker image for the first time, it loads the layers specified in the Ubuntu image.
When you create a Docker container, you place a layer on top that can be modified later.
When the image is running and you need to make some changes, the file is copied to the top layer available for editing. You can find out how to do this here.
More about the Docker file
Apart from being compressed, your one-line image is also slow, provides little information and does nothing during container startup. Take a look at the longer Docker file, which runs a lighter image and also executes a script at launch time.
LABEL maintainer="[email protected]"
RUN apk update && apk upgrade && apk add bash
COPY . ./app
RUN ["mkdir", "/a_directory"]
CMD ["python", "./my_script.py"]
But what does it all stand for?
The base image is the official Python image with the tag
3.7.2-alpine3.8. As you can see from the sources, the image includes Linux, Python and nothing else. Alpine images are very popular because they are small, fast and secure. However, Alpine images do not come with all the components that are specific to your operating system. Some packages you will have to install yourself.
The next instruction is LABEL. LABEL adds metadata to the image, and provides contact information. It does not slow down startup or take up a lot of space, but does provide some useful information, so be sure to use it.
ENV creates an environment variable that is available when the container is launched. In the example above, you may have seen the use of the variable ADMIN when creating the container.
ENV is useful for naming constants. If a constant is used in several places in a Dockerfile, and you need to change its value later, you can do it in one place.
A Dockerfile often provides several ways to solve a single task. It will be good if your solution takes into account the balance of Docker conventions, transparency and speed. For example, RUN, CMD and ENTRYPOINT serve different purposes and can be used to execute commands.
RUN creates a layer at runtime. Docker captures the state of the image after each RUN instruction.
It is most commonly used to install the right packages inside a container. In the example above, RUN apk update && apk upgrade tells Docker to update packages from the base image. && apk add bash tells Docker to install bash for the base image.
apk is short for Alpine Linux package manager. If you are using a non-Alpine Linux base image, install packages with the
RUN apt-get command.
- If you need to run the same command multiple times, select ENTRYPOINT;
- Use ENTRYPOINT when your container acts as an executable program;
- If there are additional default arguments that can be changed through the command line, CMD is a better choice.
In the example above, ENTRYPOINT ["python", "my_script.py", "my_var"] runs the Python script my_script.py in the container with the argument my_var. The my_var variable can then be used in my_script argparse. Note that my_var has a default value previously set in the Docker file using ARG. So, if the argument is not set via the command line, its default value will be taken.
Generally, Docker recommends you to use an executable form with the JSON syntax
ENTRYPOINT ["executable", "param1", "param2"].
The EXPOSE instruction tells you which port to forward from the container.
Use the docker run command with the
-p flag to forward and map multiple ports during startup. The uppercase
-P flag will proxy all open ports.
VOLUME determines where the container will store and access persistent data.