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
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
WORKDIR – Set up a working directory in the container for the commands
CMD , and
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:
COPY (as well as
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:
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
# Create app directory
RUN mkdir -p /usr/src/app
# Install app dependencies
COPY package.json /usr/src/app/
RUN npm install
# Bundle app source
COPY . /usr/src/app
CMD [ "npm", "start" ]
When making the build it will look like this
$ docker build -t expressweb .
Step 1 : FROM node:argon
argon: Pulling from library/node...
Status: Downloaded newer image for node:argon
Step 2 : RUN mkdir -p /usr/src/app
---> Running in 5090fde23e44
Removing intermediate container 5090fde23e44
Step 3 : WORKDIR /usr/src/app
---> Running in 2987746b5fba
Removing intermediate container 2987746b5fba
Step 4 : COPY package.json /usr/src/app/
Removing intermediate container a678c817e467
Step 5 : RUN npm install
---> Running in 31ee9721cccb
Removing intermediate container 31ee9721cccb
Step 6 : COPY . /usr/src/app
Removing intermediate container a3b7591bf46d
Step 7 : EXPOSE 8080
---> Running in fddb8afb98d7
Removing intermediate container fddb8afb98d7
Step 8 : CMD npm start
---> Running in a262fd016da6
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
ADD . Then I will explain. We issue a history command to see all the layers created.
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
ENTRYPOINT. And one thing to note is that don’t forget
HEALTHCHECKfor 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
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
FROM microsoft/dotnet:2.1-sdk AS build
COPY . .
RUN dotnet restore
RUN dotnet build
FROM build AS publish
RUN dotnet publish -c Debug -o /app
FROM base AS final
COPY --from=publish /app .
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:
docker build [OPTIONS] PATH | URL | -
Usually we execute this command right in the folder containing Dockerfile so about
URL , I usually do not recommend using it. In my opinion for the docker build, we should only care about the
--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:
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.
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