Container shorthand - Dockerfile main directive

Keywords: Operation & Maintenance Back-end Container dockerfile

This article introduces the purpose of the main commands in Dockerfile

FROM

Specify the basic image. The recommended method is image:tag. Specify it precisely. Note: you can use multiple FROM, which will build multiple mirrors.

For example, I want busybox and nginx at the same time. I can write this

FROM busybox:latest
FROM nginx:latest

Then build and run

# structure
root@10-9-175-15:/home/ubuntu/docker-learn$ docker build -t busybox-nginx .
Sending build context to Docker daemon  40.96kB
# layer 1
Step 1/2 : FROM busybox
 ---> d23834f29b38
# layer 2
Step 2/2 : FROM nginx
latest: Pulling from library/nginx
e5ae68f74026: Pull complete 
21e0df283cd6: Pull complete 
ed835de16acd: Pull complete 
881ff011f1c9: Pull complete 
77700c52c969: Pull complete 
44be98c0fab6: Pull complete 
Digest: sha256:9522864dd661dcadfd9958f9e0de192a1fdda2c162a35668ab6ac42b465f0603
Status: Downloaded newer image for nginx:latest
 ---> f652ca386ed1
Successfully built f652ca386ed1
Successfully tagged busybox-nginx:latest

# We run it and enter the shell
root@10-9-175-15:/home/ubuntu/docker-learn$ docker run -it f652ca386ed1 sh

# Check nginx, yes, in
$ nginx -v
nginx version: nginx/1.21.4

ENV and ARG

ENV declares environment variables for the created container. This environment variable is available in the container that is started, and can also be used in specific instructions, including ENV, ADD, COPY, WORKDIR, EXPOSE, VOLUME, and USER.

ARG is similar, but the variables declared by ARG can only be used in Dockerfile and can no longer be used in the container started.

Let's do an experiment, output the defined environment variable in the docker build process, and output the environment variable in the container

FROM busybox
  
ENV ENVTEST="hello,world"

RUN echo $ENVTEST

Then build and run

root@10-9-175-15:/home/ubuntu/docker-learn$ docker build -t busybox-env .
Sending build context to Docker daemon  40.96kB
# layer 1
Step 1/3 : FROM busybox
 ---> d23834f29b38
# layer 2
Step 2/3 : ENV ENVTEST="hello,world"
 ---> Running in 4e20395f2f9a
Removing intermediate container 4e20395f2f9a
 ---> 79a34075b8ca
# layer 3
Step 3/3 : RUN echo $ENVTEST
 ---> Running in 3a9ca99e38c6
hello,world
Removing intermediate container 3a9ca99e38c6
 ---> 79b312023015
Successfully built 79b312023015
Successfully tagged busybox-env:latest

# Run up
root@10-9-175-15:/home/ubuntu/docker-learn$ docker run -it --name busybox-env busybox-env sh

# Print the environment variables set above
/ $ env | grep ENVTEST
ENVTEST=hello,world

RUN

RUN will create a container based on the image created by the previous command, and RUN the command in the container. After that, submit the container as a new image, which will be the basis of the next command.

RUN has two formats

# shell format, run through / bin/sh -c
RUN ls -a -l
# exec format, directly run the executable file, followed by the parameters
RUN ["ls", "-a", "-l"]

Several points for attention

  • exec format is recommended.
  • In the exec format, ["ls", "-a", "-l"] will be parsed by Docker as a json array, so double quotes must be used
  • In the exec format, because SH is not used for running, environment variables will not be resolved unless your executable refers to SH, that is ["sh", "-c", "ls", "-al"]

We won't demonstrate it here. We demonstrated it in ENV and intercepted the fragment as follows: the temporary container id started is 3a9ca99e38c6. After running echo, the container is removed and a layer with id 79b312023015 is generated.

Step 3/3 : RUN echo $ENVTEST
 ---> Running in 3a9ca99e38c6
hello,world
Removing intermediate container 3a9ca99e38c6
 ---> 79b312023015

COPY and ADD

The two are very similar. What they have in common is that when they act on local files, they are common replication relationships, that is, local files or folders are copied to the new image; The difference is that ADD adds two additional functions. When the source file is a URL, it will be downloaded and put into the image. When the source file is a local compressed file, it can unzip the file and copy it into the image.

In general, COPY is recommended because it is easier to understand. Where ADD can be used, it can be completed with the command of RUN plus wget or. The following demonstration

FROM busybox
  
ENV ENVTEST="hello,world"

RUN echo $ENVTEST

# Create the / data/test folder in the image
RUN mkdir -p /data/test
# Copy the Dockerfile of the current directory
COPY ./Dockerfile /data/test/
# Copy in namespace.c
ADD ./namespace.c /data/test/
# Download a file remotely and copy it in
ADD https://github.com/moby/moby/blob/master/client/client.go /data/test/
# Unzip the local compressed file and copy it in
ADD ./test.tar.gz /data/test/

verification

root@10-9-175-15:/home/ubuntu/docker-learn# docker build -t busybox-add-copy .
Sending build context to Docker daemon  25.09kB
Step 1/8 : FROM busybox
 ---> d23834f29b38
Step 2/8 : ENV ENVTEST="hello,world"
 ---> Using cache
 ---> 79a34075b8ca
Step 3/8 : RUN echo $ENVTEST
 ---> Using cache
 ---> 79b312023015
# Create directory
Step 4/8 : RUN mkdir -p /data/test
 ---> Running in 221ac6587f2e
Removing intermediate container 221ac6587f2e
 ---> 4e3602a7ab46
# copy
Step 5/8 : COPY ./Dockerfile /data/test/
 ---> ad3cb3d4e974
# copy
Step 6/8 : ADD ./namespace.c /data/test/
 ---> 057dfbef575c
# download
Step 7/8 : ADD https://github.com/moby/moby/blob/master/client/client.go /data/test/
Downloading  283.9kB

 ---> 587b68473628
# decompression
Step 8/8 : ADD ./test.tar.gz /data/test/
 ---> 908872aeb387
Successfully built 908872aeb387

# Start container
root@10-9-175-15:/home/ubuntu/docker-learn# docker run -it --name busybox-add-copy busybox-add-copy sh
# Jump to the target directory and view
/ # cd data/test/
/data/test # ls
Dockerfile   client.go    namespace.c  namespace.o

Can see

  • Dockerfile was copied in
  • namespace.c was copied in
  • client.go was downloaded and copied in
  • test.tar.gz was unzipped into namespace.o and copied in

CMD and ENTRYPOINT

These two must be mentioned together, because developers often don't understand the relationship between them

Let's look at CMD first. It has three formats

# shell format
CMD ls
# exec format
CMD ["ls", "-a", "-l"]
# param format, which provides parameters for ENTRYPOINT specification
CMD ["-a", "-l"]

Its precautions

  • There can be multiple CMD instructions in a Dockerfile, but only the last one will take effect
  • The first two formats of CMD are similar to RUN, but its own function is completely different from RUN. It is not executed during the container build process, but as the first execution instruction when the container starts
  • If the user explicitly specifies an instruction when docker run, the instruction specified by CMD will be overwritten

Let's look at ENTRYPOINT, which has only two formats

# shell format
ENTRYPOINT ls
# exec format
ENTRYPOINT ["ls", "-a", "-l"]

Its precautions

  • A Dockerfile can have multiple entry point instructions, but only the last one will take effect

  • When using shell format, ENTRYPOINT ignores any CMD and docker run specified instructions and runs in sh -c. This means that the process PID we specified will not be 1 and cannot receive Unix signals. When using docker stop to end the container, our process cannot receive the end signal.

  • When using the exec format, the parameters passed in by docker run will overwrite the contents specified by CMD and be attached to the parameters of the ENTRYPOINT instruction.

    That is, if I have the following dockerfile statement

    CMD ["java", "-jar", "hello.jar"]
    ENTRYPOINT ["sh", "-c"]
    

    When you start the container without any parameters, the actual execution is: sh -c java -jar hello.jar.

    If you run docker run xxx ls, the actual execution is: sh -c ls

Validate CMD

FROM ubuntu
  
ENV ENVTEST="hello,world"

RUN echo $ENVTEST

CMD ["/bin/bash"]

function

root@10-9-175-15:/home/ubuntu/docker-learn# docker build -t ubuntu-cmd .
Sending build context to Docker daemon  25.09kB
Step 1/4 : FROM ubuntu
 ---> ba6acccedd29
Step 2/4 : ENV ENVTEST="hello,world"
 ---> Running in 1f3709504258
Removing intermediate container 1f3709504258
 ---> 0ff22c83d97b
Step 3/4 : RUN echo $ENVTEST
 ---> Running in 270682e9ba4c
hello,world
Removing intermediate container 270682e9ba4c
 ---> 61316edd34e2
Step 4/4 : CMD ["/bin/bash"]
 ---> Running in 4178afd282b1
Removing intermediate container 4178afd282b1
 ---> ddf82bc91062
Successfully built ddf82bc91062
Successfully tagged ubuntu-cmd:latest

# Run up
root@10-9-175-15:/home/ubuntu/docker-learn# docker run -it ubuntu-cmd

# View current process number
root@a0bf5cba468b:/# echo $$
1

It can be seen that / bin/bash is run by default when the container starts, and the process number is 1.

Overwrite with docker run

# Specify the container to start and run ls. Here you can see that LS is indeed running, and then exit the container immediately, indicating that the CMD instruction has been overwritten
root@10-9-175-15:/home/ubuntu/docker-learn# docker run -it ubuntu-cmd ls
bin   dev  home  lib32  libx32  mnt  proc  run   srv  tmp  var
boot  etc  lib   lib64  media   opt  root  sbin  sys  usr

Verify ENTRYPOINT

FROM ubuntu
  
ENV ENVTEST="hello,world"

RUN echo $ENVTEST

# If you deliberately build a wrong instruction, / bin/bash ls -a -l, it will fail to execute
CMD ["ls", -a", "-l"]
ENTRYPOINT ["/bin/bash"]

function

root@10-9-175-15:/home/ubuntu/docker-learn# docker build -t ubuntu-entrypoint .
Sending build context to Docker daemon  25.09kB
Step 1/5 : FROM ubuntu
 ---> ba6acccedd29
Step 2/5 : ENV ENVTEST="hello,world"
 ---> Using cache
 ---> 0ff22c83d97b
Step 3/5 : RUN echo $ENVTEST
 ---> Using cache
 ---> 61316edd34e2
Step 4/5 : CMD ["ls", -a", "-l"]
 ---> Running in 5eb37a4b1820
Removing intermediate container 5eb37a4b1820
 ---> eb29627384d0
Step 5/5 : ENTRYPOINT ["/bin/bash"]
 ---> Running in fe3f5150adb5
Removing intermediate container fe3f5150adb5
 ---> 74c6f840a68c
Successfully built 74c6f840a68c
Successfully tagged ubuntu-entrypoint:latest

# The startup report ls error indicates that CMD has indeed been added to the parameter
root@10-9-175-15:/home/ubuntu/docker-learn# docker run --name ubuntu-entrypoint ubuntu-entrypoint
/usr/bin/ls: /usr/bin/ls: cannot execute binary file

# docker run specifies the start parameter touch. The package touch is wrong. It indicates that CMD is replaced, but the ENTRYPOINT has not been changed
root@10-9-175-15:/home/ubuntu/docker-learn# docker run --name ubuntu-entrypoint ubuntu-entrypoint touch
/usr/bin/touch: /usr/bin/touch: cannot execute binary file

< unfinished to be continued >

Dockerfile certainly does more than write instructions, but these are the most important contents. The key to correctly apply dockerfile is to add others when necessary

other

Several principles and precautions

  • Sharing Dockerfile is better than sharing Docker image, because Dockerfile is easy to version control, the construction process is clear and takes less space
  • There is no trade-off between CMD instruction and ENTRYPOINT. They are used together. CMD is applicable to specifying variable parameters because it can be overridden by docker run, and ENTRYPOINT is applicable to specifying immutable instructions accordingly.
  • Use to ensure that the image is as small as possible
    • Use a light enough base mirror
    • Don't put useless content in the image
    • If a file needs to be shared, mount it with volume instead of putting it in the image
  • Make full use of the cache. Docker images are layered. No matter how they are built or pulled, there will be a cache. Reducing the change part of Dockerfile is conducive to improving the cache hit rate

Posted by Fsoft on Sun, 05 Dec 2021 15:09:33 -0800