PSA: use COPY –chown instead of RUN chown after COPY in Dockerfile

I stumbled upon this when I was containerizing a proprietary web app we're using, in my Dockerfile I copied the app with COPY and chowned it in RUN, wasn't happy with image size, then I found COPY --chown parameter, with it the size of an image was smaller by the size of the directory I was COPYing, of course I know every RUN creates a layer containing all changes made inside of it, kinda like Copy On Write mechanism, but I didn't realized a simple change of file properties like owner makes a whole new copy


FROM alpine:latest AS downloader


RUN wget -q -O- | \
unzip -q -

FROM php:7.2-apache

COPY --chown=www-data:www-data --from=downloader /app /var/www/html/

# build time dependencies needed to build required PHP extensions
ENV EXT_BUILD_DEPS libfreetype6-dev libjpeg62-turbo-dev libpng-dev \
libxmlrpc-epi-dev libxmltok1-dev libxslt1-dev libssl-dev libz-dev libtidy-dev libc-client-dev \
librecode-dev libmariadb-dev libbz2-dev libpspell-dev libkrb5-dev libmemcached-dev libzip-dev \

# run time dependencies needed for PHP the required extensions to run
ENV EXT_RUNTIME_DEPS libpng16-16 libc-client2007e libmemcached11 libaspell15 librecode0 libxslt1.1 \
libzip4 libtidy5deb1 libjpeg62-turbo libmemcachedutil2 libfreetype6 libmagickwand-6.q16-6

RUN export PHP_EXT_DIR=$(php-config --extension-dir) \
&& curl -sSL \
|tar zxv -C$PHP_EXT_DIR/ ioncube/ --strip-components=1 \
&& mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
&& echo "zend_extension = $PHP_EXT_DIR/" >> "$PHP_INI_DIR/php.ini" \
&& apt-get update && apt-get install --no-install-recommends -y $EXT_BUILD_DEPS $EXT_RUNTIME_DEPS \
&& docker-php-ext-configure gd --with-freetype-dir --with-jpeg-dir \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-configure imap --with-imap-ssl --with-kerberos \
&& docker-php-ext-install -j$(nproc) imap \
&& docker-php-ext-install -j$(nproc) intl \
&& docker-php-ext-install -j$(nproc) mysqli \
&& docker-php-ext-install -j$(nproc) opcache \
&& docker-php-ext-install -j$(nproc) pspell \
&& docker-php-ext-install -j$(nproc) recode \
&& docker-php-ext-install -j$(nproc) tidy \
&& docker-php-ext-install -j$(nproc) xmlrpc \
&& docker-php-ext-install -j$(nproc) xsl \
&& docker-php-ext-configure zip --with-libzip \
&& docker-php-ext-install -j$(nproc) zip \
&& yes "" | pecl install memcached \
&& docker-php-ext-enable memcached \
&& yes "" | pecl install imagick \
&& docker-php-ext-enable imagick \
&& apt-get purge -y -f --force-yes $EXT_BUILD_DEPS \
&& apt-get autoremove -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*


in my case app size is around 160MB, an image is 600MB, with `chown www-data:www-data /var/www/html` in RUN instead as COPY option the image size is around 760MB

5 thoughts on “PSA: use COPY –chown instead of RUN chown after COPY in Dockerfile”

  1. Every instruction in a `Dockerfile` creates a new layer, not just `RUN`. While it won’t reduce the size by much, you can reduce the number of layers by combining your ENV instructions in to one using quotes and separating by commas.

  2. As a side note, I see you’re performing a multi-stage build just to get the output of a zip file.

    You could’ve just piped the zip file to an unzip into a directory with a one liner pipe without the zip file ever touching the disk.


Leave a Comment