This topic's purpose is to introduce to you the fundamental idea of Docker, which is a Dockerfile. This is a set of instructions/commands that Docker uses to create images automatically. You will learn about its structure and study two very important instructions. In all sections, you will see hyperlinks to the official documentation. Open them and make sure to study thoroughly. They complement the content provided here. So, let's start with an overview of Dockerfile.
Dockerfile overview
Dockerfile is the basis for creating images. With its help, you can describe what should be included in the image and what it should do using special instructions. Docker executes each instruction from top to bottom, line by line. Instructions should satisfy the following structure; INSTRUCTION argument(s). It can be preceded by an appropriate comment that will be removed by Docker before instruction execution. The keywords aren't case-sensitive but code convention requires writing the whole instruction using uppercase letters. Combined with comments, each instruction resembles the following code snippet:
# Comment
INSTRUCTION argument(s)Starting with this topic, we'll present a group of topics dedicated to instructions. You will learn about several fundamental yet important instructions in each of them.
FROM instruction
For each Dockerfile FROM is an instruction that you will see in the beginning. The only exception may be ARG instructions but you will learn about their purpose later on. The FROM instruction specifies the base image you want to use. While using this instruction, the repository name must be set using lowercase letters.
Except for the ARG and comments the FROM instruction can be preceded by the escape parser directive.
Note that you have two options to specify the base image when working with FROM instruction:
Without a version tag: Consider you have a simple image like this:
FROM ubuntu LABEL author=HyperUser ENTRYPOINT ["/bin/bash"]In such cases, Docker will find and pull the version of the image with the
latesttag, that isubuntu:latest. This can be beneficial as you get access to the latest features. The latest version might bring security or performance enhancements but there may be bugs or vulnerabilities too. But if you want a bug-free and secure experience, then you can choose a tried and tested stable version.With a version tag: This option doesn't give you the latest updates but prevents different kinds of issues you might face with a newer image version. Your container can run with an older version but might have errors due to changes brought by the new version.
You get to choose the version you need. If you need the latest features then you can select a newer version. But if you want a bug-free and secure experience, then you can choose a tried and tested stable version.
FROM instruction also supports the AS <NAME> option. You can use this option to name a build stage when you have multistage builds. This feature will be covered in future topics.
LABEL instruction
In Dockerfile you can define labels to specify metadata inside the Docker image. The labels can be author info, environment and version info, and many more. These labels are key-value pairs with key=value format, where both key and value can be either enclosed in double quotes or without them:
FROM ubuntu:22.04
LABEL author=HyperUser
LABEL "application_environment"="development"
LABEL "version"=1.0
LABEL multi.first-key="first-value" multi.second-key="second-value"
LABEL first-key=first-value \
second-key=second-value
ENTRYPOINT ["/bin/bash"]If you inspect the image or container, the label information will be available in the Config section. Let's check the label information for the image created with the Dockerfile above. For that purpose, create ubuntu:v1 image from the Dockerfile above and execute the docker inspect ubuntu:v1 --format='{{json .Config}}' command. Combined with jq, it shows us the output in JSON format:
{
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": null,
"Image": "sha256:616dd48aceb01a1e3874006bef404aac16abb4c6579d3da63d2448b8e6278271",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": [
"/bin/bash"
],
"OnBuild": null,
"Labels": {
"application_environment": "development",
"author": "HyperUser",
"key_1": "value_1",
"key_2": "value_2",
"multi.label1": "value1",
"multi.label2": "value2",
"version": "1.0"
}
}You can also define a label on a specific Docker object using the --label flag.
CMD instruction
The CMD instruction runs applications inside images and is executed after container instantiation. It takes the command and its arguments for that purpose. In general, its preferable structure is CMD ["executable", "param1", "param2"…] but there are other approaches too. Each Dockerfile should have only one CMD instruction. But, this doesn't mean that a Dockerfile can't have several CMD instructions. In that case, each CMD will override the previous one.
Let's observe CMD in practice. Assume you have the following Dockerfile:
FROM ubuntu:22.04
LABEL author=HyperUser
# Shell form
CMD echo Hello Students.
# Exec form
CMD ["echo", "Hello World."]As you can see there are two approaches to defining CMD instruction in a Dockerfile:
CMD echo Hello Students.: Runs the command in a shell. By default it is/bin/sh -c. The command enables shell features, such as piping, subcommands, signal processing, etc.CMD ["echo", "Hello World."]: Runs the specified binary with provided args. This approach is more commonly used.
If you build the image and run a container on it, the output will only be Hello Students.. Besides specifying commands inside the Dockerfile, you also can pass the command when running the container. Let's do it with the image ID created on the Dockerfile above:
$ docker run d559b5d58841 echo Hi
HiThis command will override the second CMD instruction and print Hi instead. It is crucial to understand this behavior since it is the main difference between CMD and ENTRYPOINT instruction.
ENTRYPOINT instruction
The ENTRYPOINT also targets the application in the image when the container is created. This instruction differs from CMD as it doesn't override the existing ENTRYPOINT instruction inside the image if you pass arguments when running the container.
In practice, one of the use cases for ENTRYPOINT is combining it with CMD instruction. You'll learn about this in the next section.
Before using this instruction, let's explore the shell form deeper. As you already know, when you use it you execute the command using /bin/sh -c which expects a string. So echo Hello Students. is captured inside a string to execute the /bin/sh -c 'echo Hello Students.' command. Assume you have the following Dockerfile:
FROM ubuntu:22.04
LABEL author=HyperUser
# shell form
ENTRYPOINT echo Hello Students. Now, check what you'll see in the COMMAND column when listing containers:
$ docker ps -a --format "table {{.Names}}\\t{{.Command}}" --last 1
NAMES COMMAND ...
hs-ubuntu-2 "/bin/sh -c 'echo He…"This prevents you from passing one more argument to echo when you run the container. To avoid this, let's explicitly tell Docker to run the command with /bin/echo applying the exec form. By the way, you will see similar behavior if you use echo instead of /bin/echo for this example. After a small update, the Dockerfile will look like this:
FROM ubuntu:22.04
LABEL author=HyperUser
# exec form
ENTRYPOINT ["/bin/echo", "Hello Students."]Now, let's you use this Dockerfile to run a container by passing an argument to it:
$ docker run --name hs-ubuntu-3 ubuntu:v3 `date`
Hello Students. Thu Nov 10 12:01:19 UTC 2022 As you can see, we didn't override the existing ENTRYPOINT but added one more argument to it. Now, let's check the last container info:
NAMES COMMAND
hs-ubuntu-3 "/bin/echo 'Hello St…"This time we applied the commands to /bin/echo as expected. Now let's try to combine the last two instructions.
Combining CMD and ENTRYPOINT
You can combine both CMD and ENTRYPOINT if you want to define the main command of the image and have the ability to define flags for it and override them when you need them. Let's prepare an image to see this structure in practice:
FROM ubuntu:22.04
LABEL author=HyperUser
ENTRYPOINT ["echo"]
CMD ["Hello","Students."]If you run a container based on the Dockerfile you'll have a base echo command that can't be overridden. On the other hand, you can either run the container with parameters defined inside the Dockerfile or pass new parameters when running the container.
Conclusion
With this topic, you have stepped into the world of Dockerfile instructions. You explored some crucial instructions that will help you while using Docker. Let's sum up the topic:
FROMinstruction lets you specify the base image.You can use
LABELinstruction to specify metadata inside an image.The
CMDinstruction allows you to run applications inside images.ENTRYPOINTinstruction differs fromCMDbecause you cannot redefine it when you start a container.