{"id":2928,"date":"2020-05-12T18:46:22","date_gmt":"2020-05-12T13:16:22","guid":{"rendered":"https:\/\/opstree.com\/blog\/\/?p=2928"},"modified":"2020-05-13T14:52:35","modified_gmt":"2020-05-13T09:22:35","slug":"fasten-docker-build","status":"publish","type":"post","link":"https:\/\/opstree.com\/blog\/2020\/05\/12\/fasten-docker-build\/","title":{"rendered":"Fasten Docker build"},"content":{"rendered":"\r\n<div class=\"wp-block-image\">\r\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2985\" src=\"https:\/\/opstree.com\/blog\/\/wp-content\/uploads\/2020\/05\/flash.gif?w=400\" alt=\"Gif for Fasten Docker Build \" width=\"600\" height=\"336\" \/><\/figure>\r\n<\/div>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Context<\/h2>\r\n\r\n\r\n\r\n<p>Recently I started working on a microservices project, as a DevOps engineer my responsibility was to ensure smooth build and release of the project. One of the challenges that I was facing was the image building process of the projects was painfully slow. Following true Opstree spirit of <strong>continuous improvement<\/strong> I started exploring how I can fix this problem and finally got a decent success, I was able to reduce docker image build time from <strong>4 minutes to 20 seconds<\/strong>. In this blog, I would like to showcase various ways through which image building can be reduced drastically.<\/p>\r\n<p><!--more--><\/p>\r\n\r\n\r\n\r\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><cite>You can find the complete code available <a href=\"https:\/\/github.com\/OT-CONTAINER-KIT\/fasten-docker-build\" target=\"_blank\" rel=\"noopener\">here<\/a> in this repository<\/cite><\/blockquote>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Problem statement<\/h2>\r\n\r\n\r\n\r\n<p>I&#8217;m using a Springboot HelloWorld project to walk you through the problem statement and eventually the various solutions that we would be applying.<\/p>\r\n\r\n\r\n\r\n<p>The base of the problem lies in the fact that <strong>80-90%<\/strong> of image building time is consumed in <strong>downloading the dependencies<\/strong> defined in pom.xml. Since the scope of downloaded dependencies is limited to the image build process only, that&#8217;s why every time an image builds the complete process starts from the beginning.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">FROM maven:3-jdk-8\r\n\r\nLABEL maintainer=\"opensource@opstree.com\"\r\n\r\nWORKDIR \/usr\/src\/app\r\n\r\nADD . \/usr\/src\/app\r\nRUN mvn clean package -Dmaven.test.skip=true\r\n\r\nENTRYPOINT [\"java\",\"-jar\",\"\/usr\/src\/app\/target\/helloworld-0.0.1-SNAPSHOT.jar\"]\r\n<\/pre>\r\n\r\n\r\n\r\n<p>If we build a docker image using the above Dockerfile, it will take close to 3 minutes to build the image.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">$ make problem-build-package-with-time\r\nreal\t3m3.356s\r\nuser\t0m1.221s\r\nsys\t0m1.067s<\/pre>\r\n\r\n\r\n\r\n<h3 class=\"wp-block-heading\">Solution1 | Avoid downloading dependencies<\/h3>\r\n\r\n\r\n\r\n<p>We know the solution to this problem lies in the fact &#8220;<strong>if somehow we can skip downloading dependencies<\/strong>&#8221; our problem will be solved. If there would have been an option to mount the host system local repository( ~\/.m2) while building the image this problem would have been resolved.<\/p>\r\n\r\n\r\n\r\n<p>We have solved this problem by moving the artifact generation part out of the image building process. Artifact generation is done using a build container having the local maven repo mounted so that dependencies would be downloaded only if not already present.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">solution1-build:\r\n\tdocker run -it -v ~\/.m2\/repository:\/root\/.m2\/repository \\\r\n\t -w \/usr\/src\/mymaven -v ${PWD}:\/usr\/src\/mymaven --rm \\\r\n\t maven:3-jdk-8 mvn clean package -Dmaven.test.skip=true<\/pre>\r\n\r\n\r\n\r\n<p>Once the artifact generation is done the only thing that you have to do in your Dockerfile is to copy the generated artifact in your Docker image.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">FROM maven:3-jdk-8\r\n\r\nLABEL maintainer=\"opensource@opstree.com\"\r\n\r\nWORKDIR \/usr\/src\/app\r\n\r\nADD target\/helloworld-0.0.3-SNAPSHOT.jar \/usr\/src\/app\/app.jar\r\n\r\nENTRYPOINT [\"java\",\"-jar\",\"\/usr\/src\/app\/app.jar\"]\r\n<\/pre>\r\n\r\n\r\n\r\n<p>Now if we build our image post this solution the image build time will drastically reduce to ~20 seconds<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">solution1-build:\r\n\tdocker run -it -v ~\/.m2\/repository:\/root\/.m2\/repository \\\r\n\t -w \/usr\/src\/mymaven -v ${PWD}:\/usr\/src\/mymaven --rm \\\r\n\t maven:3-jdk-8 mvn clean package -Dmaven.test.skip=true\r\n\r\nsolution1-package:\r\n\tdocker build -t opstree\/fasten-build -f Dockerfile.solution1 .\r\n\r\nsolution1-build-package:\r\n\tmake solution1-build\r\n\tmake solution1-package\r\n\r\nsolution1-build-package-with-time:\r\n\ttime make solution1-build-package &gt;\/dev\/null 2&gt;&amp;1\r\n\r\n$ make solution1-build-package-with-time\r\ntime make solution1-build-package &gt;\/dev\/null 2&gt;&amp;1\r\n\r\nreal\t0m15.212s\r\nuser\t0m0.288s\r\nsys\t0m0.286s<\/pre>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Solution2 | Leverage docker layer caching<\/h2>\r\n\r\n\r\n\r\n<p>One of the very interesting concepts of docker is the caching of layers where a <strong>layer is only built if there are supposed to be some changes<\/strong>.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">FROM maven:3-jdk-8\r\n\r\nLABEL maintainer=\"opensource@opstree.com\"\r\n\r\nWORKDIR \/usr\/src\/app\r\n\r\nADD pom.xml \/usr\/src\/app\r\nRUN mvn dependency:resolve -Dmaven.test.skip=true\r\n\r\nADD . \/usr\/src\/app\r\nRUN mvn clean install -Dmaven.test.skip=true\r\n\r\nENTRYPOINT [\"java\",\"-jar\",\"\/usr\/src\/app\/target\/helloworld-0.0.3-SNAPSHOT.jar\"]<\/pre>\r\n\r\n\r\n\r\n<p>If you notice <strong>lines 7 &amp; 8<\/strong> are the new addition in comparison to the original Dockerfile. When the image will be built using this Dockerfile, the layers corresponding to lines 7 &amp; 8 will be only built if &amp; only if there is a <strong>change in pom.xml<\/strong> else the previously built layers cache will be used. Hence no time will be wasted in downloading the compile-time dependencies(A concept unique to maven).<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">solution2-build-package:\r\n\ttime docker build -t opstree\/fasten-build -f \\\r\nDockerfile.solution2 .\r\n\r\nsolution2-build-package-with-time:\r\n\ttime make solution2-build-package &gt;\/dev\/null 2&gt;&amp;1\r\n\r\n$ make solution2-build-package-with-time\r\ntime make solution2-build-package &gt;\/dev\/null 2&gt;&amp;1\r\n\r\nreal\t1m32.215s\r\nuser\t0m0.788s\r\nsys\t0m0.787s<\/pre>\r\n\r\n\r\n\r\n<h3 class=\"wp-block-heading\">Solution 3 | Best of both worlds<\/h3>\r\n\r\n\r\n\r\n<p>Solution2 worked pretty well, the only problem in the approach was that if even there would be 1 single change of line in pom.xml the dependencies layer would be built again.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">FROM maven:3-jdk-8\r\n\r\nLABEL maintainer=\"opensource@opstree.com\"\r\n\r\nWORKDIR \/usr\/src\/app\r\n\r\nADD . \/usr\/src\/app\r\nRUN mvn clean package -Dmaven.test.skip=true\r\n<\/pre>\r\n\r\n\r\n\r\n<p>In solution 3 we are sort of combining Solution1 and Solution 2 where the dependencies downloading is moved out via an intermediate builder image as shown above.<\/p>\r\n\r\n\r\n\r\n<p>The final image build process will use this build image as a base image that will already have all the dependencies inside it. Even if there is a new dependency added in pom.xml the final image will only <strong>download that delta dependency<\/strong> as the rest of the dependencies would already be present via base builder image.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">FROM opstree\/fasten-build-builder\r\n\r\nADD . \/usr\/src\/app\r\n\r\nRUN mvn clean package -Dmaven.test.skip=true\r\n\r\nENTRYPOINT [\"java\",\"-jar\",\"\/usr\/src\/app\/target\/helloworld-0.0.3-SNAPSHOT.jar\"]<\/pre>\r\n\r\n\r\n\r\n<p>The complete image building process would be executed as given below, please note that you <strong>don&#8217;t have to build the builder image frequently<\/strong> it can be a scheduled operation nightly or weekly depending on the dependencies update in your pom.xml.<\/p>\r\n\r\n\r\n\r\n<pre class=\"wp-block-syntaxhighlighter-code\">solution3-build-builder:\r\n\tdocker build -t opstree\/fasten-build-builder \\\r\n\t -f Dockerfile.solution3.builder .\r\n\r\nsolution3-build-package:\r\n\tdocker build -t opstree\/fasten-build \\\r\n\t-f Dockerfile.solution3 .\r\n\r\nsolution3-build-package-with-time:\r\n\ttime make solution3-build-package &gt;\/dev\/null 2&gt;&amp;1\r\n\r\n$ make solution3-build-package-with-time\r\ntime make solution3-build-package &gt;\/dev\/null 2&gt;&amp;1\r\n\r\nreal\t0m4.800s\r\nuser\t0m0.224s\r\nsys\t0m0.200s<\/pre>\r\n\r\n\r\n\r\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\r\n\r\n\r\n\r\n<p>In conclusion, I would like to summarise that you can either go for Solution 1\/2 or 3 on case to case basis. <strong>Solution 2 will be apt when your pom.xml is stabilized<\/strong> and there are rarely any changes in pom.xml.<\/p>\r\n\r\n\r\n\r\n<p>Also, you would have noticed that the Dockerfile is not written as per best practices i.e non-root user, multistage docker build that I didn&#8217;t cover intentionally. That I would like to cover in the next blog.<\/p>\r\n\r\n\r\n\r\n<p>More importantly, I would like to stress the most important part that is <strong>continuous improvement<\/strong> whenever you do work it&#8217;s fine that you start with a workable solution but then you should be in constant pursuit of taking it to next level.<\/p>\r\n\r\n\r\n\r\n<p>Feel free to give any feedback or recommendation would love to hear your thoughts or if you have another smart solution that would be great. Till we meet next time <strong>Happy Learning<\/strong>.<\/p>\r\n<p>Gif reference: https:\/\/giphy.com\/gifs\/the-flash-Z2pZfL0YPC9sk<\/p>\r\n<p>&nbsp;<\/p>\r\n<p>Opstree is an End to End DevOps solution provider<\/p>\r\n<p>\r\n\r\n<\/p>\r\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\r\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link\" title=\"https:\/\/www.opstree.com\/contact-us\" href=\"https:\/\/www.opstree.com\/contact-us\" target=\"_blank\" rel=\"noopener\">contact\u00a0<\/a><\/div>\r\n<\/div>\r\n","protected":false},"excerpt":{"rendered":"<p>Context Recently I started working on a microservices project, as a DevOps engineer my responsibility was to ensure smooth build and release of the project. One of the challenges that I was facing was the image building process of the projects was painfully slow. Following true Opstree spirit of continuous improvement I started exploring how &hellip; <a href=\"https:\/\/opstree.com\/blog\/2020\/05\/12\/fasten-docker-build\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Fasten Docker build&#8221;<\/span><\/a><\/p>\n","protected":false},"author":167208048,"featured_media":29900,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[28070474,4504191],"tags":[242358,768739305,699635298],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/opstree.com\/blog\/wp-content\/uploads\/2025\/11\/DevSecOps-1.jpg","jetpack_likes_enabled":true,"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pfDBOm-Le","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/2928"}],"collection":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/users\/167208048"}],"replies":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/comments?post=2928"}],"version-history":[{"count":24,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/2928\/revisions"}],"predecessor-version":[{"id":2992,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/posts\/2928\/revisions\/2992"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/media\/29900"}],"wp:attachment":[{"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/media?parent=2928"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/categories?post=2928"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/opstree.com\/blog\/wp-json\/wp\/v2\/tags?post=2928"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}