{"id":297,"date":"2023-09-21T01:32:00","date_gmt":"2023-09-21T01:32:00","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=297"},"modified":"2023-09-21T01:32:00","modified_gmt":"2023-09-21T01:32:00","slug":"build-docker-image","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=297","title":{"rendered":"How to Build Docker Image [Comprehensive Beginners Guide]"},"content":{"rendered":"<p>In this article, you will learn to build a Docker image from scratch and deploy and run your application as a Docker container using <code>Dockerfile<\/code><\/p>\n<p>As you know Docker is a tool for packaging, deploying, and running applications in lightweight <a href=\"https:\/\/devopscube.com\/what-is-a-container-and-how-does-it-work\/\">containers<\/a>. If you want to learn about the basics of Docker, refer to the <a href=\"https:\/\/devopscube.com\/what-is-docker\/\">Docker explained<\/a> blog.<\/p>\n<p>If you don&#8217;t have Docker installation, check out the <a href=\"https:\/\/devopscube.com\/how-to-install-and-configure-docker\/\">docker installation guide<\/a>.<\/p>\n<h2 id=\"dockerfile-explained\">Dockerfile Explained<\/h2>\n<p>The very basic building block of a Docker image is a <code>Dockerfile<\/code><\/p>\n<p>A <code>Dockerfile<\/code> is a simple text file with instructions and arguments. Docker can build images automatically by reading the instructions given in a <code>Dockerfile<\/code>.<\/p>\n<p>In a Dockerfile everything on the left is <strong>INSTRUCTION<\/strong>, and on the right is an <strong>ARGUMENT<\/strong> to those instructions. Remember that the file name is <code>\"Dockerfile\"<\/code> without any extension.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-4-36.png\" class=\"kg-image\" alt=\"Dockerfile instructions and arguments\" loading=\"lazy\" width=\"454\" height=\"200\"><\/figure>\n<p>The following table contains the important <strong>Dockerfile<\/strong> instructions and their explanation.<\/p>\n<p><!--kg-card-begin: html--><\/p>\n<table>\n<thead>\n<tr>\n<th>Dockerfile Instruction<\/th>\n<th>Explanation<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>FROM<\/td>\n<td>To specify the base image that can be pulled from a container registry( Docker hub, GCR, Quay, ECR, etc.)<\/td>\n<\/tr>\n<tr>\n<td>RUN<\/td>\n<td>Executes commands during the image build process.<\/td>\n<\/tr>\n<tr>\n<td>ENV <\/td>\n<td>Sets environment variables inside the image. It will be available during build time as well as in a running container. If you want to set only build-time variables, use ARG instruction.<\/td>\n<\/tr>\n<tr>\n<td>COPY<\/td>\n<td>Copies local files and directories to the image<\/td>\n<\/tr>\n<tr>\n<td>EXPOSE<\/td>\n<td>Specifies the port to be exposed for the Docker container.<\/td>\n<\/tr>\n<tr>\n<td>ADD<\/td>\n<td>It is a more feature-rich version of the COPY instruction. It also allows copying from the URL that is the source and <strong>tar file auto-extraction<\/strong> into the image. However, usage of COPY command is recommended over ADD. If you want to download remote files, use curl or get using RUN.<\/td>\n<\/tr>\n<tr>\n<td>WORKDIR<\/td>\n<td>Sets the current working directory. You can reuse this instruction in a Dockerfile to set a different working directory. If you set WORKDIR, instructions like <code>RUN<\/code>,&nbsp;<code>CMD<\/code>,&nbsp;<code>ADD<\/code>,&nbsp;<code>COPY<\/code>, or&nbsp;<code>ENTRYPOINT<\/code> gets executed in that directory.<\/td>\n<\/tr>\n<tr>\n<td>VOLUME<\/td>\n<td>It is used to create or mount the volume to the Docker container<\/td>\n<\/tr>\n<tr>\n<td>USER<\/td>\n<td>Sets the user name and UID when running the container. You can use this instruction to set a non-root user of the container.<\/td>\n<\/tr>\n<tr>\n<td>LABEL<\/td>\n<td>It is used to specify metadata information of Docker image<\/td>\n<\/tr>\n<tr>\n<td>ARG<\/td>\n<td>Is used to set build-time variables with key and value. the ARG variables will not be available when the container is running. If you want to persist a variable on a running container, use ENV.<\/td>\n<\/tr>\n<tr>\n<td>SHELL<\/td>\n<td>This instruction is used to set shell options and default shell for the RUN, CMD, and ENTRYPOINT instructions that follow it.<\/td>\n<\/tr>\n<tr>\n<td>CMD<\/td>\n<td>It is used to execute a command in a running container. There can be only one CMD, if multiple CMDs then it only applies to the last one. It <strong>can be overridden<\/strong> from the Docker CLI.<\/td>\n<\/tr>\n<tr>\n<td>ENTRYPOINT<\/td>\n<td>Specifies the commands that will execute when the Docker container starts. If you don&#8217;t specify any ENTRYPOINT, it defaults to <code>\/bin\/sh -c<\/code>. You can also <strong>override ENTRYPOINT<\/strong> using the <code>--entrypoint <\/code>flag using CLI. Please refer <a href=\"https:\/\/devopscube.com\/run-scripts-docker-arguments\/\" data-type=\"URL\" data-id=\"https:\/\/devopscube.com\/run-scripts-docker-arguments\/\">CMD vs ENTRYPOINT<\/a> for more information.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><!--kg-card-end: html--><\/p>\n<h2 id=\"build-docker-image-using-dockerfile\">Build Docker Image Using Dockerfile<\/h2>\n<p>In this section, you will learn to build a docker image using a real-world example. We will <strong>create an Nginx docker image<\/strong> from scratch with a <strong>custom index page<\/strong>.<\/p>\n<p>The following image shows the high-level workflow of the image build process.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/docker-build-workflow-1.png\" class=\"kg-image\" alt=\"Docker image build workflow\" loading=\"lazy\" width=\"581\" height=\"611\"><\/figure>\n<p>Follow the steps given below to build a docker image.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-grey\">\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Github Repo:<\/strong><\/b> The Dockerfile and configs used for this article is hosted on a <a href=\"https:\/\/github.com\/techiescamp\/docker-image-examples?ref=devopscube.com\" rel=\"noreferrer noopener\">Docker image examples Github repo<\/a>. You can clone the repo for reference.<\/div>\n<\/div>\n<h3 id=\"step-1-create-the-required-files-and-folders\">Step 1: Create the required Files and folders<\/h3>\n<p>Create a folder named <code>nginx-image<\/code> and create a folder named <code>files<\/code><\/p>\n<pre><code>mkdir nginx-image &amp;&amp; cd nginx-image\nmkdir files<\/code><\/pre>\n<p>Create a .<code>dockerignore<\/code> file<\/p>\n<pre><code>touch .dockerignore<\/code><\/pre>\n<h3 id=\"step-2-create-a-sample-html-file-and-config-file\">Step 2: Create a sample HTML file and config file<\/h3>\n<p>When you build a docker image for real-time projects, it contains <strong>code or application config files.<\/strong><\/p>\n<p>For demo purposes, we will create a simple HTML file &amp; config file as our app code and package it using Docker. This is a simple<code> index.html<\/code> file. You can create your own if you want.<\/p>\n<p>cd into the files folder<\/p>\n<pre><code>cd files<\/code><\/pre>\n<p>Create a <code>index.html<\/code> file<\/p>\n<pre><code>vi index.html<\/code><\/pre>\n<p>Copy the following contents to the <code>index.html<\/code> and save the file.<\/p>\n<pre><code>&lt;html&gt;\n  &lt;head&gt;\n    &lt;title&gt;Dockerfile&lt;\/title&gt;\n  &lt;\/head&gt;\n  &lt;body&gt;\n    &lt;div class=\"container\"&gt;\n      &lt;h1&gt;My App&lt;\/h1&gt;\n      &lt;h2&gt;This is my first app&lt;\/h2&gt;\n      &lt;p&gt;Hello everyone, This is running via Docker container&lt;\/p&gt;\n    &lt;\/div&gt;\n  &lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n<p>Create a file name default<\/p>\n<pre><code>vi default<\/code><\/pre>\n<p>Copy the following contents to the default file.<\/p>\n<pre><code>server {\n    listen 80 default_server;\n    listen [::]:80 default_server;\n    \n    root \/usr\/share\/nginx\/html;\n    index index.html index.htm;\n\n    server_name _;\n    location \/ {\n        try_files $uri $uri\/ =404;\n    }\n}<\/code><\/pre>\n<h3 id=\"step-3-choose-a-base-image\">Step 3: Choose a Base Image<\/h3>\n<p>We use <code>FROM <\/code>command in the <code>Dockerfile<\/code> which instructs Docker to create an image based on an image that is available on the Docker hub or any container registry configured with Docker. We call it a <strong>base image<\/strong>.<\/p>\n<p>It is similar to how we create Virtual machines on the cloud from a virtual machine image.<\/p>\n<p>Choosing a base image depends on our application and os platform of choice. In our case, we will pick the <code>ubuntu:18.04<\/code> base image.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-grey\">\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note:<\/strong><\/b> Always use official\/org approved base images for your applications to avoid potential vulnerabilities. Towards the end, we have added all the public registries that has verified container base images. Also, when it comes to <b><strong style=\"white-space: pre-wrap;\">production use cases<\/strong><\/b>, always use a <a href=\"https:\/\/hub.docker.com\/_\/alpine?ref=devopscube.com\" rel=\"noreferrer noopener\">minimal base image like alpine<\/a> (<b><strong style=\"white-space: pre-wrap;\">just 5 Mib<\/strong><\/b>) or <a href=\"https:\/\/github.com\/GoogleContainerTools\/distroless?ref=devopscube.com\" rel=\"noreferrer noopener\">distroless images.<\/a> Distroless alpine is <b><strong style=\"white-space: pre-wrap;\">just 2 MiB<\/strong><\/b><\/div>\n<\/div>\n<h3 id=\"step-3-create-the-dockerfile\">Step 3: Create the Dockerfile<\/h3>\n<p>Create a Dockerfile in the <code>nginx-image<\/code>  folder. <\/p>\n<pre><code>vi Dockerfile<\/code><\/pre>\n<p>Here&#8217;s the simple <code>Dockerfile<\/code> content for our use case. Add the content to the Dockerfile.<\/p>\n<pre><code>FROM ubuntu:18.04  \nLABEL maintainer=\"contact@devopscube.com\" \nRUN  apt-get -y update &amp;&amp; apt-get -y install nginx\nCOPY files\/default \/etc\/nginx\/sites-available\/default\nCOPY files\/index.html \/usr\/share\/nginx\/html\/index.html\nEXPOSE 80\nCMD [\"\/usr\/sbin\/nginx\", \"-g\", \"daemon off;\"]<\/code><\/pre>\n<p>Here&#8217;s the explanation of each step:<\/p>\n<ol>\n<li>With <code>LABEL<\/code> instruction, we are adding metadata about the maintainer. It is not a mandatory instruction.<\/li>\n<li><code>FROM<\/code> instruction will pull the Ubuntu 18.04 version Image from the Docker hub.<\/li>\n<li>In the second line, we&#8217;re installing Nginx.<\/li>\n<li>Then we&#8217;re copying the Nginx default config file from the local <code>files<\/code> directory to the target image directory.<\/li>\n<li>Next, we&#8217;re copying our <code>index.html<\/code> file from the local <code>files<\/code> directory into the target image directory. It will overwrite the default <strong>index.html<\/strong> file created during the Nginx installation.<\/li>\n<li>We&#8217;re exposing port 80 as the Nginx service listens on port 80.<\/li>\n<li>At last, we&#8217;re running the Nginx server using <code>CMD<\/code> instructions when the Docker image launches.<\/li>\n<\/ol>\n<p>For Docker containers<strong>,<\/strong> the <code>daemon off;<\/code> directive tells Nginx to <strong>stay in the foreground<\/strong>. This means the nginx process will keep running and won&#8217;t stop until you stop the container. It disables the self-daemonizing behavior of Nginx. The <code>-g<\/code> option specifies a directive to Nginx.<\/p>\n<p>The reason we run the process in the foreground is to <strong>attach the console process<\/strong> to standard input, output, and error. Meaning, you can see logs or messages from the Nginx process<\/p>\n<h3 id=\"step-4-build-your-first-docker-image\">Step 4: Build your first Docker Image<\/h3>\n<p>The final folder &amp; file structure would look like the following.<\/p>\n<pre><code>nginx-image\n\u251c\u2500\u2500 Dockerfile\n\u2514\u2500\u2500 files\n    \u251c\u2500\u2500 default\n    \u2514\u2500\u2500 index.html<\/code><\/pre>\n<p>Now, we will build our image using the Docker command. The below command will build the image using <code>Dockerfile <\/code>from the same directory.<\/p>\n<p>docker build -t nginx:1.0 .<\/p>\n<ol>\n<li>-t is for tagging the image.<\/li>\n<li><code>nginx<\/code> is the name of the image.<\/li>\n<li><code>1.0<\/code> is the tag name. If you don&#8217;t add any tag, it defaults to the tag named latest.<\/li>\n<li>.  (dot) at the end means, we are referring to the Dockerfile location as the docker build context. That is our current directory.<\/li>\n<\/ol>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-5-46.png\" class=\"kg-image\" alt=\"docker image build using docker build command\" loading=\"lazy\" width=\"768\" height=\"588\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-5-46.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-5-46.png 768w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>If the <code>Dockerfile<\/code> is in another folder then you need to specify it explicitly.<\/p>\n<pre><code>docker build -t nginx \/path\/to\/folder <\/code><\/pre>\n<p>Now, we can list the images by using this command.<\/p>\n<pre><code>docker images<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-7-40.png\" class=\"kg-image\" alt=\"docker build tag\" loading=\"lazy\" width=\"658\" height=\"200\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-7-40.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-7-40.png 658w\"><\/figure>\n<p>We can see the tag is <code>1.0<\/code> here. If we want to put a specific tag we can put it like this <code>image-name:&lt;tag&gt;<\/code>. If you don&#8217;t specify any tag, it defaults to <code>latest<\/code> tag.<\/p>\n<pre><code>docker build -t nginx:2.0 .<\/code><\/pre>\n<p>A single image can have multiple tags. There are two approaches we generally follow to tag the image:<\/p>\n<ol>\n<li><strong>Stable Tags<\/strong> &#8211; We can continue to pull a specific tag, which continues to get updates. Our tags are always constant, but the image content is changed.<\/li>\n<li><strong>Unique Tags<\/strong> &#8211; We use a different and unique tag for each image. There are different ways to provide unique tags for example date-time stamp, build number, commit ID, etc.<\/li>\n<\/ol>\n<div class=\"kg-card kg-callout-card kg-callout-card-grey\">\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note<\/strong><\/b>: When it comes to production, a recommended docker image tagging method is <a href=\"https:\/\/semver.org\/?ref=devopscube.com\" rel=\"noreferrer noopener\">semantic versioning (Semver)<\/a>.<\/div>\n<\/div>\n<p>Docker caches the build steps. So if we build the image again the process will move a little faster. For example, it will not download the Ubuntu 18.04 image again.<\/p>\n<p>Using large images slows down the build and deployment time of containers. If you want to learn more about optimizing Docker images, check out <a href=\"https:\/\/devopscube.com\/reduce-docker-image-size\/\" rel=\"noreferrer noopener\">reduce docker image<\/a> guide.<\/p>\n<h3 id=\"step-5-test-the-docker-image\">Step 5: Test the Docker Image<\/h3>\n<p>Now after building the image, we will run the Docker image. The command will be<\/p>\n<pre><code>docker run -d -p 9090:80 --name webserver nginx:1.0 <\/code><\/pre>\n<p>Here,<\/p>\n<ol>\n<li><code>-d<\/code> flag is for running the container in detached mode<\/li>\n<li><code>-p<\/code> flag for the port number, the format is local-port:container-port<\/li>\n<li><code>--name<\/code> for the container name, webserver in our case<\/li>\n<\/ol>\n<p>We can check the container by using the below command<\/p>\n<pre><code>docker ps<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-8-34.png\" class=\"kg-image\" alt=\"docker container status\" loading=\"lazy\" width=\"1055\" height=\"155\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-8-34.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-8-34.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-8-34.png 1055w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Now in the browser, if you go to <code>http:\/\/&lt;host-ip&gt;:9090<\/code>, you can see the index page which displays the content in the custom HTML page we added to the docker image.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-12-25.png\" class=\"kg-image\" alt=\"valaidating nginx docker image by running the docker container.\" loading=\"lazy\" width=\"444\" height=\"255\"><\/figure>\n<h2 id=\"push-docker-image-to-docker-hub\">Push Docker Image To Docker Hub<\/h2>\n<p>To push our Docker image to the Docker hub, we need to create an account in the <a href=\"https:\/\/hub.docker.com\/?ref=devopscube.com\" rel=\"noreferrer noopener\">Docker hub<\/a>.<\/p>\n<p>Post that, execute the below command to log in from the terminal. It will ask for a username and password. Provide the Docker hub credentials.<\/p>\n<pre><code>docker login<\/code><\/pre>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-9-27.png\" class=\"kg-image\" alt=\"docker hub login\" loading=\"lazy\" width=\"681\" height=\"302\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-9-27.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-9-27.png 681w\"><\/figure>\n<p>After login, we now need to <strong>tag our image with the docker username<\/strong> as shown below.<\/p>\n<pre><code>docker tag nginx:1.0 &lt;username&gt;\/&lt;image-name&gt;:tag<\/code><\/pre>\n<p>For example, here <code>devopscube<\/code> is the dockerhub username.<\/p>\n<pre><code> docker tag nginx:1.0 devopscube\/nginx:1.0<\/code><\/pre>\n<p>Run <code>docker images<\/code> command again and check the tagged image will be there.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-10-35.png\" class=\"kg-image\" alt=\"tagging docker image with dockerhub username\" loading=\"lazy\" width=\"696\" height=\"255\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-10-35.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-10-35.png 696w\"><\/figure>\n<p>Now we can push our images to the Docker hub using the below command.<\/p>\n<pre><code>docker push devopscube\/nginx:1.0<\/code><\/pre>\n<p>Now you can check this image will be available in your Docker Hub account.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-11-30.png\" class=\"kg-image\" alt=\"docker image in docker hub\" loading=\"lazy\" width=\"674\" height=\"456\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-11-30.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-11-30.png 674w\"><\/figure>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/docker-image-build-1.gif\" class=\"kg-image\" alt=\"Docker image build to registry workflow\" loading=\"lazy\" width=\"1080\" height=\"1350\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/docker-image-build-1.gif 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/docker-image-build-1.gif 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/docker-image-build-1.gif 1080w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h2 id=\"using-heredoc-with-dockerfile\">Using heredoc With Dockerfile<\/h2>\n<p><code>Dockerfile<\/code> also supports <a href=\"https:\/\/tldp.org\/LDP\/abs\/html\/here-docs.html?ref=devopscube.com\" rel=\"noreferrer noopener\">heredoc<\/a> syntax. If we have multiple <code>RUN<\/code> commands then we can use <code>heredoc<\/code> syntax as shown below.<\/p>\n<pre><code>RUN &lt;&lt;EOF\napt-get update\napt-get upgrade -y\napt-get install -y nginx\nEOF<\/code><\/pre>\n<p>Also, let&#8217;s say you want to execute a Python script from a Dockerfile, you can use the following syntax.<\/p>\n<pre><code>RUN python3 &lt;&lt;EOF\nwith open(\"\/hello\", \"w\") as f:\n    print(\"Hello\", file=f)\n    print(\"World\", file=f)\nEOF<\/code><\/pre>\n<p>You can also use the heredoc syntax to create a file. Here is an Nginx example.<\/p>\n<pre><code>FROM nginx\n\nCOPY &lt;&lt;EOF \/usr\/share\/nginx\/html\/index.html\n&lt;html&gt;\n  &lt;head&gt;\n    &lt;title&gt;Dockerfile&lt;\/title&gt;\n  &lt;\/head&gt;\n  &lt;body&gt;\n    &lt;div class=\"container\"&gt;\n      &lt;h1&gt;My App&lt;\/h1&gt;\n      &lt;h2&gt;This is my first app&lt;\/h2&gt;\n      &lt;p&gt;Hello everyone, This is running via Docker container&lt;\/p&gt;\n    &lt;\/div&gt;\n  &lt;\/body&gt;\n&lt;\/html&gt;\nEOF<\/code><\/pre>\n<h2 id=\"dockerfile-best-practices\">Dockerfile Best Practices<\/h2>\n<p>Some of the <code>Dockerfile<\/code> practices that we should follow:<\/p>\n<ol>\n<li>Use a <code>.dockerignore<\/code> file to exclude unnecessary files and directories to increase the build\u2019s performance.<\/li>\n<li>Use <strong>trusted base images<\/strong> only and keep updating the images periodically.<\/li>\n<li>Each instruction in the <code>Dockerfile<\/code> adds an extra layer to the Docker image. Minimize the number of layers by consolidating the instructions to increase the build\u2019s performance and time.<\/li>\n<li>Run as a <strong>Non-Root User<\/strong> to avoid security breaches.<\/li>\n<li><strong>Keep the image small:<\/strong> Reduce the image size for faster deployment and avoid installing unnecessary tools in your image. Use minimal images to reduce the attack surface.<\/li>\n<li>Use specific tags over the latest tag for the image to avoid breaking changes over time.<\/li>\n<li>Avoid using multiple <code>RUN<\/code> commands as it creates multiple cacheable layers which will affect the efficiency of the build process.<\/li>\n<li>Never share or copy the application credentials or any sensitive information in the <code>Dockerfile<\/code>. If you use it, add it to .<code>dockerignore<\/code><\/li>\n<li>Use <code>EXPOSE <\/code>and <code>ENV<\/code> commands as late as possible in <code>Dockerfile<\/code>.<\/li>\n<li><strong>Use a linter: <\/strong>Use a linter like <a href=\"https:\/\/devopscube.com\/lint-dockerfiles-using-hadolint\/\">hadolint<\/a> to check your Dockerfile for common issues and best practices.<\/li>\n<li><strong>Use a single process per container:<\/strong> Each container should run a single process. This makes it easier to manage and monitor containers and helps to keep containers lightweight.<\/li>\n<li><strong>Use multi-stage builds:<\/strong> Use multi-stage builds to create smaller and more efficient images.<\/li>\n<\/ol>\n<h2 id=\"possible-docker-build-issues\">Possible Docker Build Issues<\/h2>\n<ol>\n<li>If there is a syntax error or an invalid argument in<code> Dockerfile,<\/code> <code>docker build <\/code>command will fail with an error message. Correct the syntax to resolve this.<\/li>\n<li>Always try to give the container name using <code>docker run <\/code>command otherwise Docker automatically assigns a name to the container and it might lead to several problems.<\/li>\n<li>Sometimes we get <strong>Bind for 0.0.0.0.:8080 failed: port is already allocated<\/strong> error, this is because some other software\/service is using these ports. We can check the listening ports using <code>netstat<\/code> or <code>ss<\/code> command. Use a different port to resolve this or stop that service.<\/li>\n<li>Sometimes Docker fails to download the packages with this error <strong>Failed to download package &lt;package-name&gt;<\/strong>. This is because the container may not be able to access the internet or other dependencies issues.<\/li>\n<\/ol>\n<h2 id=\"docker-image-registries\">Docker Image Registries<\/h2>\n<p>As mentioned in Step 1, you should always choose verified official base images for your application.<\/p>\n<p>The following table has the list of publicly available container registries where you can find officially verified base images and application images.<\/p>\n<p><!--kg-card-begin: html--><\/p>\n<table class=\"has-fixed-layout\">\n<thead>\n<tr>\n<th>Registry<\/th>\n<th>Base Images<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Docker<\/td>\n<td><a href=\"https:\/\/hub.docker.com\/search?q=&#038;type=image&#038;image_filter=official%2Cstore&#038;ref=devopscube.com\" target=\"_blank\" data-type=\"URL\" data-id=\"https:\/\/hub.docker.com\/search?q=&amp;type=image&amp;image_filter=official%2Cstore\" rel=\"noreferrer noopener\">Docker hub base images<\/a><\/td>\n<\/tr>\n<tr>\n<td>Google Cloud<\/td>\n<td><a href=\"https:\/\/github.com\/GoogleContainerTools\/distroless?ref=devopscube.com\" target=\"_blank\" data-type=\"URL\" data-id=\"https:\/\/github.com\/GoogleContainerTools\/distroless\" rel=\"noreferrer noopener\">Distroless base images<\/a><\/td>\n<\/tr>\n<tr>\n<td>AWS<\/td>\n<td><a href=\"https:\/\/gallery.ecr.aws\/?verified=verified&#038;operatingSystems=Linux&#038;page=1&#038;ref=devopscube.com\" target=\"_blank\" data-type=\"URL\" data-id=\"https:\/\/gallery.ecr.aws\/?verified=verified&amp;operatingSystems=Linux&amp;page=1\" rel=\"noreferrer noopener\">ECR public registry<\/a><\/td>\n<\/tr>\n<tr>\n<td>Redhat Quay<\/td>\n<td><a href=\"https:\/\/quay.io\/search?ref=devopscube.com\" target=\"_blank\" data-type=\"URL\" data-id=\"https:\/\/quay.io\/search\" rel=\"noreferrer noopener\">Quay Registry<\/a><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><!--kg-card-end: html--><\/p>\n<h2 id=\"docker-image-vs-containers\">Docker Image vs. Containers<\/h2>\n<p>A Docker image is a snapshot of the file system and application dependencies. It is an executable package of software that includes everything needed like application code, libraries, tools, dependencies, and other files to run an application. You can <strong>compare it to a VM golden image.<\/strong><\/p>\n<p>A Docker image is organized in read-only layers stacked on top of each other.<\/p>\n<p>A Docker container is a running instance of a Docker image. We create VMs from VM images. Similarly way we create a container from a container image. When you create a container from a Docker image, you are creating a writable layer on top of the existing image layers.<\/p>\n<p>The key difference between a Docker image and a container is the writable layer on top of the image. This means, that if you have five containers running from an image, all the containers share the same read-only layers from the image and only the top writable layer is different for all five containers.<\/p>\n<p>This means, that when you delete the container, the writable layer gets deleted.<\/p>\n<p>Images can exist without containers, whereas a container needs an image to run. We can create multiple containers from the same image, each with its own unique data and state.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-13-27.png\" class=\"kg-image\" alt=\"Docker image vs container\" loading=\"lazy\" width=\"751\" height=\"561\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-13-27.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-13-27.png 751w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h2 id=\"docker-image-build-faqs\">Docker Image Build FAQs<\/h2>\n<p>Following are the frequently asked questions regarding <a href=\"https:\/\/devopscube.com\/docker-image-build-and-promotion-pipeline\/\">docker image build<\/a>.<\/p>\n<h3 id=\"how-to-use-base-images-from-container-registries-other-than-docker-hub\">How to use base images from container registries other than Docker hub?<\/h3>\n<p>By default docker, the docker engine is configured with the docker hub container registry. So if you mention the image name, it pulls the image from the docker hub. However, if you want to use an image from a different container registry, you need to provide the full URL of the image. For example, FROM <code>gcr.io\/distroless\/static-debian11<\/code><\/p>\n<h3 id=\"what-is-docker-build-context\">What is Docker build context?<\/h3>\n<p><strong>Docker build context<\/strong> is the docker host location where all the code, files, configs, and Dockerfile are present during the docker build process. You can specify the current build context using a dot [.] or the path of the folder. Also, you can have the Dockerfile in a different location than the build context. As a best practice, always have only the required files in the build context. Or else you will have unwanted files and a bloated Docker image.<\/p>\n<h3 id=\"how-to-build-a-docker-image-from-a-git-repository\">How to build a Docker Image from a git repository?<\/h3>\n<p>You can use the docker build command with a git repository to build a docker image. The git repository should have the Dockerfile and required files for a successful image build.<\/p>\n<h2 id=\"whats-next\">Whats Next<\/h2>\n<p>In the same way, you can try building more Docker images.<\/p>\n<p>For example, you can try <a href=\"https:\/\/devopscube.com\/dockerize-java-application\/\">Dockerizing a Java application<\/a> and run it.<\/p>\n<p>Further you can dockerize the following applictions to gain more knowledge.<\/p>\n<ol>\n<li>Python<\/li>\n<li>NodeJS<\/li>\n<li>Django<\/li>\n<li>FastAPI<\/li>\n<\/ol>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>In this article, we discussed how we can <strong>build a Docker Image<\/strong> and run our app as a Docker container using a <code>Dockerfile<\/code>.<\/p>\n<p>We discussed  <code>Dockerfile<\/code> in detail and went through some good practices to write it.<\/p>\n<p>As a <a href=\"https:\/\/devopscube.com\/become-devops-engineer\/\">DevOps engineer<\/a>, it is important to have a solid understanding of Docker best practices before implementing them in a project. Additionally, to <a href=\"https:\/\/devopscube.com\/learn-kubernetes-complete-roadmap\/\">learn Kubernetes<\/a>, you need to understand the workflow of building container images.<\/p>\n<p>Podman is another container tool using which you can manage containers. To know more, check out the <a href=\"https:\/\/devopscube.com\/podman-tutorial-beginners\/\">podman tutorial.<\/a><\/p>\n<p>Ready for a real-world advanced use case? <\/p>\n<p>Learn about <a href=\"https:\/\/devopscube.com\/deploying-llama-with-docker-and-vllm\/\">deploying LLaMA models with Docker and vLLM<\/a> for GPU inference.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/build-docker-image\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Build Docker Image [Comprehensive Beginners Guide] \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/build-docker-image\/<\/p>\n","protected":false},"author":1,"featured_media":298,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-297","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/297","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=297"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/297\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/298"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=297"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=297"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=297"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}