Come memorizzare nella cache l'istruzione di installazione RUN npm quando docker crea un Dockerfile


86

Attualmente sto sviluppando un backend Node per la mia applicazione. Quando si dockerizza it ( docker build .) la fase più lunga è il file RUN npm install. L' RUN npm installistruzione viene eseguita su ogni piccola modifica del codice del server, il che impedisce la produttività a causa dell'aumento del tempo di compilazione.

Ho scoperto che l'esecuzione di npm install dove risiede il codice dell'applicazione e l'aggiunta di node_modules al contenitore con l'istruzione ADD risolve questo problema, ma è lontano dalla migliore pratica. In un certo senso rompe l'intera idea di dockerizzarlo e fa sì che il container pesi molto di più.

Altre soluzioni?

Risposte:


125

Ok, quindi ho trovato questo fantastico articolo sull'efficienza durante la scrittura di un file Docker.

Questo è un esempio di un file docker non valido che aggiunge il codice dell'applicazione prima di eseguire l' RUN npm installistruzione:

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

Dividendo la copia dell'applicazione in 2 istruzioni COPY (una per il file package.json e l'altra per il resto dei file) ed eseguendo l'istruzione di installazione npm prima di aggiungere il codice effettivo, qualsiasi modifica del codice non attiverà l'installazione di RUN npm istruzione, solo le modifiche del package.json lo attiveranno. File docker migliore pratica:

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

Qui è dove è stato aggiunto il file package.json, installa le sue dipendenze e copiale nel contenitore WORKDIR, dove risiede l'app:

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

Per evitare la fase di installazione di npm su ogni build docker, copia queste righe e modifica ^ / opt / app ^ nella posizione in cui si trova la tua app all'interno del contenitore.


2
Che funzioni. Alcuni punti però. ADDè scoraggiato a favore di COPY, afaik. COPYè ancora più efficace. IMO, gli ultimi due paragrafi non sono necessari, poiché sono duplicati e anche dal punto di vista dell'app non importa dove sul file system risiede l'app, purché WORKDIRsia impostata.
eljefedelrodeodeljefe

2
Meglio ancora è combinare tutti i comandi apt-get in un unico RUN, incluso un file apt-get clean. Inoltre, aggiungi ./node_modules al tuo .dockerignore, per evitare di copiare la directory di lavoro nel contenitore creato e per velocizzare il passaggio di copia del contesto di compilazione della compilazione.
Simmetrico

1
Lo stesso approccio ma anche solo l'aggiunta package.jsonalla posizione di riposo finale funziona bene (eliminando qualsiasi cp / mv).
J. Fritz Barnes

27
Non lo capisco. Perché lo installi in una directory temporanea e poi lo sposti nella directory dell'app? Perché non installarlo nella directory delle app? Cosa mi manca qui?
joniba

1
Questo probabilmente è morto, ma ho pensato di menzionarlo per i futuri lettori. @joniba un motivo per farlo sarebbe montare la cartella temp come volume persistente in compose senza interferire con i node_modules del file system dell'host locale. Ad esempio, potrei voler eseguire la mia app localmente ma anche in un contenitore e mantenere comunque la possibilità di avere i miei node_modules non costantemente scaricati nuovamente quando package.json cambia
dancypants

41

Strano! Nessuno menziona più fasi .

# ---- 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 fantastico qui: https://codefresh.io/docker-tutorial/node_docker_multistage/


2
Che succede con una COPYdichiarazione dopo ENTRYPOINT?
lindhe

Fantastico, questo offre anche un buon vantaggio quando stai testando il tuo Dockerfile senza reinstallare le dipendenze ogni volta che modifichi il tuo Dockerfile
Xavier Brassoud

31

Ho scoperto che l'approccio più semplice è sfruttare la semantica della copia di Docker:

L'istruzione COPY copia nuovi file o directory da e li aggiunge al filesystem del contenitore nel percorso.

Ciò significa che se si copia prima in modo esplicito il package.jsonfile e quindi si esegue il npm installpassaggio, è possibile memorizzarlo nella cache e quindi è possibile copiare il resto della directory di origine. Se il package.jsonfile è stato modificato, sarà nuovo e eseguirà nuovamente il caching dell'installazione di npm per build future.

Uno snippet dalla fine di un Dockerfile sarebbe simile a:

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

# install application
COPY     . /usr/app

7
Invece di cd /usr/apppuoi / dovresti usare WORKDIR /usr/app.
Vladimir Vukanac

1
@VladimirVukanac: +1: sull'utilizzo di WORKDIR; Ho aggiornato la risposta sopra per tenerne conto.
J. Fritz Barnes

1
@ user557657 WORKDIR imposta la directory all'interno dell'immagine futura da cui verrà eseguito il comando. Quindi, in questo caso, sta eseguendo npm install /usr/appdall'interno dell'immagine che creerà un /usr/app/node_modulescon le dipendenze installate dall'installazione di npm.
J. Fritz Barnes

1
@ J.FritzBarnes grazie mille. isnt COPY . /usr/appsarebbe copiare package.jsonnuovo file /usr/appcon il resto dei file?
user557657

1
Docker non rieseguirà il npm installcomando in caso di package.jsonmodifiche, memorizza nella cache il risultato del comando RUN e presume che lo stesso comando RUN produca lo stesso risultato. Per invalidare la cache dovresti eseguire docker buildcon il flag --no-cache o modificare in qualche modo il comando RUN.
Mikhail Zhuravlev

3

Immagino che tu lo sappia già, ma potresti includere un file .dockerignore nella stessa cartella contenente

node_modules
npm-debug.log

per evitare di gonfiare la tua immagine quando spingi al docker hub


1

non è necessario utilizzare la cartella tmp, basta copiare package.json nella cartella dell'applicazione del contenitore, eseguire alcune operazioni di installazione e copiare tutti i file in seguito.

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

quindi stai eseguendo l'installazione di npm nella directory del contenitore / opt / app, quindi stai copiando tutti i file dalla macchina locale a / opt / app?
user557657
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.