docker - как уменьшить размер образа
Некоторые замечания и соображения по тому, как уменьшить общий размер образа, создаваемого Docker. Размер образа (image) может легко превысить несколько гигабайт, даже у самого простого приложения, ведь мы создаем окружение, а значит включаем в образ большое количество зависимостей. При активном выпуске обновлений/релизов приложения это может стать значительной проблемой и потребовать контроля за расходованием дискового пространства, особенно на серверах где место лимитировано.
#1 Используем базовые Docker-образы меньших размеров
Разные образы могут предоставлять одинаковую функциональность, но быть основаны на разных операционных системах и редакциях. Например есть выбрать в качестве основы FROM ubuntu
, то мы получим сразу более 100MB, а если выбрать FROM alpine
, то начальный размер будет порядка 5MB. То же касается и других популярных образов.
Внимательно анализируем документацию образа, смотрим варианты, которые предлагают нам создатели и выбираем наиболее подходящие. Сейчас практически все популярные образы имеют множество вариантов сборки: -alpine
, -slim
и еще что-то подобное.
#2 Не устанавливаем ненужные программы и приложения
Стараемся не устанавливает "лишние" приложения, которые реально не используются в работе приложений. Если всё же без этого не обойтись, то разделяем сборки образов на development
и production
, используем разные Dockerfile-файлы. Тогда в образе разработки мы можем позволить себе больше, а образ для работы приложения будет держать в тонусе.
#3 Уменьшаем размеры "слоёв"
Каждая строчка в Dockerfile это один шаг в процессе сборки, и для каждого шага будет создан отдельный слой, который будет иметь свой размер.
Комбинируем одинаковые по смыслу RUN инструкции
ENV COMMON_PACKAGES bash \
curl \
build-base \
tzdata \
libxml2-dev \
postgresql-dev \
curl-dev sqlite-dev \
libsass \
libsass-dev \
gcompat \
libc6-compat \
nodejs \
npm \
yarn \
imagemagick \
git \
icu-dev
RUN set -xe \
&& apk update \
&& apk upgrade \
&& apk add --update ${COMMON_PACKAGES}
Здесь тоже есть минусы, если потребуется добавить всего один пакеты, образ будет пересобран с нуля, заново. Но возможно это и не минус вовсе, так как стабильность в таком случае будет выше, если что-то пойдет не так, мы узнаем это сразу.
#4 Не устанавливаем рекомендованные пакеты
Менеджер apt
, apt-get
могут предлагать и устанавливает рекомендованные пакеты, мы же явно отказываемся от этой услуги
RUN set -xe \
&& apt-get update -qq \
&& apt-get install --no-install-recommends -y apt-transport-https curl ca-certificates python3 python3-distutils
для apk
добавляем --no-cache
RUN set -xe \
&& apk update \
&& apk upgrade \
&& apk add --no-cache --update ${COMMON_PACKAGES} \
&& rm -rf /var/cache/apk/* /tmp/* /var/tmp/*
#5 удаляем артефакты работы apt, apk
Удаляем временные директории, использовавшиеся в работе apt, apk и очищаем кеш
для apk
RUN set -xe \
&& apk update \
&& apk upgrade \
&& apk add --no-cache --update ${COMMON_PACKAGES} \
&& rm -rf /var/cache/apk/* /tmp/* /var/tmp/*
для apt
RUN set -xe \
&& apt-get update -qq \
&& apt-get install --no-install-recommends -y ${COMMON_PACKAGES} \
&& rm -rf /var/lib/apt/lists/*
Бонус! Используем [FromLatest.io](https://fromlatest.io) для оценки своего Dockerfile на предмет оптимизации.
docker multistage-build
А это самое интересное и полезное.
Начиная с версии 17.05 Docker поддерживает этот новый подход к оптимизации размера образа. В отличие от использования нескольких Dockerfile файлов для сборки и публикации, этот подход использует один файл, понятен для чтения и удобен для поддержки.
Для использования multistrage
сборки указываем несколько инструкций FROM
. Каждая инструкция может использовать разные базовые образы, и каждая из них начинает свой этап сборки. Мы можем выборочно копировать артифакты из одного этапа в другой, собирая в финальном образе только самое необходимое.
Пример Rails приложения
# 1 =====================================
# специальный образ, который содержит все необходимые
# для сборки библиотеки и приложения
# размер образа ~730mb
FROM diproart/ruby:2.6.4-alpine3.10 AS builder
# полный на2ор пакетов
ENV COMMON_PACKAGES \
tzdata \
...
ENV ..
RUN set -xe \
&& apk update \
&& apk upgrade \
&& apk add --no-cache --update ${COMMON_PACKAGES} \
&& rm -rf /var/cache/apk/* /tmp/* /var/tmp/*
# additional clean
#RUN rm -rf /usr/local/bundle/cache/*gem
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY . .
RUN
# 2 =====================================
# чистый образ, в который добавим только самое необходимое
# размер образа ~51mb
FROM ruby:2.6.4-alpine3.10
# только необходимые для работы
# в production пакеты
ENV PRODUCTION_PACKAGES \
tzdata \
...
ENV ...
RUN set -xe \
&& apk update \
&& apk upgrade \
&& apk add --no-cache --update ${PRODUCTION_PACKAGES} \
&& rm -rf /var/cache/apk/* /tmp/* /var/tmp/*
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
WORKDIR /usr/src/app
# копируем скомпилированное приложение
# и пакеты в напрямую в образ из предыдущего шага
COPY --from=builder /usr/src/app .
COPY --from=builder /usr/local/bundle/ /usr/local/bundle/
EXPOSE 3000
ENTRYPOINT [ "bundle", "exec" ]
CMD [ "rails", "s", "-b", "0.0.0.0" ]
Подобный подход может радикально уменьшить размер конечного образа, в некоторых случаях до 4x-раз.