Reduce size of a docker image Reduce size of a docker image nginx nginx

Reduce size of a docker image


Edit 2018-09-26: Lots of edits to tidy up my mistakes and include all the dependencies. Much of my previous poking around is irrelevant, now that I've included all the dependencies and have numbers which add up sensibly.


I built an image from the provided repository in order to see the complete list of packages installed, including dependencies. My build did not complete, but it got far enough to produce an image (5ed25a4a3cf1) which shows where the space is. Part way through the build, at the end of the RUN apk add ..., it said this:

OK: 150 MiB in 102 packages

Looking at the image, we see that the packages are 145MB according to docker history (image layer 8138a6c99655 - you probably need to scroll right to see the size column):

user@host:~/docker-craft-nginx$ sudo docker history 5ed25a4a3cf1IMAGE               CREATED              CREATED BY                                      SIZE                COMMENT5ed25a4a3cf1        About a minute ago   /bin/sh -c #(nop) COPY dir:e9a848580d7409c11…   0B79bba3526427        About a minute ago   /bin/sh -c #(nop) COPY dir:41ddb696977d39ee6…   7.38kBf4d1e79f00b4        About a minute ago   /bin/sh -c #(nop) COPY dir:e9a848580d7409c11…   21Bab8ad35f5a93        About a minute ago   /bin/sh -c #(nop) COPY dir:4ff26c2555a73b795…   1.18kB29a6368b96c5        About a minute ago   /bin/sh -c #(nop) COPY dir:cb92d968d83d14948…   3.43kBea429fb6f1fa        About a minute ago   /bin/sh -c #(nop) COPY file:b1cc7638b7536f51…   139Bf0e1dbcec6c5        About a minute ago   /bin/sh -c #(nop) COPY file:e0f1165c2cf43ac3…   1.07kB8138a6c99655        About a minute ago   /bin/sh -c apk add --no-cache     bash     n…   145MBb743c478b647        2 minutes ago        /bin/sh -c #(nop)  LABEL description=Minimal…   0Bf3dab9765884        2 minutes ago        /bin/sh -c #(nop)  LABEL maintainer=Eivind M…   0B196d12cf6ab1        11 days ago          /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B<missing>           11 days ago          /bin/sh -c #(nop) ADD file:25c10b1d1b41d46a1…   4.41MB

Given the complete list of packages to install (they're shown during the image build), we can get their individual sizes using apk info -s NAME_OF_PACKAGE

(I've sorted them in order by decreasing size, and am only showing the first 20)

user@host:~$ sudo docker run -it alpine sh -c "apk update; apk info -s ncurses-terminfo-base ncurses-terminfo ncurses-libs readline bash libxau libbsd libxdmcp libxcb libx11 libxext libbz2 expat libpng freetype fontconfig libgcc libgomp lcms2 libltdl libxml2 imagemagick-libs libxrender pixman cairo libffi libintl libuuid libblkid libmount pcre glib dbus-libs avahi-libs gmp nettle p11-kit libtasn1 libunistring gnutls libstdc++ cups-libs jbig2dec libjpeg-turbo tiff ghostscript libxft graphite2 harfbuzz pango libcroco shared-mime-info gdk-pixbuf librsvg libwebp imagemagick nginx php7-common libedit php7 php7-ctype ca-certificates nghttp2-libs libssh2 libcurl php7-curl php7-dom php7-fileinfo php7-fpm libice libsm libxt libxpm php7-gd php7-iconv php7-imagick icu-libs php7-intl php7-json php7-mbstring php7-opcache php7-openssl php7-pdo php7-mysqlnd php7-pdo_mysql php7-phar php7-session libzip php7-zip" | awk '/^[0-9]/{gsub(/[^0-9]+$/,"",$1); print $1,prev,"\r"; next}{prev=$1}' | sort -gr | head -n 2050036736 ghostscript-9.24-r031248384 icu-libs-60.2-r27245824 ncurses-terminfo-6.1_p20180818-r15070848 php7-fileinfo-7.2.10-r04849664 php7-fpm-7.2.10-r04775936 php7-7.2.10-r04489216 imagemagick-7.0.7.32-r03440640 imagemagick-libs-7.0.7.32-r03379200 libx11-1.6.5-r13010560 glib-2.56.1-r02338816 shared-mime-info-1.9-r02203648 harfbuzz-1.7.6-r11638400 php7-mbstring-7.2.10-r01470464 libunistring-0.9.7-r01384448 libstdc++-6.4.0-r81282048 gnutls-3.6.2-r01236992 p11-kit-0.23.10-r01224704 libxml2-2.9.8-r01187840 libcroco-0.6.12-r11175552 nginx-1.14.0-r1

Or a grand total size of all the packages put together:

user@host:~$ sudo docker run -it alpine sh -c "apk update; apk info -s ncurses-terminfo-base ncurses-terminfo ncurses-libs readline bash libxau libbsd libxdmcp libxcb libx11 libxext libbz2 expat libpng freetype fontconfig libgcc libgomp lcms2 libltdl libxml2 imagemagick-libs libxrender pixman cairo libffi libintl libuuid libblkid libmount pcre glib dbus-libs avahi-libs gmp nettle p11-kit libtasn1 libunistring gnutls libstdc++ cups-libs jbig2dec libjpeg-turbo tiff ghostscript libxft graphite2 harfbuzz pango libcroco shared-mime-info gdk-pixbuf librsvg libwebp imagemagick nginx php7-common libedit php7 php7-ctype ca-certificates nghttp2-libs libssh2 libcurl php7-curl php7-dom php7-fileinfo php7-fpm libice libsm libxt libxpm php7-gd php7-iconv php7-imagick icu-libs php7-intl php7-json php7-mbstring php7-opcache php7-openssl php7-pdo php7-mysqlnd php7-pdo_mysql php7-phar php7-session libzip php7-zip" | awk '/^[0-9]+/ {s+=$1} END {printf "%.0f\n", s}'152952832

At this point, it looks like your problem is a "your stuff is too big" problem, rather than a docker problem. You may or may not have known that part before, but I've learned some stuff about digging in docker, so it's been fun :-)


Edit 2018-09-25: This is now not so useful, after correcting my earlier errors, but perhaps still has some relevant information: It occurs to me that even though my build of the full image failed, I don't care why - we're only interested in poking around in the big layer where the apks are installed. So, I made a fairly minimal dockerfile:

FROM alpine:3.8# install nginx, php, and php extensions for CraftRUN apk add --no-cache \    bash \    nginx \    php7 \    php7-fpm \    php7-opcache \    php7-phar \    php7-zlib \    php7-ctype \    php7-session \    php7-fileinfo \# Required php extensions for Craft    php7-pdo \    php7-pdo_mysql \    php7-gd \    php7-openssl \    php7-mbstring \    php7-json \    php7-curl \    php7-zip \# Optional extensions for Craft    php7-iconv \    php7-intl \    php7-dom \# Extra Optional extensions for Craft    imagemagick \    php7-imagickCMD sh

Built it: sudo docker build . and got an image Successfully built e344a23763c9. Running a container from this image with sudo docker run -it e344a23763c9 I got a shell. Installed ncdu (which I could have installed via the dockerfile) with apk add --no-cache ncdu then ran ncdu / - now I can easily see where the big directories are, starting at the root (Note: I have trimmed small files & dirs from the output):

  146.0 MiB [##########] /usr                                                                                                                                      3.6 MiB [          ] /lib    2.0 MiB [          ] /etc    1.4 MiB [          ] /bin

Navigating to /usr we find:

   84.4 MiB [##########] /lib   35.6 MiB [####      ] /share   20.5 MiB [##        ] /bin    5.5 MiB [          ] /sbin

In /usr/lib:

   25.7 MiB [##########]  libicudata.so.60.2   15.0 MiB [#####     ]  libgs.so.9.24    9.0 MiB [###       ] /php7    3.9 MiB [#         ] /ImageMagick-7.0.7    2.3 MiB [          ]  libicui18n.so.60.2    2.2 MiB [          ]  libMagickCore-7.Q16HDRI.so.6.0.0    1.5 MiB [          ]  libicuuc.so.60.2    1.4 MiB [          ]  libgio-2.0.so.0.5600.1    1.4 MiB [          ]  libunistring.so.2.0.0    1.3 MiB [          ]  libstdc++.so.6.0.22    1.2 MiB [          ]  libgnutls.so.30.20.2    1.2 MiB [          ]  libxml2.so.2.9.8    1.1 MiB [          ]  libX11.so.6.3.0    1.1 MiB [          ]  libMagickWand-7.Q16HDRI.so.6.0.0    1.1 MiB [          ]  libp11-kit.so.0.3.0

In /usr/share:

   17.7 MiB [##########] /ghostscript    6.9 MiB [###       ] /terminfo    5.6 MiB [###       ] /mime    2.3 MiB [#         ] /gtk-doc    2.1 MiB [#         ] /X11

In /usr/bin:

   14.8 MiB [##########]  gs    4.5 MiB [###       ]  php7

It looks to me like imagemagick is the single biggest contributor (along with its dependency ghostscript). Running a trial build without imagemagick & php7-imagick yields an image layer size of 65.8MB (as reported by docker history).

Removing php7-intl gets the layer down to 32.7MB (mostly by removing libicudata.so.60.2 which is part of "International Components for Unicode")

If you want a smaller container than your original, I think you you either need to do away with image processing and internationalisation, or find smaller ways to achieve them - they're far and away the largest possibly-trimmable components (depending on what you want to achieve).


You can get a tiny reduction in size/layers by combining your chmod calls:

RUN chmod 777 -R \   /www/config \   /www/vendor \   /www/storage \   /www/web/cpresources \&& chmod 777 \   /www/.env \   /www/composer.json \   /www/composer.lock

Edit 2018-09-29: I notice that the terminfo database in the image is [probably] excessive - almost 7MB according to the alpine package database entry for ncurses-terminfo. It has terminfos for virtually every possible term, which seems like overkill for a docker container (depending on what you are doing).

I can't see an easy way to not install that package (other things require it to be installed and I couldn't find a sensible way to force apk to not honor the dependency), but you could get the RUN apk add ... line to delete the unused terminfos before the layer is committed - delete most of these files.


One thing you can certainly do is remove the apk cache after all apk add calls are done:

RUN rm -rf /var/cache/apk