Answers: <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>
  
Answers: CC BY-SA 4.0
Answers: 2022-08-30T02:22:39.190Z
Answers: 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!
Answers: T145
Answers: CC BY-SA 4.0
Answers: 2022-08-31T02:29:40.797Z
Answers: 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?
Answers: T145
Answers: CC BY-SA 4.0
Answers: 2022-08-31T02:32:08.630Z
Answers: `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).
Answers: larsks
Answers: CC BY-SA 4.0
Answers: 2022-12-27T18:44:59.810Z
Answers: I’m not sure which implementation of `/bin/ls` you’re using, but mine has 140 *kilo*bytes.
Answers: lxg
Answers: CC BY-SA 4.0
Answers: 2022-08-29T23:19:09.300Z
Answers: 2022-08-29T23:19:09.300Z
Answers: larsks
Body: <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>
  
Comments: CC BY-SA 4.0
Comments: 2022-08-29T19:26:09.410Z
Comments: 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.
Comments: larsks
Comments: CC BY-SA 4.0
Comments: 2022-08-29T21:16:49.240Z
Comments: @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
Comments: T145
Tags: <docker><containers><linux>
Title: Why does stripping executables in Docker add ridiculous layer memory overhead?
UserDisplayName: T145
ThreadText: # Why does stripping executables in Docker add ridiculous layer memory overhead?
  
  (Asked by: username_0 on 2022-08-29)
  
  On [this image](https://github.com/username_0/black-mirror/blob/master/Dockerfile#L55), I ran the following command to reduce executable sizes:
  
  ```bash
  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
  
  ```
  
  1. 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
  
     ```
  2. 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"
       ]
     }
  
     ```
  3. 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](https://github.com/goldmann/docker-squash), [2](https://github.com/jwilder/docker-squash), [3](https://github.com/qwertycody/Bash_Docker_Squash)) that accomplish something similar. `docker build` [has a `--squash` option](https://docs.docker.com/engine/reference/commandline/build/) 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.  
  
  ---
  
  