<p>The <code>RUN</code> statements represent each one layer. Imagine that one downloads a package, installs it and would like to remove it. If one uses three <code>RUN</code> statements then the image size will not shrink as there are separate layers. If one runs all the commands using one <code>RUN</code> statement the disk image size could be reduced.</p>

CC BY-SA 3.0
2017-08-14T15:26:33.373Z
2017-08-14T15:26:33.373Z
030
<p>Each instruction you create in your Dockerfile results in a new image layer being created. Each layer brings additional data that are not always part of the resulting image. For example, if you add a file in one layer, but remove it in another layer later, the final image’s size will include the added file size in a form of a special "whiteout" file although you removed it.</p>

<p>Let's say you have the following Dockerfile:</p>

<pre><code>FROM centos:6

RUN yum -y update 
RUN yum -y install epel-release
</code></pre>

<p>The resulting image size will be</p>

<pre><code>bigimage     latest        3c5cbfbb4116        2 minutes ago    407MB
</code></pre>

<p>As opposite, with "similar" Dockerfile:</p>

<pre><code>FROM centos:6

RUN yum -y update  &amp;&amp; yum -y install epel-release
</code></pre>

<p>The resulting image size will be</p>

<pre><code>smallimage     latest        7edeafc01ffe        3 minutes ago    384MB
</code></pre>

<p>You will get even smaller size, if you clean the yum cache in a single RUN statement.</p>

<p>So you want to keep balance between readability/easy of maintenance and number of layers/image size.</p>

CC BY-SA 3.0
2017-08-14T15:31:54.450Z
2017-08-14T15:31:54.450Z
oryades
<p>A docker image is actually a linked list of filesystem layers. Each instruction in a <em>Dockerfile</em> creates a filesystem layer that describes the differences in the filesystem before and after execution of the corresponding instruction. The <code>docker inspect</code> subcommand can be used on a docker image to reveal its nature of being a linked list of filesystem layers.</p>
<p>The number of layers used in an image is important</p>
<ul>
<li>when pushing or pulling images, as it affects the number of concurrent uploads or downloads occuring.</li>
<li>when starting a container, as the layers are combined together to produce the filesystem used in the container; the more layers are involved, the worse the performance is, but the different filesystem backends are affected differently by this.</li>
</ul>
<p>This has several consequences for how images should be built. The first and most important advice I can give is:</p>
<blockquote>
<p><strong>Advice #1</strong> Make sure that the build steps where your source code is involved comes as late as possible in the <em>Dockerfile</em> and are not tied to previous commands using a <code>&amp;&amp;</code> or a <code>;</code>.</p>
</blockquote>
<p>The reason for this, is that all the previous steps will be cached and the corresponding layers will not need to be downloaded over and over again. This means faster builds and faster releases, which is probably what you want.  Interestingly enough, it is surprisingly hard to make optimal use of the docker cache.</p>
<p>My second advice is less important but I find it very useful from a maintenance view point:</p>
<blockquote>
<p><strong>Advice #2</strong> Do not write complex commands in the <em>Dockerfile</em> but rather use scripts that are to be copied and executed.</p>
</blockquote>
<p>A <em>Dockerfile</em> following this advice would look like</p>
<pre><code>COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
COPY install_pacakges.sh /root/
RUN sh -x /root/install_packages.sh
</code></pre>
<p>and so on.  The advice of binding several commands with <code>&amp;&amp;</code> has only a limited scope. It is much easier to write with scripts, where you can use functions, etc. to avoid redundancy or for documentation purposes.</p>
<p>People interested by pre-processors and willing to avoid the small overhead caused by the <code>COPY</code> steps and are actually generating on-the-fly a <em>Dockerfile</em> where the</p>
<pre><code>COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
</code></pre>
<p>sequences are replaced by</p>
<pre><code>RUN base64 --decode … | sh -x
</code></pre>
<p>where the <code>…</code> is the base64-encoded version of <code>apt_setup.sh</code>.</p>
<p>My third advice is for people who wants to limit the size and the number of layers at the possible cost of longer builds.</p>
<blockquote>
<p><strong>Advice #3</strong> Use the <code>with</code>-idiom to avoid files present in intermediary layers but not in the resulting filesystem.</p>
</blockquote>
<p>A file added by some docker instruction and removed by some later instruction is not present in the resulting filesystem but it is mentioned two times in the docker layers constituting the docker image in construction. Once, with name and full content in the layer resulting from the instruction adding it, and once as a deletion notice in the layer resulting from the instruction removing it.</p>
<p>For instance, assume we temporarily need a C compiler and some image and consider the</p>
<pre><code># !!! THIS DISPLAYS SOME PROBLEM --- DO NOT USE !!!
RUN apt-get install -y gcc
RUN gcc --version
RUN apt-get --purge autoremove -y gcc
</code></pre>
<p>(A more realistic example would build some software with the compiler instead of merely asserting its presence with the <code>--version</code> flag.)</p>
<p>The Dockerfile snippet creates three layers, the first one contains the full gcc suite so that even if it is not present in the final filesystem the corresponding data is still part of the image in same manner and need to be downloaded, uploaded and unpacked whenever the final image is.</p>
<p>The <code>with</code>-idiom is a common form in functional programming to isolate resource ownership and resource releasing from the logic using it. It is easy to transpose this idiom to shell-scripting, and we can rephrase the previous commands as the following script, to be used with <code>COPY &amp; RUN</code> as in <em>Advice #2.</em></p>
<pre><code># with_c_compiler SIMPLE-COMMAND
#  Execute SIMPLE-COMMAND in a sub-shell with gcc being available.

with_c_compiler()
(
    set -e
    trap 'apt-get --purge autoremove -y gcc' EXIT
    apt-get install -y gcc
    &quot;$@&quot;
)

with_c_compiler\
    gcc --version
</code></pre>
<p>Complex commands can be turned into function so that they can be fed to the <code>with_c_compiler</code>. It is also possible to chain calls of several <code>with_whatever</code> functions, but maybe not very desirable. (Using more esoteric features of the shell, it is certainly possible to make the <code>with_c_compiler</code> accept complex commands, but it is in all aspects preferable to wrap these complex commands into functions.)</p>
<p>If we want to ignore Advice #2, the resulting Dockerfile snippet would be</p>
<pre><code>RUN apt-get install -y gcc\
 &amp;&amp; gcc --version\
 &amp;&amp; apt-get --purge autoremove -y gcc
</code></pre>
<p>which is not so easy to read and maintain because of the obfuscation. See how the shell-script variant outs emphasis on the important part <code>gcc --version</code> while the chained-<code>&amp;&amp;</code> variant buries that part in the middle of noise.</p>

CC BY-SA 3.0
2017-08-14T16:04:17.483Z
Could you include the outcome of the box size after building using a script and using the multiple commands in one RUN statement?
030
CC BY-SA 3.0
2017-08-14T18:23:56.137Z
It seems like a bad idea to me to mix the configuration of the image base (i.e. the OS stuff) and even libs with the set up of the source that you wrote.  You say "Make sure that the build steps where your source code is involved comes as late as possible".  Is there any issue in making that part a completely independent artifact?
JimmyJames
CC BY-SA 3.0
2017-08-14T18:26:21.680Z
@030 What do you mean with “box” size? I have no idea which box you are referring to.
Michaël Le Barbier
CC BY-SA 3.0
2017-08-14T18:27:13.967Z
I meant docker image size
030
CC BY-SA 3.0
2017-08-14T18:34:07.067Z
@JimmyJames It vastly depends on your deployment scenario. If we assume a compiled program, the “right thing to do” would be to package it and install that package dependencies and the package itself as two distinct near-to-be-final steps. This to maximise the usefulness of the docker cache and to avoid downloading over and over layers with the same files. I find it easier to share build recipes to build docker images than building long dependency chains of images, because the latter makes rebuilding harder.
Michaël Le Barbier
CC BY-SA 3.0
2017-08-14T18:37:23.697Z
@030 I never measured this. I am more concerned with the inflation of layers, even if they are as small as possible they incur some tangible overhead when pulling images.
Michaël Le Barbier
CC BY-SA 3.0
2017-08-14T19:19:58.990Z
@MichaelGrünewald Perhaps I misunderstand.  If I build image D->C->B->A where D is my image I will deploy, rebuilding D means I don't need to download those dependencies again unless I change something about them.  If I have D->A and put all the stuff from C and B in D, then I would think you need to recreate all those on every D rebuild.  You seem to be suggesting the opposite so I think I'm confused in one way or another.
JimmyJames
CC BY-SA 3.0
2017-08-14T20:42:38.127Z
@JimmyJames the `docker build` command maintains a cache of layers or intermediary images yielded by directives found in *Dockerfiles*. This means that even if you do not give a name to `A`, `B` and `C` they will still exist as intermediary cache artefacts.
Michaël Le Barbier
CC BY-SA 3.0
2017-08-14T20:51:47.897Z
@MichaelGrünewald That's good to know.  Are you saying it doesn't really matter?  I took another meaning from your prior comment.
JimmyJames
CC BY-SA 3.0
2017-08-15T07:46:58.007Z
@JimmyJames I only use a customised linux distribution as base image (with extra repos) and build every product image on top of it. So I just have images `D*` and `A`. It is possible to design the *Dockerfile* for `D*` so that it goes through the same build steps as building in turn `B`, `C` and `D`. Now taking full advantage of the docker cache requires careful preparation of the docker context, which is not necessarily trivial but aside from this, regarding building it does not matter. For testing or product owning it does, so you will need thought or experimentation to find your trade-offs.
Michaël Le Barbier
CC BY-SA 3.0
2017-08-21T13:50:39.860Z
For absolute completeness I had liked a paragraph about why removing files in a second RUN command doesn't reduce the size (as described in the proposed duplicate).
Tensibai
CC BY-SA 3.0
2017-08-21T15:36:01.433Z
@Tensibai Good point. I added a paragraph about it and a useful shell construct to ease the install-uninstall steps of the procedure.
Michaël Le Barbier
CC BY-SA 4.0
2017-08-14T16:00:42.640Z
2022-11-15T09:32:03.850Z
2022-11-15T09:32:03.850Z
Michaël Le Barbier
<p>We have the following block in our <code>Dockerfile</code>:</p>

<pre><code>RUN yum -y update
RUN yum -y install epel-release
RUN yum -y groupinstall "Development Tools"
RUN yum -y install python-pip git mysql-devel libxml2-devel libxslt-devel python-devel openldap-devel libffi-devel openssl-devel
</code></pre>

<p>I've been told that we should unite these <code>RUN</code> commands to cut down on created docker layers:</p>

<pre><code>RUN yum -y update \
    &amp;&amp; yum -y install epel-release \
    &amp;&amp; yum -y groupinstall "Development Tools" \
    &amp;&amp; yum -y install python-pip git mysql-devel libxml2-devel libxslt-devel python-devel openldap-devel libffi-devel openssl-devel
</code></pre>

<p>I'm very new to docker and not sure I completely understand the differences between these two versions of specifying multiple RUN commands. When would one unite <code>RUN</code> commands into a single one and when it makes sense to have multiple <code>RUN</code> commands?</p>

CC BY-SA 3.0
2017-08-14T17:35:42.573Z
Possible duplicate of [How to create the smallest working docker image every time?](https://devops.stackexchange.com/questions/413/how-to-create-the-smallest-working-docker-image-every-time)
030
<docker><dockerfile>
Understanding Docker layers
alecxe
# Understanding Docker layers

(Asked by: username_0 on 2017-08-14)

We have the following block in our Dockerfile:
RUN yum -y update
RUN yum -y install epel-release
RUN yum -y groupinstall "Development Tools"
RUN yum -y install python-pip git mysql-devel libxml2-devel libxslt-devel python-devel openldap-devel libffi-devel openssl-devel

I've been told that we should unite these RUN commands to cut down on created docker layers:
RUN yum -y update \
    && yum -y install epel-release \
    && yum -y groupinstall "Development Tools" \
    && yum -y install python-pip git mysql-devel libxml2-devel libxslt-devel python-devel openldap-devel libffi-devel openssl-devel

I'm very new to docker and not sure I completely understand the differences between these two versions of specifying multiple RUN commands. When would one unite RUN commands into a single one and when it makes sense to have multiple RUN commands?

**Tags:** docker, dockerfile

> **Comments:**
> **username_1**: Possible duplicate of [How to create the smallest working docker image every time?](https://devops.stackexchange.com/questions/413/how-to-create-the-smallest-working-docker-image-every-time)  

---

## 3 answers

### Answer by username_3 on 2017-08-14. Score: 41 | Accepted answer

A docker image is actually a linked list of filesystem layers. Each instruction in a Dockerfile creates a filesystem layer that describes the differences in the filesystem before and after execution of the corresponding instruction. The docker inspect subcommand can be used on a docker image to reveal its nature of being a linked list of filesystem layers.
The number of layers used in an image is important

when pushing or pulling images, as it affects the number of concurrent uploads or downloads occuring.
when starting a container, as the layers are combined together to produce the filesystem used in the container; the more layers are involved, the worse the performance is, but the different filesystem backends are affected differently by this.

This has several consequences for how images should be built. The first and most important advice I can give is:

Advice #1 Make sure that the build steps where your source code is involved comes as late as possible in the Dockerfile and are not tied to previous commands using a && or a ;.

The reason for this, is that all the previous steps will be cached and the corresponding layers will not need to be downloaded over and over again. This means faster builds and faster releases, which is probably what you want.  Interestingly enough, it is surprisingly hard to make optimal use of the docker cache.
My second advice is less important but I find it very useful from a maintenance view point:

Advice #2 Do not write complex commands in the Dockerfile but rather use scripts that are to be copied and executed.

A Dockerfile following this advice would look like
COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
COPY install_pacakges.sh /root/
RUN sh -x /root/install_packages.sh

and so on.  The advice of binding several commands with && has only a limited scope. It is much easier to write with scripts, where you can use functions, etc. to avoid redundancy or for documentation purposes.
People interested by pre-processors and willing to avoid the small overhead caused by the COPY steps and are actually generating on-the-fly a Dockerfile where the
COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh

sequences are replaced by
RUN base64 --decode … | sh -x

where the … is the base64-encoded version of apt_setup.sh.
My third advice is for people who wants to limit the size and the number of layers at the possible cost of longer builds.

Advice #3 Use the with-idiom to avoid files present in intermediary layers but not in the resulting filesystem.

A file added by some docker instruction and removed by some later instruction is not present in the resulting filesystem but it is mentioned two times in the docker layers constituting the docker image in construction. Once, with name and full content in the layer resulting from the instruction adding it, and once as a deletion notice in the layer resulting from the instruction removing it.
For instance, assume we temporarily need a C compiler and some image and consider the
# !!! THIS DISPLAYS SOME PROBLEM --- DO NOT USE !!!
RUN apt-get install -y gcc
RUN gcc --version
RUN apt-get --purge autoremove -y gcc

(A more realistic example would build some software with the compiler instead of merely asserting its presence with the --version flag.)
The Dockerfile snippet creates three layers, the first one contains the full gcc suite so that even if it is not present in the final filesystem the corresponding data is still part of the image in same manner and need to be downloaded, uploaded and unpacked whenever the final image is.
The with-idiom is a common form in functional programming to isolate resource ownership and resource releasing from the logic using it. It is easy to transpose this idiom to shell-scripting, and we can rephrase the previous commands as the following script, to be used with COPY & RUN as in Advice #2.
# with_c_compiler SIMPLE-COMMAND
#  Execute SIMPLE-COMMAND in a sub-shell with gcc being available.

with_c_compiler()
(
    set -e
    trap 'apt-get --purge autoremove -y gcc' EXIT
    apt-get install -y gcc
    "$@"
)

with_c_compiler\
    gcc --version

Complex commands can be turned into function so that they can be fed to the with_c_compiler. It is also possible to chain calls of several with_whatever functions, but maybe not very desirable. (Using more esoteric features of the shell, it is certainly possible to make the with_c_compiler accept complex commands, but it is in all aspects preferable to wrap these complex commands into functions.)
If we want to ignore Advice #2, the resulting Dockerfile snippet would be
RUN apt-get install -y gcc\
 && gcc --version\
 && apt-get --purge autoremove -y gcc

which is not so easy to read and maintain because of the obfuscation. See how the shell-script variant outs emphasis on the important part gcc --version while the chained-&& variant buries that part in the middle of noise.

> **Comments:**
> **username_1**: Could you include the outcome of the box size after building using a script and using the multiple commands in one RUN statement?  
> **username_4**: It seems like a bad idea to me to mix the configuration of the image base (i.e. the OS stuff) and even libs with the set up of the source that you wrote.  You say "Make sure that the build steps where your source code is involved comes as late as possible".  Is there any issue in making that part a completely independent artifact?  
> **username_3**: @username_1 What do you mean with “box” size? I have no idea which box you are referring to.  
> **username_1**: I meant docker image size  
> **username_3**: @username_4 It vastly depends on your deployment scenario. If we assume a compiled program, the “right thing to do” would be to package it and install that package dependencies and the package itself as two distinct near-to-be-final steps. This to maximise the usefulness of the docker cache and to avoid downloading over and over layers with the same files. I find it easier to share build recipes to build docker images than building long dependency chains of images, because the latter makes rebuilding harder.  
> **username_3**: @username_1 I never measured this. I am more concerned with the inflation of layers, even if they are as small as possible they incur some tangible overhead when pulling images.  
> **username_4**: @MichaelGrünewald Perhaps I misunderstand.  If I build image D->C->B->A where D is my image I will deploy, rebuilding D means I don't need to download those dependencies again unless I change something about them.  If I have D->A and put all the stuff from C and B in D, then I would think you need to recreate all those on every D rebuild.  You seem to be suggesting the opposite so I think I'm confused in one way or another.  
> **username_3**: @username_4 the `docker build` command maintains a cache of layers or intermediary images yielded by directives found in *Dockerfiles*. This means that even if you do not give a name to `A`, `B` and `C` they will still exist as intermediary cache artefacts.  
> **username_4**: @MichaelGrünewald That's good to know.  Are you saying it doesn't really matter?  I took another meaning from your prior comment.  
> **username_3**: @username_4 I only use a customised linux distribution as base image (with extra repos) and build every product image on top of it. So I just have images `D*` and `A`. It is possible to design the *Dockerfile* for `D*` so that it goes through the same build steps as building in turn `B`, `C` and `D`. Now taking full advantage of the docker cache requires careful preparation of the docker context, which is not necessarily trivial but aside from this, regarding building it does not matter. For testing or product owning it does, so you will need thought or experimentation to find your trade-offs.  
> **username_5**: For absolute completeness I had liked a paragraph about why removing files in a second RUN command doesn't reduce the size (as described in the proposed duplicate).  
> **username_3**: @username_5 Good point. I added a paragraph about it and a useful shell construct to ease the install-uninstall steps of the procedure.  

---

### Answer by username_2 on 2017-08-14. Score: 14

Each instruction you create in your Dockerfile results in a new image layer being created. Each layer brings additional data that are not always part of the resulting image. For example, if you add a file in one layer, but remove it in another layer later, the final image’s size will include the added file size in a form of a special "whiteout" file although you removed it.
Let's say you have the following Dockerfile:
FROM centos:6

RUN yum -y update 
RUN yum -y install epel-release

The resulting image size will be
bigimage     latest        3c5cbfbb4116        2 minutes ago    407MB

As opposite, with "similar" Dockerfile:
FROM centos:6

RUN yum -y update  && yum -y install epel-release

The resulting image size will be
smallimage     latest        7edeafc01ffe        3 minutes ago    384MB

You will get even smaller size, if you clean the yum cache in a single RUN statement.
So you want to keep balance between readability/easy of maintenance and number of layers/image size.


---

### Answer by username_1 on 2017-08-14. Score: 5

The `RUN` statements represent each one layer. Imagine that one downloads a package, installs it and would like to remove it. If one uses three `RUN` statements then the image size will not shrink as there are separate layers. If one runs all the commands using one `RUN` statement the disk image size could be reduced.


---

