# Lesson 3: Image layers 1. Change directory into `lesson03`. 1. Build the `hello-world` image with the tag `v1`: $ docker build . -t hello-world:v1 1. Each Docker image consists of a sequence of file system _layers_ with the later layers overwriting earlier ones. Each layer corresponds to an instruction in the `Dockerfile`. Let's look at the layers. (Note: in this and subsequent displays I leave off the CREATED column to save space). $ docker history hello-world:v1 IMAGE CREATED BY f38d648975d5 /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/roo... 09b895731b10 /bin/sh -c chmod a+x /root/run.sh 07d951eff6a0 /bin/sh -c #(nop) ADD file:ae2a94e6e79a95786â¦... 34fd6d6fbb15 /bin/sh -c #(nop) LABEL maintainer=adamhl@s... 589ac6f94be4 /bin/sh -c #(nop) CMD ["bash"] /bin/sh -c #(nop) ADD file:422aca8901ae3d869... $ docker history debian:buster-slim IMAGE CREATED BY 589ac6f94be4 /bin/sh -c #(nop) CMD ["bash"] /bin/sh -c #(nop) ADD file:422aca8901ae3d869... 1. Notice that the `hello-world:v1` image's first two layers are the same as `debian:buster-slim`'s layers. This reflects the fact that `hello-world:v1` starts FROM the `debian:buster-slim` image. 1. The subsequent layers of the `hello-world:v1` image each correspond to the commands in the `Dockerfile`. Here is the Dockerfile again as a reminder: # Dockerfile FROM debian:buster-slim LABEL maintainer="adamhl@stanford.edu" ADD run.sh /root/run.sh RUN chmod a+x /root/run.sh CMD /root/run.sh 1. When you create a running Docker container using `docker run` the Docker system loads your image with each layer being _read-only_ with a final, new layer being added. This last layer is writable. Let's try it. $ docker run --rm --name=fuzzle busybox /bin/sh -c "sleep 10000" & $ docker exec -ti fuzzle /bin/sh (we "login" to the running container) / # cd /root; ls ~ # date > date.txt; ls -l -rw-r--r-- 1 root root 0 Jan 16 03:56 date.txt ~ # cat /root/date.txt Sat Jan 16 03:57:15 UTC 2021 ~ # exit 1. The container is still running. Let's make sure the file we created is still there. $ docker exec -ti fuzzle /bin/sh ~ # cat /root/date.txt Sat Jan 16 03:57:15 UTC 2021 ~ # exit 1. However, once the container exits, that final writable layer is thrown away. **It does not persist**. Let's see. $ docker rm fuzzle # You should get an error here. Why? 1. To stop a running container from the outside use the `docker kill` command. $ docker kill fuzzle $ docker rm fuzzle # This last command returns an error. Why? 1. To see that the file `/root/date.txt` is really gone, let's start the container again and look. $ docker run --rm --name=fuzzle busybox /bin/sh -c "sleep 10000" & $ docker exec fuzzle ls -l /root 1. Notice that we did not login to the container to do an `ls`, rather, we used the `exec` command. 1. To reiterate, changes made to the containers top writable layer do not persist. If you want the container to make persistent changes to files another mechanism is needed such as mounting an external volume or writing to some other persistent data store; more on that in a later lesson. 1. Back to layers. 1. When we looked at the history of the `hello-world:v1` image we saw this: $ docker history hello-world:v1 IMAGE CREATED BY f38d648975d5 /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/roo... 09b895731b10 /bin/sh -c chmod a+x /root/run.sh 07d951eff6a0 /bin/sh -c #(nop) ADD file:ae2a94e6e79a95786â¦... 34fd6d6fbb15 /bin/sh -c #(nop) LABEL maintainer=adamhl@s... 589ac6f94be4 /bin/sh -c #(nop) CMD ["bash"] /bin/sh -c #(nop) ADD file:422aca8901ae3d869... 1. Under the `IMAGE` column are hex strings. Those correspond to the SHA256 hash of the new layer's content. (More precisely, the SHA256 hash of the layers configuration object.) 1. Think of these hashes (roughly) corresponding to git commit hashes. The layers and their hashes are useful for they tell Docker when a layer has changed. We use a copy of `Dockerfile` with one small change: # Dockerfile-v2 FROM debian:buster-slim LABEL maintainer="adamhl@stanford.edu" ADD run.sh /root/run.sh RUN chmod a+rx /root/run.sh # This is the changed line. CMD /root/run.sh 1. Build the image and look at its history: $ docker build . -f Dockerfile-v2 -t hello-world:v2 $ docker history hello-world:v2 IMAGE CREATED BY 0dd6cfe9bb51 /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/roo... 39085b76a64f /bin/sh -c chmod a+rx /root/run.sh 07d951eff6a0 /bin/sh -c #(nop) ADD file:ae2a94e6e79a95786... 34fd6d6fbb15 /bin/sh -c #(nop) LABEL maintainer=adamhl@s... 589ac6f94be4 /bin/sh -c #(nop) CMD ["bash"] /bin/sh -c #(nop) ADD file:422aca8901ae3d869... $ docker history hello-world:v1 IMAGE CREATED BY f38d648975d5 /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/roo... 09b895731b10 /bin/sh -c chmod a+x /root/run.sh 07d951eff6a0 /bin/sh -c #(nop) ADD file:ae2a94e6e79a95786â¦... 34fd6d6fbb15 /bin/sh -c #(nop) LABEL maintainer=adamhl@s... 589ac6f94be4 /bin/sh -c #(nop) CMD ["bash"] /bin/sh -c #(nop) ADD file:422aca8901ae3d869... 1. Note that the layers have the same image ID except for the layers starting with the one we changed. The top layers have different id's since even though they are the same Dockerfile command they derive from the layer that changed (again, think of a git commit). 1. This has the pleasant result that if the first N Dockerfile commands do not change but the N+1'st command _does_ change, then `docker build` will use the cached layers for the first N layers and only rebuild from layer N+1 onwards. This means rebuilds can be quite fast. 1. **Important.** The image changes whenever one or more of the Dockerfile `ADD`, `COPY`, or `RUN` command lines change. Even adding whitespace to a line that has no real effect can trigger a new layer. 1. What does it mean for a Dockerfile line to change? Either the line in the Dockerfile itself changes (as we saw above) **or**, for commands that add files from the local environment, the file being added changes. For example, if we added a comment line to `run.sh` this counts as a change to the `ADD run.sh /root/run.sh` line and would trigger a layer rebuild from that layer forward. 1. Edit the file `run.sh` by adding a comment and then rebuild: #!/bin/sh # An extra comment echo "Hello, world." exit 0 $ docker build . -t hello-world:v3 $ docker history hello-world:v1 IMAGE CREATED BY 2131b85a91d5 /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/roo... 30a489d1e5b0 /bin/sh -c chmod a+x /root/run.sh 94667dcd1388 /bin/sh -c #(nop) ADD file:ae2a94e6e79a95786... ebca792f7949 /bin/sh -c #(nop) LABEL maintainer=adamhl@s... 589ac6f94be4 /bin/sh -c #(nop) CMD ["bash"] /bin/sh -c #(nop) ADD file:422aca8901ae3d869... $ docker history hello-world:v3 IMAGE CREATED BY acc54a5d1b49 /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/roo... 82b9cde2c517 /bin/sh -c chmod a+x /root/run.sh 2a19083a38b0 /bin/sh -c #(nop) ADD file:997845d6d2fdc9492... ebca792f7949 /bin/sh -c #(nop) LABEL maintainer=adamhl@s... 589ac6f94be4 /bin/sh -c #(nop) CMD ["bash"] /bin/sh -c #(nop) ADD file:422aca8901ae3d869... 1. Notice that the `ADD` line has changed its image id. 1. A gotcha: your Dockerfile installs a package downloaded from an external repository (like Debian). You build the image. A little while later the package is updated in the Debian repository. You rebuild the container thinking the rebuild will catch this change. But is does NOT. The docker build only notices changes to the Dockerfile itself, not to sources external to the build environment. 1. A way around this gotcha. If you want to be sure that every time you build your container it rebuilds each layer regardless of any changes to the Dockerfile use the `--no-cache` option. This has the advantage of reliability but the disadvantage that it can take significantly more time. $ docker build . -t hello-world:v3 (this will be fast) $ docker build . --no-cache -t hello-world:v3 (this will slower) 1. Another advantage of Docker caching layers is that different images built off the same layer help save space. Recall that images layers are loaded read-only, so if two different running containers are built off the same layer, both containers can point to the same physical file. This is why spawning a second container running on the same image as the first can be so quick: the base layer is already loaded into memory so all that has to be done is add the final writable layer that is distinct for each container.