Como armazenar em cache a instrução de instalação RUN npm ao docker construir um Dockerfile


86

Atualmente, estou desenvolvendo um back-end do Node para meu aplicativo. Ao encaixá-lo ( docker build .), a fase mais longa é o RUN npm install. A RUN npm installinstrução é executada em cada pequena alteração no código do servidor, o que impede a produtividade por meio do aumento do tempo de construção.

Descobri que executar o npm install onde reside o código do aplicativo e adicionar node_modules ao contêiner com a instrução ADD resolve esse problema, mas está longe de ser a melhor prática. Isso meio que quebra toda a ideia de encaixá-lo e fazer com que o contêiner pese muito mais.

Alguma outra solução?

Respostas:


124

Ok, então encontrei este ótimo artigo sobre eficiência ao escrever um arquivo docker.

Este é um exemplo de arquivo docker inválido adicionando o código do aplicativo antes de executar a RUN npm installinstrução:

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"]

Dividindo a cópia do aplicativo em 2 instruções COPY (uma para o arquivo package.json e outra para o restante dos arquivos) e executando a instrução de instalação do npm antes de adicionar o código real, qualquer alteração de código não acionará a instalação RUN do npm instrução, apenas alterações do package.json irão acioná-lo. Arquivo docker de prática recomendada:

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"]

É aqui que o arquivo package.json adicionado, instale suas dependências e copie-as para o contêiner WORKDIR, onde o aplicativo reside:

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

Para evitar a fase de instalação do npm em cada build do docker, apenas copie essas linhas e altere ^ / opt / app ^ para o local em que seu aplicativo está dentro do contêiner.


2
Isso funciona. Porém, alguns pontos. ADDé desencorajado em favor de COPY, afaik. COPYé ainda mais eficaz. IMO, os dois últimos parágrafos não são necessários, uma vez que são duplicados e também do ponto de vista do app não importa onde no sistema de arquivos o app está, desde que WORKDIResteja configurado.
— eljefedelrodeodeljefe

2
Melhor ainda é combinar todos os comandos apt-get em um RUN, incluindo um apt-get clean. Além disso, adicione ./node_modules ao seu .dockerignore, para evitar copiar seu diretório de trabalho em seu contêiner construído e para acelerar a etapa de cópia do contexto de construção da construção.
— Simétrico

1
A mesma abordagem, mas apenas adicionando package.jsonà posição de repouso final, também funciona bem (eliminando qualquer cp / mv).
— J. Fritz Barnes,

27
Eu não entendo. Por que você instala em um diretório temporário e o move para o diretório do aplicativo? Por que não apenas instalar no diretório do aplicativo? O que estou perdendo aqui?
— Joniba

1
Provavelmente está morto, mas pensei em mencioná-lo para futuros leitores. @joniba um motivo para fazer isso seria montar a pasta temporária como um volume persistente na composição sem interferir com os node_modules do sistema de arquivos host local. Ou seja, posso querer executar meu aplicativo localmente, mas também em um contêiner e ainda manter a capacidade de fazer o download de meus node_modules não constantemente quando o package.json mudar
— dancypants

41

Esquisito! Ninguém menciona a construção em vários estágios .

# ---- 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

Tuto incrível aqui: https://codefresh.io/docker-tutorial/node_docker_multistage/


2
O que há em ter uma COPYdeclaração depois ENTRYPOINT?
— Lindhe

Ótimo, isso também fornece uma boa vantagem quando você está testando seu Dockerfile sem reinstalar dependências cada vez que você edita seu Dockerfile
— Xavier Brassoud

30

Descobri que a abordagem mais simples é aproveitar a semântica de cópia do Docker:

A instrução COPY copia novos arquivos ou diretórios e os adiciona ao sistema de arquivos do contêiner no caminho.

Isso significa que se você primeiro copiar explicitamente o package.jsonarquivo e depois executar a npm installetapa, ele poderá ser armazenado em cache e, em seguida, poderá copiar o restante do diretório de origem. Se o package.jsonarquivo foi alterado, isso será novo e executará novamente o cache de instalação do npm para compilações futuras.

Um snippet do final de um Dockerfile ficaria assim:

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

# install application
COPY     . /usr/app

6
Em vez de cd /usr/appvocê pode / deve usar WORKDIR /usr/app.
— Vladimir Vukanac de

1
@VladimirVukanac: +1: ao usar o WORKDIR; Atualizei a resposta acima para levar isso em consideração.
— J. Fritz Barnes

é npm install executado no diretório / usr / app ou. ?
— user557657

1
@ user557657 O WORKDIR define o diretório na imagem futura a partir do qual o comando será executado. Portanto, neste caso, ele está executando a instalação do npm de /usr/appdentro da imagem que criará um /usr/app/node_modulescom dependências instaladas da instalação do npm.
— J. Fritz Barnes

1
@ J.FritzBarnes muito obrigado. isnt COPY . /usr/appcopiaria o package.jsonarquivo novamente /usr/appcom o resto dos arquivos?
— user557657

3

Imagino que você já saiba, mas poderia incluir um arquivo .dockerignore na mesma pasta contendo

node_modules
npm-debug.log

para evitar o inchaço de sua imagem ao empurrar para o hub do docker


1

você não precisa usar a pasta tmp, apenas copie o package.json para a pasta do aplicativo do seu contêiner, faça algum trabalho de instalação e copie todos os arquivos mais tarde.

COPY app/package.json /opt/app/package.json
RUN cd /opt/app && npm install
COPY app /opt/app

então você está executando npm install no diretório container / opt / app e copiando todos os arquivos da máquina local para / opt / app?
— user557657 01 de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.