This article introduces the purpose of the main commands in Dockerfile
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 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/
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
- 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"]
- 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"]
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
FROM ubuntu ENV ENVTEST="hello,world" RUN echo $ENVTEST CMD ["/bin/bash"]
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
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"]
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
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