<p>Each directive in your Dockerfile <em>adds</em> another layer to the image.
So anything you do -- removing files, stripping binaries, etc -- is
only going to <em>increase</em> the size of the image.</p>
<p>It looks like you're trying to overcome this issue by using a multi-stage build, but that's not doing you any good: those two <code>COPY</code> directives are introducing effectively the same changes introduced by the <code>find</code> command in the previous stage.</p>
<p>The way to solve this is by discarding the old layers, generally by creating a new image that reflects the state of the top layer only. This is called &quot;squashing&quot; the image, and there are various ways of doing this. Here's one mechanism that works. For this example, I'm using this Dockerfile (based on your linked example) to build <code>squashtest:base</code>:</p>
<pre><code>FROM docker.io/parrotsec/core:base-lts-amd64

RUN export DEBIAN_FRONTEND=noninteractive &amp;&amp; \
    apt-get -q -y update --no-allow-insecure-repositories \
    &amp;&amp; apt-get -y upgrade --with-new-pkgs \
    &amp;&amp; apt-get -y install --no-install-recommends \
    aria2=1.35.0-3 \
    apparmor=2.13.6-10 \
    apparmor-utils=2.13.6-10 \
    auditd=1:3.0-2 \
    curl \
    debsums=3.0.2 \
    gawk=1:5.1.0-1 \
    git  \
    iprange=1.0.4+ds-2 \
    jq=1.6-2.1 \
    libdata-validate-domain-perl=0.10-1.1 \
    libdata-validate-ip-perl=0.30-1 \
    libnet-idn-encode-perl=2.500-1+b2 \
    libnet-libidn-perl=0.12.ds-3+b3 \
    libregexp-common-perl=2017060201-1 \
    libtext-trim-perl=1.04-1 \
    libtry-tiny-perl=0.30-1 \
    localepurge=0.7.3.10 \
    locales \
    miller=5.10.0-1 \
    moreutils=0.65-1 \
    p7zip-full=16.02+dfsg-8 \
    pandoc=2.9.2.1-1+b1 \
    preload=0.6.4-5+b1 \
    python3-pip=20.3.4-4+deb11u1 \
    rkhunter=1.4.6-9 \
    symlinks=1.4-4 \
    &amp;&amp; apt-get install -y --no-install-recommends --reinstall ca-certificates=\* \
    &amp;&amp; apt-get -y autoremove \
    &amp;&amp; apt-get -y clean \
    &amp;&amp; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
    &amp;&amp; rm -f /var/cache/ldconfig/aux-cache \
    &amp;&amp; find -P -O3 /var/log -depth -type f -print0 | xargs -0 truncate -s 0 \
    &amp;&amp; localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 \
    &amp;&amp; localepurge \
    &amp;&amp; symlinks -rd / \
    &amp;&amp; apt-get -y purge --auto-remove localepurge symlinks \
    &amp;&amp; find -P -O3 /etc/ /usr/ -type d -empty -delete
</code></pre>
<ol>
<li><p>Build the base image.</p>
<pre><code>docker build -t squashtest:base -f Dockerfile.base .
</code></pre>
<p>This produces the following:</p>
<pre><code>$ docker image ls squashtest:base
REPOSITORY   TAG       IMAGE ID       CREATED             SIZE
squashtest   base      58dff2c40a28   About an hour ago   786MB
</code></pre>
</li>
<li><p>Build a new image <code>squashtest:stripped</code> with stripped binaries
using this Dockerfile:</p>
<pre><code>FROM squashtest:base

RUN find -P -O3 /usr/bin/ /usr/local/bin \
  -type f -not -name strip -and -not -name dbus-daemon \
  -execdir strip -v --strip-unneeded '{}' \; || :
</code></pre>
<p>Which produces:</p>
<pre><code>$ docker image ls squashtest:stripped
REPOSITORY   TAG        IMAGE ID       CREATED             SIZE
squashtest   stripped   42aa25ebc0c7   About an hour ago   997MB
</code></pre>
<p>At this point, the image consists of the following layers:</p>
<pre><code>$ docker image inspect squashtest:stripped | jq '.[0].RootFS'
{
  &quot;Type&quot;: &quot;layers&quot;,
  &quot;Layers&quot;: [
    &quot;sha256:7e203d602b1c20e9cf0b06b3dd3383eb36bc2b25f6e8064d9c81326dfdc67143&quot;,
    &quot;sha256:1fc5866a0b6b7a23a246acfd46b4c513b4a188d2db2d8a26191989a4a18c74d3&quot;,
    &quot;sha256:cc3a9d1a7f9222eee31b688d887c79745e20389ecfe0fe208349c73cfd172b4a&quot;
  ]
}
</code></pre>
</li>
<li><p>We can collapse these into a single layer like this:</p>
<pre><code>docker run --rm squashtest:stripped \
  tar -C / -cf- --exclude=./dev --exclude=./sys \
  --exclude=./proc  . |
  docker import - squashtest:imported
</code></pre>
<p>This produces:</p>
<pre><code>$ docker image ls squashtest:imported
REPOSITORY   TAG        IMAGE ID       CREATED          SIZE
squashtest   imported   6f036f16d477   46 seconds ago   626MB
</code></pre>
<p>We've saved 160MB off the base image.</p>
</li>
</ol>
<p>There are other ways to squash a Docker image; there are a number of
tools on GitHub (<a href="https://github.com/goldmann/docker-squash" rel="nofollow noreferrer">1</a>, <a href="https://github.com/jwilder/docker-squash" rel="nofollow noreferrer">2</a>, <a href="https://github.com/qwertycody/Bash_Docker_Squash" rel="nofollow noreferrer">3</a>) that accomplish something similar. <code>docker build</code> <a href="https://docs.docker.com/engine/reference/commandline/build/" rel="nofollow noreferrer">has a <code>--squash</code> option</a> if you enable experimental features, but that doesn't appear to accomplish much when I try it.</p>
<p>I would argue that for the 160MB we've managed to save here the effort
isn't worth it. Unless you're running Docker in an extremely
constrained environment, that's going to be nothing but a drop in the
bucket (for reference, that's about the size of <code>/bin/ls</code>).</p>
<p>In fact, the <code>strip</code> operation in your Dockerfile is mostly pointless: distributions generally strip binaries by default; you can verify this by running <code>file</code> on all the binaries in <code>/usr/bin</code> and <code>/bin</code> on <code>docker.io/parrotsec/core:base-lts-amd64</code>:</p>
<pre><code>$ docker run -it --rm docker.io/parrotsec/core:base-lts-amd64 bash
# apt -y install file
# file /bin/* /usr/bin/* | grep ELF  | grep -v stripped
</code></pre>
<p>That last command returns zero results: all the binaries have been stripped.</p>

CC BY-SA 4.0
2022-08-30T02:22:39.190Z
I'm guessing all that needs to be done here then is to perform all operations in the base image and squash it. My main goal for this environment is to deliver a high-quality image. So if other distributors strip their executables, why shouldn't I? I actually was researching `docker-squash` but the main repo has an annoying bug that blocks its runtime. Thanks for the help!
T145
CC BY-SA 4.0
2022-08-31T02:29:40.797Z
Also, for Step 3, wouldn't `docker run --name tmp squashtest:stripped && docker image save tmp | docker import - squashtest:imported` be the command you want?
T145
CC BY-SA 4.0
2022-08-31T02:32:08.630Z
`docker image save` operates on images, not on containers. Your command would result in `No such image: tmp`. Additionally, `docker image save` does not reduce the number of layers in an image; it simply exports all the layers as a tar archive (which will be reconstituted when your read them back in).
larsks
CC BY-SA 4.0
2022-12-27T18:44:59.810Z
I’m not sure which implementation of `/bin/ls` you’re using, but mine has 140 *kilo*bytes.
lxg
CC BY-SA 4.0
2022-08-29T23:19:09.300Z
2022-08-29T23:19:09.300Z
larsks
<p>On <a href="https://github.com/T145/black-mirror/blob/master/Dockerfile#L55" rel="nofollow noreferrer">this image</a>, I ran the following command to reduce executable sizes:</p>
<pre class="lang-bash prettyprint-override"><code>find -P -O3 /usr/bin/ /usr/local/bin -type f -not -name strip -and -not -name dbus-daemon -execdir strip -v --strip-unneeded '{}' \;
</code></pre>
<p>And its size jumped up from ~779.53 to ~986.55MB!</p>
<p>As an attempt to bypass this caveat I created an intermediate layer to copy the changes over from, like so:</p>
<pre><code>FROM base as stripped

RUN find -P -O3 /usr/bin/ /usr/local/bin -type f -not -name strip -and -not -name dbus-daemon -execdir strip -v --strip-unneeded '{}' \;

FROM base

COPY --from=stripped /usr/bin/ /usr/bin/
COPY --from=stripped /usr/local/bin/ /usr/local/bin/
</code></pre>
<p>However the resulting image size did not change. Also note that the <code>base</code> image has other programs installed on it, so simply using another Debian distribution as the intermediate layer wouldn't cover stripping each program on the base image.</p>
<p>Why is this large size difference happening? Is there a way to strip executables in Docker at all without having this happen?</p>

CC BY-SA 4.0
2022-08-29T19:26:09.410Z
Can you share which base image you're actually using? The base `debian` image is only a little over 100MB, so stripping the binaries is pretty much pointless. I'm not suggesting that your choice of base image is related to the problem (it's not), but I need some base image to use for demonstration purposes that's not tiny.
larsks
CC BY-SA 4.0
2022-08-29T21:16:49.240Z
@larsks Of course; the posted has been updated to reference it. I'll probably ask for a Dockerfile review at some point so please critique only what would be relevant for this topic xd
T145
<docker><containers><linux>
Why does stripping executables in Docker add ridiculous layer memory overhead?
T145
# Why does stripping executables in Docker add ridiculous layer memory overhead?

(Asked by: username_0 on 2022-08-29)

On this image, I ran the following command to reduce executable sizes:
find -P -O3 /usr/bin/ /usr/local/bin -type f -not -name strip -and -not -name dbus-daemon -execdir strip -v --strip-unneeded '{}' \;

And its size jumped up from ~779.53 to ~986.55MB!
As an attempt to bypass this caveat I created an intermediate layer to copy the changes over from, like so:
FROM base as stripped

RUN find -P -O3 /usr/bin/ /usr/local/bin -type f -not -name strip -and -not -name dbus-daemon -execdir strip -v --strip-unneeded '{}' \;

FROM base

COPY --from=stripped /usr/bin/ /usr/bin/
COPY --from=stripped /usr/local/bin/ /usr/local/bin/

However the resulting image size did not change. Also note that the base image has other programs installed on it, so simply using another Debian distribution as the intermediate layer wouldn't cover stripping each program on the base image.
Why is this large size difference happening? Is there a way to strip executables in Docker at all without having this happen?

**Tags:** docker, containers, linux

> **Comments:**
> **username_1**: Can you share which base image you're actually using? The base `debian` image is only a little over 100MB, so stripping the binaries is pretty much pointless. I'm not suggesting that your choice of base image is related to the problem (it's not), but I need some base image to use for demonstration purposes that's not tiny.  
> **username_0**: @username_1 Of course; the posted has been updated to reference it. I'll probably ask for a Dockerfile review at some point so please critique only what would be relevant for this topic xd  

---

## 1 answers

### Answer by username_1 on 2022-08-29. Score: 3 | Accepted answer

Each directive in your Dockerfile adds another layer to the image.
So anything you do -- removing files, stripping binaries, etc -- is
only going to increase the size of the image.
It looks like you're trying to overcome this issue by using a multi-stage build, but that's not doing you any good: those two COPY directives are introducing effectively the same changes introduced by the find command in the previous stage.
The way to solve this is by discarding the old layers, generally by creating a new image that reflects the state of the top layer only. This is called "squashing" the image, and there are various ways of doing this. Here's one mechanism that works. For this example, I'm using this Dockerfile (based on your linked example) to build squashtest:base:
FROM docker.io/parrotsec/core:base-lts-amd64

RUN export DEBIAN_FRONTEND=noninteractive && \
    apt-get -q -y update --no-allow-insecure-repositories \
    && apt-get -y upgrade --with-new-pkgs \
    && apt-get -y install --no-install-recommends \
    aria2=1.35.0-3 \
    apparmor=2.13.6-10 \
    apparmor-utils=2.13.6-10 \
    auditd=1:3.0-2 \
    curl \
    debsums=3.0.2 \
    gawk=1:5.1.0-1 \
    git  \
    iprange=1.0.4+ds-2 \
    jq=1.6-2.1 \
    libdata-validate-domain-perl=0.10-1.1 \
    libdata-validate-ip-perl=0.30-1 \
    libnet-idn-encode-perl=2.500-1+b2 \
    libnet-libidn-perl=0.12.ds-3+b3 \
    libregexp-common-perl=2017060201-1 \
    libtext-trim-perl=1.04-1 \
    libtry-tiny-perl=0.30-1 \
    localepurge=0.7.3.10 \
    locales \
    miller=5.10.0-1 \
    moreutils=0.65-1 \
    p7zip-full=16.02+dfsg-8 \
    pandoc=2.9.2.1-1+b1 \
    preload=0.6.4-5+b1 \
    python3-pip=20.3.4-4+deb11u1 \
    rkhunter=1.4.6-9 \
    symlinks=1.4-4 \
    && apt-get install -y --no-install-recommends --reinstall ca-certificates=\* \
    && apt-get -y autoremove \
    && apt-get -y clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
    && rm -f /var/cache/ldconfig/aux-cache \
    && find -P -O3 /var/log -depth -type f -print0 | xargs -0 truncate -s 0 \
    && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 \
    && localepurge \
    && symlinks -rd / \
    && apt-get -y purge --auto-remove localepurge symlinks \
    && find -P -O3 /etc/ /usr/ -type d -empty -delete


Build the base image.
docker build -t squashtest:base -f Dockerfile.base .

This produces the following:
$ docker image ls squashtest:base
REPOSITORY   TAG       IMAGE ID       CREATED             SIZE
squashtest   base      58dff2c40a28   About an hour ago   786MB


Build a new image squashtest:stripped with stripped binaries
using this Dockerfile:
FROM squashtest:base

RUN find -P -O3 /usr/bin/ /usr/local/bin \
  -type f -not -name strip -and -not -name dbus-daemon \
  -execdir strip -v --strip-unneeded '{}' \; || :

Which produces:
$ docker image ls squashtest:stripped
REPOSITORY   TAG        IMAGE ID       CREATED             SIZE
squashtest   stripped   42aa25ebc0c7   About an hour ago   997MB

At this point, the image consists of the following layers:
$ docker image inspect squashtest:stripped | jq '.[0].RootFS'
{
  "Type": "layers",
  "Layers": [
    "sha256:7e203d602b1c20e9cf0b06b3dd3383eb36bc2b25f6e8064d9c81326dfdc67143",
    "sha256:1fc5866a0b6b7a23a246acfd46b4c513b4a188d2db2d8a26191989a4a18c74d3",
    "sha256:cc3a9d1a7f9222eee31b688d887c79745e20389ecfe0fe208349c73cfd172b4a"
  ]
}


We can collapse these into a single layer like this:
docker run --rm squashtest:stripped \
  tar -C / -cf- --exclude=./dev --exclude=./sys \
  --exclude=./proc  . |
  docker import - squashtest:imported

This produces:
$ docker image ls squashtest:imported
REPOSITORY   TAG        IMAGE ID       CREATED          SIZE
squashtest   imported   6f036f16d477   46 seconds ago   626MB

We've saved 160MB off the base image.


There are other ways to squash a Docker image; there are a number of
tools on GitHub (1, 2, 3) that accomplish something similar. docker build has a --squash option if you enable experimental features, but that doesn't appear to accomplish much when I try it.
I would argue that for the 160MB we've managed to save here the effort
isn't worth it. Unless you're running Docker in an extremely
constrained environment, that's going to be nothing but a drop in the
bucket (for reference, that's about the size of /bin/ls).
In fact, the strip operation in your Dockerfile is mostly pointless: distributions generally strip binaries by default; you can verify this by running file on all the binaries in /usr/bin and /bin on docker.io/parrotsec/core:base-lts-amd64:
$ docker run -it --rm docker.io/parrotsec/core:base-lts-amd64 bash
# apt -y install file
# file /bin/* /usr/bin/* | grep ELF  | grep -v stripped

That last command returns zero results: all the binaries have been stripped.

> **Comments:**
> **username_0**: I'm guessing all that needs to be done here then is to perform all operations in the base image and squash it. My main goal for this environment is to deliver a high-quality image. So if other distributors strip their executables, why shouldn't I? I actually was researching `docker-squash` but the main repo has an annoying bug that blocks its runtime. Thanks for the help!  
> **username_0**: Also, for Step 3, wouldn't `docker run --name tmp squashtest:stripped && docker image save tmp | docker import - squashtest:imported` be the command you want?  
> **username_1**: `docker image save` operates on images, not on containers. Your command would result in `No such image: tmp`. Additionally, `docker image save` does not reduce the number of layers in an image; it simply exports all the layers as a tar archive (which will be reconstituted when your read them back in).  
> **username_2**: I’m not sure which implementation of `/bin/ls` you’re using, but mine has 140 *kilo*bytes.  

---

