<p>Probably not exactly an answer, but worth giving the alternatives.</p>

<p>Chef's <a href="https://www.habitat.sh" rel="nofollow noreferrer"><strong>habitat</strong></a> has been created with this in mind, creating a package with all needed dependencies without the extraneous distro/base image load you don't wish.</p>

<p>Extracts on what matters here, the container size from <a href="https://blog.chef.io/2016/11/28/why-habitat-plans-and-packages-part-2/" rel="nofollow noreferrer"><strong>this blog post</strong></a> with a simple nodejs app:</p>

<blockquote>
<pre><code>michael@ricardo-2:plans_pkg_part_2$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
mfdii/node-example   latest              36c6568c606b        40 minutes ago      655.9 MB
node                 latest              04c0ca2a8dad        16 hours ago        654.6 MB
mfdii/mytutorialapp  latest              534afd80d74d        2 minutes ago       182.1 MB
</code></pre>
</blockquote>

<p><code>mdfii/node-example</code> is a docker image from a classic dockerfile while <code>mfdii/mytutorialapp</code> is the docker image produced with habitat.</p>

<p>If size is your main concern and you're up to take the learning curve of Habitat plans, this could be a solution for you.</p>

CC BY-SA 3.0
2017-03-07T12:55:25.557Z
2017-04-05T10:01:18.203Z
2017-04-05T10:01:18.203Z
Tensibai
<p>A <code>Dockerfile</code> creates a new layer for each of the commands in the file. Since layers are well, <em>layered</em> on top of each other - you cannot remove files that a previous layer added. This is why when you install packages, or download files, or create builds each in a separate command - these are still there in the image, even if in a future layer you removed them.</p>

<p>So if you just change this:</p>

<pre><code>RUN apt-get update -y
RUN apt-get install -y wget a-package
# ...
RUN apt-get purge -y wget
RUN rm -r a-build-dir
RUN apt-get purge -y a-package
</code></pre>

<p>To this:</p>

<pre><code>RUN apt-get update -y \
    &amp;&amp; apt-get install -y wget a-package \
    &amp;&amp; mkdir a-build-dir \
    &amp;&amp; wget http://some-site/very-big-source-code.tar.gz \
    &amp;&amp; tar xzvf very-big-source-code.tar.gz \
    &amp;&amp; do-some-compilation \
    &amp;&amp; apt-get purge -y wget \
    &amp;&amp; cd .. \
    &amp;&amp; rm -rf a-build-dir \
    &amp;&amp; apt-get purge -y a-package
</code></pre>

<p>You will get a much smaller image.</p>

<hr>

<p>Another option, is to <strong>squash</strong> the image after you built it.
<a href="https://stackoverflow.com/questions/41764336/how-does-the-new-docker-squash-work">Q: How does the new <code>docker --squash</code> work?</a></p>

<hr>

<p>Yet another option, is to choose a slim base image. For example, images that use <a href="https://alpinelinux.org/" rel="noreferrer">Alpine Linux</a> as their base instead of Debian, take just 10-15mb instead of 180-250mb. And this is before adding your own application and data. Many <em>official</em> base images on <a href="https://hub.docker.com/" rel="noreferrer">Docker Hub</a> have an alpine version.</p>

CC BY-SA 3.0
2017-03-07T22:48:01.773Z
`2.37` vs. `1.47 GB`
030
CC BY-SA 4.0
2020-09-09T07:59:03.657Z
Add --no-install-recommends to all apt-get install commands.
Jiri Klouda
CC BY-SA 3.0
2017-03-07T13:02:28.923Z
2017-03-07T13:26:41.493Z
2017-05-23T12:39:34.013Z
Evgeny Zislis
<p>If you want to have reusable development layers but reduce your disk usage for delivery, you can produce a merged "delivery layer" like this:</p>

<ol>
<li>Make sure you have a container that uses your image (if you don't have one, maybe use something like <code>docker run IMAGE echo</code>, if the echo command is available)</li>
<li>Find the container id (maybe using <code>docker container ls -l</code>)</li>
<li>Pipe <code>docker export</code> to <code>docker import</code> to create the merged layer (something like <code>docker export 20f192c6530a | docker import - project:merged</code>)</li>
</ol>

<p>This will keep your development layers around but give you a smaller, merged image that you can deliver.</p>

CC BY-SA 4.0
2019-06-03T07:35:33.920Z
2019-06-03T07:35:33.920Z
Bill Burdick
<p>Multi stage builds.  Use the image that has all your build components to build your application, and a lighter runtime image. Copy just your build artifact to the runtime image. No need to delete anything. </p>

<p><a href="https://docs.docker.com/develop/develop-images/multistage-build/" rel="nofollow noreferrer">https://docs.docker.com/develop/develop-images/multistage-build/</a></p>

CC BY-SA 4.0
2019-06-03T13:46:35.863Z
2019-06-03T13:46:35.863Z
frankd
<p>One could also use <a href="https://github.com/wagoodman/dive" rel="nofollow noreferrer">dive</a></p>

<pre><code>docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive:latest &lt;dive arguments...&gt;
</code></pre>

<p>to get a report about what waste could be removed from a docker image in order to reduce the size.</p>

CC BY-SA 4.0
2019-05-06T13:19:00.937Z
2019-05-06T13:19:00.937Z
030
<p>Use <code>docker ps</code> to check the current running containers. For example:</p>
<pre><code>FROM ubuntu16
    
MAINTAINER sreeni (email/domain)
    
RUN apt-get update
    
RUN apt-get install -y nginx
    
ENTRYPOINT [“/usr/sbin/nginx”,”-g”,”daemon off;”]
    
EXPOSE 80 (port)
</code></pre>
<p>Use below docker command to run the container:</p>
<pre><code>docker run -d -p 80:80 --name web server ubuntu16
</code></pre>
<p>After that, check localhost or ip address:80 (open browser and check)</p>

CC BY-SA 4.0
2019-10-11T23:50:02.293Z
2020-09-09T07:23:11.723Z
2020-09-09T07:23:11.723Z
sreeniwl
<p>There's a variety of techniques involved, with no single solution. You will likely want to do several of the following:</p>

<hr>

<p>First, optimize your image layers for reuse. Put frequently changing steps later in the Dockerfile to increase the chances that early layers are cached from previous builds. A reused layer will show up as more disk space in a <code>docker image ls</code>, but if you examine the underlying filesystem, only one copy of each layer is ever stored on disk. That means 3 images of 2 GB each, but which only have 50 MB different in the last few layers the build, will only take up 2.1 GB of disk space, even though the listing makes it appear that they are using 6 GB since you are double counting each of the reused layers.</p>

<p>Layer reuse is why you see images with infrequently changing build dependencies install those first before copying in code. See any python example that has a pattern like:</p>

<pre><code>FROM python
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
# note how the code is copied only after the pip install
# since code changes but requirements.txt doesn't
COPY . .
CMD ["gunicorn", "app:app"]
</code></pre>

<hr>

<p>Pick a minimal base image. This is why you see people go from <code>ubuntu</code> to <code>debian:slim</code> (the slim variants are smaller, shipping with fewer tools), or even <code>alpine</code>. This reduces the size of your starting point, and is very helpful if you are constantly pulling new versions of the base image. However, if your base image rarely changes, layer reuse removes much of the advantage of a minimal base image.</p>

<p>The smallest base image you can pick is <code>scratch</code>, which is nothing, no shell or libraries, and is only useful with statically compiled binaries. Otherwise, pick a base image that includes the tools you need without lots of tools you don't need.</p>

<hr>

<p>Next, any step that changes or deletes a file should be combined with previous steps that create that file. Otherwise the layered filesystem, which uses copy-on-write even on things like a file permission change, will have the original file in a previous layer and the image size will not shrink when you remove files. This is why your <code>rm</code> commands have no effect on resulting disk space. Instead, you can chain the commands, like:</p>

<pre><code>RUN apt-get update \
 &amp;&amp; apt-get install -y \
      a-package \
      wget \
 &amp;&amp; ... \
 &amp;&amp; apt-get purge -y wget \
 &amp;&amp; rm -r a-build-dir \
 &amp;&amp; apt-get purge -y a-package
</code></pre>

<p>Note that overuse of command chaining can slow down your builds since you need to reinstall the same toolset any time a prerequisite changes (e.g. the code being pulled with wget). See multi-stage below for a better alternative.</p>

<hr>

<p>Any file you create that you do not need in your resulting image should be deleted, in the step that creates it. This includes package caches, logs, man pages, etc. To discover what files are being created in each layer, you can use a tool like wagoodman/dive (which I have not personally vetted and would express caution since it runs with full root access on your host), or you can build your docker images without pruning the intermediate containers and then view the diff with:</p>

<pre><code># first create and leave containers from any RUN step using options on build
docker image build --rm=false --no-cache -t image_name . 
# review which layers use an unexpectedly large amount of space
docker image history image_name
# list all containers, particularly the exited ones from above
docker container ps -a 
# examine any of those containers
docker container diff ${container_id} 
# ... repeat the diff for other build steps
# then cleanup exited containers
docker container prune
</code></pre>

<p>With each of those intermediate containers, the diff will show what files are added, changed, or deleted in that step (these are indicated with an <code>A</code>, <code>C</code>, or <code>D</code> before each filename). What diff is showing is the container specific read/write filesystem, which is any file changed by the container from the image state using copy-on-write.</p>

<hr>

<p>The best way to reduce image size is eliminating any unneeded components, like compilers, from your shipped image. For that, multi-stage builds let you compile in one stage, and then copy only the resulting artifacts from the build stage to a runtime image that has only the minimum needed to run the application. This avoids the need to optimize any of the build steps since they are not shipped with the resulting image.</p>

<pre><code>FROM debian:9 as build
# still chain update with install to prevent stale cache issues
RUN apt-get update \
 &amp;&amp; apt-get install -y \
      a-package \
      wget \
RUN ... # perform any download/compile steps

FROM debian:9-slim as release
COPY --from=build /usr/local/bin/app /usr/local/bin/app
CMD [ "/usr/local/bin/app" ]
</code></pre>

<p>Multi-stage is ideal with statically compiled binaries which you can run with scratch as your base image, or transitioning from a compile environment like JDK to a runtime like JRE. This is the easiest way to dramatically reduce your image size while still having fast builds. You may still perform chaining of steps in your release stage if you have steps that change or delete files created in previous steps, but for the most part, the <code>COPY</code> from another stage isolates the release stage from any layer bloat experienced in the earlier build stages.</p>

<hr>

<p>Note, I do not recommend squashing images since this reduces the size of one image at the expense of eliminating layer reuse. That means future builds of the same image will require more disk and network traffic to send updates. To go back to the first example, squashing may reduce your image from 2 GB to 1 GB, but not 3 images may take up 3 GB instead of the 2.1 GB.</p>

CC BY-SA 4.0
2020-06-03T12:20:13.560Z
My situation is that - due to security reasons I am downloading the packages in the intermediate layer. Then, ADD'ing them to the final image. Followed by using RUN command to install the packages and delete the packages. My goal is to delete the downloaded packages once the RUN command installs them. Is there any way to a) combine the ADD and RUN (delete) into one layer; b) remove the ADD layer after RUN?
variable
CC BY-SA 4.0
2020-06-03T13:30:00.083Z
@variable that's a scenario for the multi-stage build described above.
BMitch
CC BY-SA 4.0
2023-10-01T22:07:29.150Z
The "people go from ubuntu to debian" is now outdated. Ubuntu became ~35mb like a year after this answer was given.
Nakilon
CC BY-SA 4.0
2019-05-07T12:38:54.647Z
2019-05-08T12:02:05.990Z
2019-05-08T12:02:05.990Z
BMitch
<p><strong>Aim:</strong> to create the smallest working docker images every time</p>

<p><strong>Current</strong></p>

<pre><code>REPOSITORY          TAG       IMAGE ID            CREATED             SIZE
a-docker-image      latest    x                   42 minutes ago       1.92 GB
</code></pre>

<p><strong>Attempt</strong></p>

<p>Adding a cleanup step at the end of the Dockerfile:</p>

<pre><code>#clean
RUN apt-get purge -y wget
RUN rm -r a-build-dir
RUN apt-get purge -y a-package
</code></pre>

<p>reduced the image size a little bit:</p>

<pre><code>REPOSITORY          TAG       IMAGE ID            CREATED             SIZE
a-docker-image      latest    y                   2 minutes ago       1.86 GB
</code></pre>

<p><strong>Discussion</strong></p>

<p>I have built various docker images. Every time I try to decrease the size of the created image, but I always feel it is too large. I am looking for a script that has already been created by someone on github that removes all superfluous packages from the image so the size of the created image will be as small as possible.</p>

<p>As I said I always try to reduce the size of the image, but I want to apply this consistent so that every image that I create from now on will be as small as possible.</p>

<p><strong>Question</strong></p>

<p>How to create the smallest working docker image every time?</p>

<docker>
How to create the smallest working docker image every time?
030
# How to create the smallest working docker image every time?

(Asked by: username_0 on 2017-03-07)

Aim: to create the smallest working docker images every time
Current
REPOSITORY          TAG       IMAGE ID            CREATED             SIZE
a-docker-image      latest    x                   42 minutes ago       1.92 GB

Attempt
Adding a cleanup step at the end of the Dockerfile:
#clean
RUN apt-get purge -y wget
RUN rm -r a-build-dir
RUN apt-get purge -y a-package

reduced the image size a little bit:
REPOSITORY          TAG       IMAGE ID            CREATED             SIZE
a-docker-image      latest    y                   2 minutes ago       1.86 GB

Discussion
I have built various docker images. Every time I try to decrease the size of the created image, but I always feel it is too large. I am looking for a script that has already been created by someone on github that removes all superfluous packages from the image so the size of the created image will be as small as possible.
As I said I always try to reduce the size of the image, but I want to apply this consistent so that every image that I create from now on will be as small as possible.
Question
How to create the smallest working docker image every time?

**Tags:** docker


---

## 7 answers

### Answer by username_7 on 2019-05-07. Score: 8 | Accepted answer

There's a variety of techniques involved, with no single solution. You will likely want to do several of the following:

First, optimize your image layers for reuse. Put frequently changing steps later in the Dockerfile to increase the chances that early layers are cached from previous builds. A reused layer will show up as more disk space in a docker image ls, but if you examine the underlying filesystem, only one copy of each layer is ever stored on disk. That means 3 images of 2 GB each, but which only have 50 MB different in the last few layers the build, will only take up 2.1 GB of disk space, even though the listing makes it appear that they are using 6 GB since you are double counting each of the reused layers.
Layer reuse is why you see images with infrequently changing build dependencies install those first before copying in code. See any python example that has a pattern like:
FROM python
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
# note how the code is copied only after the pip install
# since code changes but requirements.txt doesn't
COPY . .
CMD ["gunicorn", "app:app"]


Pick a minimal base image. This is why you see people go from ubuntu to debian:slim (the slim variants are smaller, shipping with fewer tools), or even alpine. This reduces the size of your starting point, and is very helpful if you are constantly pulling new versions of the base image. However, if your base image rarely changes, layer reuse removes much of the advantage of a minimal base image.
The smallest base image you can pick is scratch, which is nothing, no shell or libraries, and is only useful with statically compiled binaries. Otherwise, pick a base image that includes the tools you need without lots of tools you don't need.

Next, any step that changes or deletes a file should be combined with previous steps that create that file. Otherwise the layered filesystem, which uses copy-on-write even on things like a file permission change, will have the original file in a previous layer and the image size will not shrink when you remove files. This is why your rm commands have no effect on resulting disk space. Instead, you can chain the commands, like:
RUN apt-get update \
 && apt-get install -y \
      a-package \
      wget \
 && ... \
 && apt-get purge -y wget \
 && rm -r a-build-dir \
 && apt-get purge -y a-package

Note that overuse of command chaining can slow down your builds since you need to reinstall the same toolset any time a prerequisite changes (e.g. the code being pulled with wget). See multi-stage below for a better alternative.

Any file you create that you do not need in your resulting image should be deleted, in the step that creates it. This includes package caches, logs, man pages, etc. To discover what files are being created in each layer, you can use a tool like wagoodman/dive (which I have not personally vetted and would express caution since it runs with full root access on your host), or you can build your docker images without pruning the intermediate containers and then view the diff with:
# first create and leave containers from any RUN step using options on build
docker image build --rm=false --no-cache -t image_name . 
# review which layers use an unexpectedly large amount of space
docker image history image_name
# list all containers, particularly the exited ones from above
docker container ps -a 
# examine any of those containers
docker container diff ${container_id} 
# ... repeat the diff for other build steps
# then cleanup exited containers
docker container prune

With each of those intermediate containers, the diff will show what files are added, changed, or deleted in that step (these are indicated with an A, C, or D before each filename). What diff is showing is the container specific read/write filesystem, which is any file changed by the container from the image state using copy-on-write.

The best way to reduce image size is eliminating any unneeded components, like compilers, from your shipped image. For that, multi-stage builds let you compile in one stage, and then copy only the resulting artifacts from the build stage to a runtime image that has only the minimum needed to run the application. This avoids the need to optimize any of the build steps since they are not shipped with the resulting image.
FROM debian:9 as build
# still chain update with install to prevent stale cache issues
RUN apt-get update \
 && apt-get install -y \
      a-package \
      wget \
RUN ... # perform any download/compile steps

FROM debian:9-slim as release
COPY --from=build /usr/local/bin/app /usr/local/bin/app
CMD [ "/usr/local/bin/app" ]

Multi-stage is ideal with statically compiled binaries which you can run with scratch as your base image, or transitioning from a compile environment like JDK to a runtime like JRE. This is the easiest way to dramatically reduce your image size while still having fast builds. You may still perform chaining of steps in your release stage if you have steps that change or delete files created in previous steps, but for the most part, the COPY from another stage isolates the release stage from any layer bloat experienced in the earlier build stages.

Note, I do not recommend squashing images since this reduces the size of one image at the expense of eliminating layer reuse. That means future builds of the same image will require more disk and network traffic to send updates. To go back to the first example, squashing may reduce your image from 2 GB to 1 GB, but not 3 images may take up 3 GB instead of the 2.1 GB.

> **Comments:**
> **username_8**: My situation is that - due to security reasons I am downloading the packages in the intermediate layer. Then, ADD'ing them to the final image. Followed by using RUN command to install the packages and delete the packages. My goal is to delete the downloaded packages once the RUN command installs them. Is there any way to a) combine the ADD and RUN (delete) into one layer; b) remove the ADD layer after RUN?  
> **username_7**: @username_8 that's a scenario for the multi-stage build described above.  
> **username_9**: The "people go from ubuntu to debian" is now outdated. Ubuntu became ~35mb like a year after this answer was given.  

---

### Answer by username_2 on 2017-03-07. Score: 27

A Dockerfile creates a new layer for each of the commands in the file. Since layers are well, layered on top of each other - you cannot remove files that a previous layer added. This is why when you install packages, or download files, or create builds each in a separate command - these are still there in the image, even if in a future layer you removed them.
So if you just change this:
RUN apt-get update -y
RUN apt-get install -y wget a-package
# ...
RUN apt-get purge -y wget
RUN rm -r a-build-dir
RUN apt-get purge -y a-package

To this:
RUN apt-get update -y \
    && apt-get install -y wget a-package \
    && mkdir a-build-dir \
    && wget http://some-site/very-big-source-code.tar.gz \
    && tar xzvf very-big-source-code.tar.gz \
    && do-some-compilation \
    && apt-get purge -y wget \
    && cd .. \
    && rm -rf a-build-dir \
    && apt-get purge -y a-package

You will get a much smaller image.

Another option, is to squash the image after you built it.
Q: How does the new docker --squash work?

Yet another option, is to choose a slim base image. For example, images that use Alpine Linux as their base instead of Debian, take just 10-15mb instead of 180-250mb. And this is before adding your own application and data. Many official base images on Docker Hub have an alpine version.

> **Comments:**
> **username_0**: `2.37` vs. `1.47 GB`  
> **username_3**: Add --no-install-recommends to all apt-get install commands.  

---

### Answer by username_1 on 2017-03-07. Score: 4

Probably not exactly an answer, but worth giving the alternatives.
Chef's habitat has been created with this in mind, creating a package with all needed dependencies without the extraneous distro/base image load you don't wish.
Extracts on what matters here, the container size from this blog post with a simple nodejs app:

michael@ricardo-2:plans_pkg_part_2$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
mfdii/node-example   latest              36c6568c606b        40 minutes ago      655.9 MB
node                 latest              04c0ca2a8dad        16 hours ago        654.6 MB
mfdii/mytutorialapp  latest              534afd80d74d        2 minutes ago       182.1 MB


mdfii/node-example is a docker image from a classic dockerfile while mfdii/mytutorialapp is the docker image produced with habitat.
If size is your main concern and you're up to take the learning curve of Habitat plans, this could be a solution for you.


---

### Answer by username_4 on 2019-06-03. Score: 0

If you want to have reusable development layers but reduce your disk usage for delivery, you can produce a merged "delivery layer" like this:

1. Make sure you have a container that uses your image (if you don't have one, maybe use something like `docker run IMAGE echo`, if the echo command is available)
2. Find the container id (maybe using `docker container ls -l`)
3. Pipe `docker export` to `docker import` to create the merged layer (something like `docker export 20f192c6530a | docker import - project:merged`)

This will keep your development layers around but give you a smaller, merged image that you can deliver.


---

### Answer by username_5 on 2019-06-03. Score: 0

Multi stage builds. Use the image that has all your build components to build your application, and a lighter runtime image. Copy just your build artifact to the runtime image. No need to delete anything.

<https://docs.docker.com/develop/develop-images/multistage-build/>


---

### Answer by username_0 on 2019-05-06. Score: 0

One could also use dive
docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive:latest 

to get a report about what waste could be removed from a docker image in order to reduce the size.


---

### Answer by username_6 on 2019-10-11. Score: -1

Use docker ps to check the current running containers. For example:
FROM ubuntu16
    
MAINTAINER sreeni (email/domain)
    
RUN apt-get update
    
RUN apt-get install -y nginx
    
ENTRYPOINT [“/usr/sbin/nginx”,”-g”,”daemon off;”]
    
EXPOSE 80 (port)

Use below docker command to run the container:
docker run -d -p 80:80 --name web server ubuntu16

After that, check localhost or ip address:80 (open browser and check)


---

