Как кэшировать инструкцию по установке RUN npm при сборке Dockerfile файла

86

В настоящее время я разрабатываю серверную часть Node для своего приложения. При dockerizing it ( docker build .) самая длинная фаза - это RUN npm install. В RUN npm installпробеги инструкции на каждом небольшом изменении кода сервера, производительность которых тормозит за счет увеличения времени сборки.

Я обнаружил, что запуск npm install там, где находится код приложения, и добавление node_modules в контейнер с помощью инструкции ADD решает эту проблему, но это далеко не лучшая практика. Это как бы нарушает саму идею докеризации и приводит к увеличению веса контейнера.

Какие-нибудь другие решения?

охадгк
источник

Ответы:

124

Хорошо, я нашел эту отличную статью об эффективности при написании файла докера.

Это пример плохого файла докера, добавляющего код приложения перед выполнением RUN npm installинструкции:

FROM ubuntu

RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
RUN apt-get update
RUN apt-get -y install python-software-properties git build-essential
RUN add-apt-repository -y ppa:chris-lea/node.js
RUN apt-get update
RUN apt-get -y install nodejs

WORKDIR /opt/app

COPY . /opt/app
RUN npm install
EXPOSE 3001

CMD ["node", "server.js"]

Разделив копию приложения на 2 инструкции КОПИРОВАНИЯ (одна для файла package.json, а другая - для остальных файлов) и запустив инструкцию установки npm перед добавлением фактического кода, любое изменение кода не приведет к запуску установки RUN npm. инструкции, только изменения package.json вызовут ее. Лучше практиковаться в файле докеров:

FROM ubuntu
MAINTAINER David Weinstein <david@bitjudo.com>

# install our dependencies and nodejs
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
RUN apt-get update
RUN apt-get -y install python-software-properties git build-essential
RUN add-apt-repository -y ppa:chris-lea/node.js
RUN apt-get update
RUN apt-get -y install nodejs

# use changes to package.json to force Docker not to use the cache
# when we change our application's nodejs dependencies:
COPY package.json /tmp/package.json
RUN cd /tmp && npm install
RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/

# From here we load our application's code in, therefore the previous docker
# "layer" thats been cached will be used if possible
WORKDIR /opt/app
COPY . /opt/app

EXPOSE 3000

CMD ["node", "server.js"]

Сюда добавляется файл package.json, устанавливаются его зависимости и копируются в контейнер WORKDIR, где находится приложение:

ADD package.json /tmp/package.json
RUN cd /tmp && npm install
RUN mkdir -p /opt/app && cp -a /tmp/node_modules /opt/app/

Чтобы избежать фазы установки npm в каждой сборке докеров, просто скопируйте эти строки и измените ^ / opt / app ^ на место, где ваше приложение находится внутри контейнера.

охадгк
источник
2
Это работает. Хотя кое-что. ADDне приветствуется в пользу COPY, афаик. COPYеще эффективнее. IMO, последние два абзаца не нужны, поскольку они дублируют друг друга, а также с точки зрения приложения не имеет значения, где в файловой системе находится приложение, если WORKDIRоно установлено.
eljefedelrodeodeljefe
2
Еще лучше объединить все команды apt-get в один RUN, включая файл apt-get clean. Кроме того, добавьте ./node_modules в свой .dockerignore, чтобы избежать копирования рабочего каталога в созданный контейнер и ускорить этап копирования контекста сборки.
Symmetric
1
Тот же подход, но просто добавление package.jsonк конечному положению покоя также отлично работает (исключая любые cp / mv).
Дж. Фриц Барнс
27
Я не понимаю. Почему вы устанавливаете во временный каталог, а затем перемещаете его в каталог приложения? Почему бы просто не установить в каталог приложения? Что мне здесь не хватает?
joniba
1
Это, вероятно, мертво, но подумал, что упомянул об этом для будущих читателей. @joniba одна из причин сделать это - смонтировать временную папку в качестве постоянного тома в compose без вмешательства в node_modules файловой системы локального хоста. Т.е. я мог бы захотеть запустить свое приложение локально, но также в контейнере, сохранив при этом возможность, чтобы мои node_modules не загружались постоянно при изменении package.json
dancypants
41

Странно! Никто не упоминает о многоступенчатой ​​сборке .

# ---- Base Node ----
FROM alpine:3.5 AS base
# install node
RUN apk add --no-cache nodejs-current tini
# set working directory
WORKDIR /root/chat
# Set tini as entrypoint
ENTRYPOINT ["/sbin/tini", "--"]
# copy project file
COPY package.json .

#
# ---- Dependencies ----
FROM base AS dependencies
# install node packages
RUN npm set progress=false && npm config set depth 0
RUN npm install --only=production 
# copy production node_modules aside
RUN cp -R node_modules prod_node_modules
# install ALL node_modules, including 'devDependencies'
RUN npm install

#
# ---- Test ----
# run linters, setup and tests
FROM dependencies AS test
COPY . .
RUN  npm run lint && npm run setup && npm run test

#
# ---- Release ----
FROM base AS release
# copy production node_modules
COPY --from=dependencies /root/chat/prod_node_modules ./node_modules
# copy app sources
COPY . .
# expose port and define CMD
EXPOSE 5000
CMD npm run start

Замечательный туто здесь: https://codefresh.io/docker-tutorial/node_docker_multistage/

Абденнур Туми
источник
2
Что случилось с COPYзаявлением после ENTRYPOINT?
lindhe
Отлично, это также дает хорошее преимущество, когда вы тестируете свой Dockerfile без переустановки зависимостей каждый раз, когда редактируете свой Dockerfile
Ксавье Брассуд,
30

Я обнаружил, что самый простой подход - использовать семантику копирования Docker:

Инструкция COPY копирует новые файлы или каталоги из и добавляет их в файловую систему контейнера по пути.

Это означает, что если вы сначала явно скопируете package.jsonфайл, а затем запустите npm installшаг, он может быть кэширован, а затем вы можете скопировать остальную часть исходного каталога. Еслиpackage.json файл был изменен, то это будет новый файл, и он повторно запустит кеширование установки npm для будущих сборок.

Фрагмент из конца Dockerfile будет выглядеть так:

# install node modules
WORKDIR  /usr/app
COPY     package.json /usr/app/package.json
RUN      npm install

# install application
COPY     . /usr/app
Дж. Фриц Барнс
источник
6
Вместо этого cd /usr/appвы можете / должны использовать WORKDIR /usr/app.
Владимир Вуканац
1
@VladimirVukanac: +1: при использовании WORKDIR; Я обновил ответ выше, чтобы учесть это.
J. Fritz Barnes
npm install запускается в каталоге / usr / app или. ?
user557657 01
1
@ user557657 WORKDIR устанавливает каталог в будущем образе, из которого будет запускаться команда. Итак, в этом случае он запускает npm install из /usr/appобраза, который создаст файл /usr/app/node_modulesс зависимостями, установленными из npm install.
J. Fritz Barnes
1
@ J.FritzBarnes большое спасибо. COPY . /usr/appразве не будет копировать package.jsonфайл снова /usr/appвместе с остальными файлами?
user557657
3

Я полагаю, вы уже знаете, но вы можете включить файл .dockerignore в ту же папку, содержащую

node_modules
npm-debug.log

чтобы избежать раздувания изображения при нажатии на docker hub

usrrname
источник
1

вам не нужно использовать папку tmp, просто скопируйте package.json в папку приложения вашего контейнера, выполните некоторые работы по установке и скопируйте все файлы позже.

COPY app/package.json /opt/app/package.json
RUN cd /opt/app && npm install
COPY app /opt/app
Майк Чжан
источник
Итак, вы выполняете установку npm в каталоге контейнера / opt / app, а затем копируете все файлы с локального компьютера в / opt / app?
user557657 01