Foreword
Docker must be no stranger to our dev brothers. The idea of Docker is to create containers that contain independent environments to launch and develop the application, in simple terms, it contains everything that the application can run. And to create those containers, we need a Docker image. Creating Docker container from Docker image is similar to installing win from ghost file, extracting ghost file has everything you need. So how to create a Docker image? ye, that’s Dockerfile writing and this is what I want to mention in this article
Dockerfile is the heart of Docker. It defines what the image details include so that Docker can determine how to build the image. Dockerfile is actually a text file called Dockerfile, without extensions. It contains the necessary commands in order to build an image (the commands will be executed from the top down). To better understand Dockerfile, we will go to the next section, the basic components of Dockerfile
The basic components of Dockerfile
In the official document on the Docker homepage, the Dockerfile will include the following commands:
FROM
– specify base image. The base image will usually be taken from Docker Hub – the place to store and share images from which you can get and customize.
RUN
– used to execute any command during image build, usually it is used to build packages in image
CMD
– used to execute any command during container run. CMD will not execute anything during the build image process and each Dockerfile contains only one CMD command
LABEL
– used to provide metadata for images, a good place to store author information, notes …
EXPOSE
– set the port to access the container after it has been launched
ENV
– set environment variables to use for building statements
ADD
and COPY
– copy files and folders to containers
ENTRYPOINT
– provides some default commands with parameters when executing containers
VOLUME
– create a folder to access and store data, linked folders from the host machine and container
USER
– specifies the username or UID used during image creation for RUN
, CMD
and ENTRYPOINT
commands
WORKDIR
– Set up a working directory in the container for the commands COPY
, ADD
, RUN
, CMD
, and ENTRYPOINT
ARG
– Defines variables for use in build-time.
ONBUILD
– create a trigger for the image to execute when it is used as the base image for building another image
STOPSIGNAL
– specify the system symbol used to stop containers.
HEALTHCHECK
– provides a method for Docker to check if the container is functioning properly.
SHELL
– used to change the default shell commands
Regarding the definition and usage of each command, you can see here ( English version ). I will not write a definition for each command because someone already has it. Besides, it will make the article too long and really because I feel it is not really necessary.
The components that need the most attention and are most used are: FROM
, WORKDIR
, RUN
, COPY
(as well as ADD
), EXPOSE
, ENTRYPOINT
and HEALTHCHECK
. Why not pay attention to VOLUME
? Mounting the directory is also important. The reason is because we can completely install it in the docker compose. I often do that to agree on the folder setting. The discrete sering in each Dockerfile, when it is needed to find or have errors, opening each file is also a problem.
With these commands, we need to pay attention to a very important detail that is the command to create a layer and which command does not. Wait, what is a layer? I don’t see any paragraphs talking about this. yeah here it is, I will mention right after this what the layer is. For some of you who know, I’d like to remind you that the process of building a container from an image will be based on a series of layers. These layers are, of course, made up of Dockerfile commands. The Dockerfile itself will define Docker for those layers and the order in which they will be created. And the thing to note here is that Docker has a layer caching mechanism so that there is no need to redo the layer if it does not change from the previous build. This is very important and one of the first basics when performing Docker optimization. The commands that will create layers during the build process are: RUN
, COPY
and ADD
. You may not be able to see how this layer was created when docker builds. For example, with a Dockerfile create a web app using nodejs
1 2 3 4 5 6 7 8 9 10 11 12 | FROM node:argon # Create app directory RUN mkdir -p /usr/src/app WORKDIR /usr/src/app # Install app dependencies COPY package.json /usr/src/app/ RUN npm install # Bundle app source COPY . /usr/src/app EXPOSE 8080 CMD [ "npm", "start" ] |
When making the build it will look like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | $ docker build -t expressweb . Step 1 : FROM node:argon argon: Pulling from library/node... ... Status: Downloaded newer image for node:argon ---> 530c750a346e Step 2 : RUN mkdir -p /usr/src/app ---> Running in 5090fde23e44 ---> 7184cc184ef8 Removing intermediate container 5090fde23e44 Step 3 : WORKDIR /usr/src/app ---> Running in 2987746b5fba ---> 86c81d89b023 Removing intermediate container 2987746b5fba Step 4 : COPY package.json /usr/src/app/ ---> 334d93a151ee Removing intermediate container a678c817e467 Step 5 : RUN npm install ---> Running in 31ee9721cccb ---> ecf7275feff3 Removing intermediate container 31ee9721cccb Step 6 : COPY . /usr/src/app ---> 995a21532fce Removing intermediate container a3b7591bf46d Step 7 : EXPOSE 8080 ---> Running in fddb8afb98d7 ---> e9539311a23e Removing intermediate container fddb8afb98d7 Step 8 : CMD npm start ---> Running in a262fd016da6 ---> fdd93d9c2c60 Removing intermediate container a262fd016da6 Successfully built fdd93d9c2c60 |
Where are the layers located in this heap? Then at the end of each step you will see a line like this ---> 530c750a346e
that is the layer created at that step with a random ID. So every step creates a layer, it’s not RUN
, COPY
and ADD
. Then I will explain. We issue a history command to see all the layers created.
1 2 3 4 5 6 7 8 9 10 11 12 | docker history <image> $ docker history expressweb IMAGE CREATED CREATED BY SIZE fdd93d9c2c60 2 days ago /bin/sh -c CMD ["npm" "start"] 0 B e9539311a23e 2 days ago /bin/sh -c EXPOSE 8080/tcp 0 B 995a21532fce 2 days ago /bin/sh -c COPY dir:50ab47bff7 760 B ecf7275feff3 2 days ago /bin/sh -c npm install 3.439 MB 334d93a151ee 2 days ago /bin/sh -c COPY file:551095e67 265 B 86c81d89b023 2 days ago /bin/sh -c WORKDIR /usr/src/app 0 B 7184cc184ef8 2 days ago /bin/sh -c mkdir -p /usr/src/app 0 B 530c750a346e 2 days ago /bin/sh -c CMD ["node"] 0 B |
In the IMAGE column, you can see that it lists all the ID layers created at each step but pay attention to the SIZE section. Have you seen the difference yet. The command to create layers is size> 0 and in principle when building an image, if there is a change from the previous layer, a new layer will be created and with commands of size> 0 it changes the current layer so it is possible. Of course it will create a new layer.
How to write Dockerfile
After getting to know the Dockerfile components, the next thing is how to write Dockerfile from those components. Referring to the above example, we can configure the structure of a Dockerfile which will consist of the following main parts:
- FROM: define base image. The first command of any Dockerfile.
- Set workdir: specify the working directory to copy source and install applications, for example. This is essential for separating applications from each other
- Install the application: After you have the source code, it must be run build and start application, right
- Customizing the configuration: This is the final step to finalize what you will publicize with the container created from the image defined with Dockerfile. Which port is, which command to run. This is where you will use commands like
EXPOSE
,CMD
orENTRYPOINT
. And one thing to note is that don’t forgetHEALTHCHECK
for some important containers.
Note when writing Dockerfile, you should also set .dockerignore to remove unnecessary files from the build process. This helps you not to rebuild the image when there are minor changes such as updating README.md …
Add an example to write in the structure above for you to use
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base WORKDIR /app FROM microsoft/dotnet:2.1-sdk AS build WORKDIR /src COPY . . WORKDIR /src/DemoService RUN dotnet restore RUN dotnet build FROM build AS publish RUN dotnet publish -c Debug -o /app FROM base AS final WORKDIR /app COPY --from=publish /app . EXPOSE 20016 ENTRYPOINT ["dotnet", "DemoService.dll", "--debug"] |
In the above example, I have used multi-stage build to optimize the build image process. You can refer to this article for more ways to tweak Dockerfile or refer to best practices from the docker homepage.
How to use Dockerfile
Dockerfile is used to build images but nothing more. To build the image we execute the following command:
1 2 | docker build [OPTIONS] PATH | URL | - |
Usually we execute this command right in the folder containing Dockerfile so about PATH
or URL
, I usually do not recommend using it. In my opinion for the docker build, we should only care about the -t
(or --tag
) option to assign names and tags to the image. Eg:
docker build -t demoapp:latest
The above command will build the image with the name demoapp
and the tag is latest
. Note format is name:tag
offline. After building the image, we perform the docker run
to start the container from that image. Easy right? Doc for docker build and docker run
You can also use Dockerfile in the docker compose, where to gather individual Dockerfiles so that they run together. Such as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | version: '3.4' services: demoapp: build: context: . dockerfile: demoapp/Dockerfile image: demo/demoapp environment: - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT} ports: - "11223:11223" demoapp-worker: build: context: . dockerfile: worker/Dockerfile image: demo/demoapp-worker environment: - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT} |
The Dockerfile will be executed when running the docker-compose build
or docker-compose up
with the --build
option (make the image build before starting the container). I plan to write one more post about the docker compose after doing more research, but now the program is still buzzing so I don’t dare to fear writing.
Epilogue
Dockerfile is a key component, the heart of docker so hopefully through this article, you have a better view of Dockerfile and more confidence when you start working with it. If you have any questions or comments, please leave a comment. If you find it interesting, I will synthesize and put it into serial questions about my docker. Many thanks for taking the time to read this article. Cheers.
Well below I would like to take a note of some good articles about Dockerfile that I have referenced when writing this article. It is worth reading.
Part 2 – Dockerfile in serial Docker: Unknown to the author of Hoan Ky: link
Docker image in production – a 1GB or 100MB story by Minh Momen: link
Refer
- https://viblo.asia/p/docker-chua-biet-gi-den-biet-dung-phan-2-dockerfile-RQqKLzeOl7z
- https://viblo.asia/p/docker-image-in-production-cau-chuyen-1gb-hay-100mb-LzD5dXyE5jY
- https://docs.docker.com/engine/reference/builder/
- https://kapeli.com/cheat_sheets/Dockerfile.docset/Contents/Resources/Documents/index
- https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#create-ephemeral-containers