There are lots of Dockerfile tips and best practices out there but the problem is, who can remember them all ! In the end, all that matters is building a Docker Image as fast as possible and keeping it lean and mean. I’m going to cover 1 easy to remember Dockerfile tip that will serve you well going forward.
Dockerfile Tip on Caching – The 1 Strike Rule!
As expected, when building your Docker Image the very first time, there is no caching taking place. However on subsequent re-builds Docker verifies each line in your Dockerfile against its cache. When a cache hit occurs, that layer is re-used and we can achieve faster Docker Image builds.
The problem is that it can be downright tricky to take full advantage of the Docker cache even if your writing some really simple Dockerfiles (as I’ll show you).
So what’s the one Strike rule? Once Docker comes across a line in your Dockerfile that invalidates the cache, it will stop verifying the cache from that point on. There’s no going back. Strike one … your out of the cache game!
All subsequent Dockerfile lines will have a new image layer created in order to build yet another new Docker Image. Obviously this slows down the build process and is to be avoided when possible.
The lesson to be learnt here … You have to know when its best to strike out to take full advantage of the cache game. No one wants to strike out on the first throw! Let’s play.
Let’s go to Bat
Here’s a simple Dockerfile example to bring home the point …
1 2 3 4 5 6 7 |
FROM anapsix/alpine-java:jdk8 COPY file1.txt /home VOLUME /home EXPOSE 80 LABEL com.mvpjava.project.version="0.0.1" LABEL maintainer="MVP Java" |
Build the Docker Image via “docker build -t demo .” for the first time …
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
docker build -t demo . Sending build context to Docker daemon 4.096 kB Step 1/6 : <strong>FROM anapsix/alpine-java:jdk8 --- ed55c27d366d Step 2/6 : COPY file1.txt /home --- 539a456b8f7b Removing intermediate container 9923a61118c5 Step 3/6 : VOLUME /home --- Running in 0cd144c441bb --- f0b6226027f4 Removing intermediate container 0cd144c441bb Step 4/6 : EXPOSE 80 --- Running in 7333e886bc1e --- 744e2f79b45c Removing intermediate container 7333e886bc1e Step 5/6 : LABEL com.mvpjava.project.verion "0.0.1" --- Running in 762896922e8e --- 620bc042769e Removing intermediate container 762896922e8e Step 6/6 : LABEL maintainer "MVP Java" --- Running in e33b40d692cb --- bd32deb80926 Removing intermediate container e33b40d692cb Successfully built bd32deb80926 |
As expected, we did not benefit from caching since it was our first time. If we don’t change anything and re-run the build, we should benefit from re-using the cached image layers …
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
docker build -t demo . Sending build context to Docker daemon 4.096 kB Step 1/6 : FROM anapsix/alpine-java:jdk8 --- ed55c27d366d Step 2/6 : COPY file1.txt /home ---Using cache ---daf12ebeb748 Step 3/6 : VOLUME /home ---Using cache ---415ddd681b9f Step 4/6 : EXPOSE 80 ---Using cache ---b09cfb968d82 Step 5/6 : LABEL com.mvpjava.project.verion "0.0.1" ---Using cache ---155848fcdd08 Step 6/6 : LABEL maintainer "MVP Java" ---Using cache ---72a55db09590 Successfully built 72a55db09590 |
Good, all green! So what happens when we make a modification now? Let’s say file1.txt gets modified (we add a line) and then we re-build the Docker Image. Let’s see …
The Big Strike-out!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
docker build -t demo . Sending build context to Docker daemon 4.096 kB Step 1/6 : FROM anapsix/alpine-java:jdk8 ---ed55c27d366d Step 2/6 : COPY file1.txt /home ---9bd7edf98d81 Removing intermediate container 8ec29258bc3e Step 3/6 : VOLUME /home ---Running in 4220618616b1 ---099fc2676cbc Removing intermediate container 4220618616b1 Step 4/6 : EXPOSE 80 ---Running in 1cf68a8ab00e ---c0caf94b3cf0 Removing intermediate container 1cf68a8ab00e Step 5/6 : LABEL com.mvpjava.project.verion "0.0.1" ---Running in f1ebee8d1ab1 ---dddd167b0edc Removing intermediate container f1ebee8d1ab1 Step 6/6 : LABEL maintainer "MVP Java" ---Running in 4b8685344a8f ---7b44735a2833 Removing intermediate container 4b8685344a8f Successfully built 7b44735a2833 |
Wow, No caching occurred what so ever! In fact, Docker didn’t even re-use the image layers in the cache for the lines that didn’t change under the line “COPY file1.txt /home“. So what happened?
As soon as you invalidate the cache due to introducing a modification (our file1.txt in this case), Docker throws its hands up in the air and just gives up even trying to look any further from that point on, hence you lose all caching benefits. Is there anything that can be done to get around this?
Keep Swinging Until You Can’t Swing No More
You can get around this by putting the Dockerfile commands that are not likely to change on the top in order to benefit from caching and place the commands that will eventually invoke a cache invalidation after (not always obvious). This way you’ll use the Cache as long as possible until you eventually strike out; which you want to do as late as possible.
Let’s do just that …
- Modify file1.txt again (add another line)
- Move the line “COPY file1.txt /home” command to the end of the Dockerfile.
- Re-build once again
Here’s the output …
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
docker build -t demo . Sending build context to Docker daemon 4.096 kB Step 1/6 : FROM anapsix/alpine-java:jdk8 ---ed55c27d366d Step 2/6 : VOLUME /home ---Using cache ---307692a18920 Step 3/6 : EXPOSE 80 ---Using cache ---ada2bd8273c2 Step 4/6 : LABEL com.mvpjava.project.verion "0.0.1" ---Using cache ---b1fc86f6a04b Step 5/6 : LABEL maintainer "MVP Java" ---Using cache ---2e9b6a95d30f Step 6/6 : COPY file1.txt /home ---e8548f23fa3c Removing intermediate container b907eba6172b Successfully built e8548f23fa3c |
Notice that we re-used the cached layers this time all the way down until we struck out on Step 6/6. That’s playing it smart! We stayed in the cache game as long as we could just by moving that line to be bottom.
Now I know that the Dockerfile example above is simple but that’s the point 🙂 It’s the principle that you have to remember no matter how complicated the Dockerfile is.
There are more gotchas out there but I’ll keep this in line with the title of the post, mainly 1 tip! It’s short, to the point, easy to remember and will serve you well going forward.
For a full list of Dockerfile best practices, check out the official Docker link Best practices for writing Dockerfiles
Want to Get Docker Certified (DCA)? Then read my blog on How I Got Docker Certified