Cara meng-cache instruksi RUN npm install ketika buruh pelabuhan membangun Dockerfile

86

Saya sedang mengembangkan backend Node untuk aplikasi saya. Saat melakukan dockerisasi ( docker build .), fase terpanjang adalah RUN npm install. The RUN npm installinstruksi berjalan pada setiap perubahan kode server kecil, yang produktivitas menghambat melalui peningkatan waktu membangun.

Saya menemukan bahwa menjalankan npm install di mana kode aplikasi berada dan menambahkan node_modules ke wadah dengan instruksi ADD memecahkan masalah ini, tetapi ini jauh dari praktik terbaik. Ini semacam merusak seluruh gagasan untuk melakukan dockerizing dan itu menyebabkan wadah menjadi lebih berat.

Ada solusi lain?

ohadgk
sumber

Jawaban:

125

Ok jadi saya menemukan artikel bagus tentang efisiensi saat menulis file buruh pelabuhan.

Ini adalah contoh dari file buruh pelabuhan yang buruk menambahkan kode aplikasi sebelum menjalankan RUN npm installinstruksi:

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

Dengan membagi salinan aplikasi menjadi 2 instruksi SALIN (satu untuk file package.json dan yang lainnya untuk file lainnya) dan menjalankan instruksi npm install sebelum menambahkan kode sebenarnya, perubahan kode apa pun tidak akan memicu RUN npm install instruksi, hanya perubahan package.json yang akan memicunya. File buruh pelabuhan praktik yang lebih baik:

FROM ubuntu
MAINTAINER David Weinstein <[email protected]>

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

Di sinilah file package.json ditambahkan, instal dependensinya, dan salin ke container WORKDIR, tempat aplikasi berada:

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

Untuk menghindari fase instal npm pada setiap build buruh pelabuhan, cukup salin baris tersebut dan ubah ^ / opt / app ^ ke lokasi tempat aplikasi Anda berada di dalam container.

ohadgk
sumber
2
Itu bekerja. Beberapa poin. ADDberkecil hati demi COPY, afaik. COPYbahkan lebih efektif. IMO, dua paragraf terakhir tidak diperlukan, karena merupakan duplikat dan juga dari sudut pandang aplikasi, tidak masalah di mana pun pada sistem file tempat aplikasi berada, selama WORKDIRdisetel.
eljefedelrodeodeljefe
2
Lebih baik lagi adalah menggabungkan semua perintah apt-get menjadi satu RUN, termasuk file apt-get clean. Selain itu, tambahkan ./node_modules ke .dockerignore Anda, untuk menghindari penyalinan direktori kerja Anda ke dalam container yang Anda buat, dan untuk mempercepat langkah penyalinan konteks build dari build.
Simetris
1
Pendekatan yang sama tetapi hanya menambahkan package.jsonke posisi istirahat terakhir juga berfungsi dengan baik (menghilangkan cp / mv).
J. Fritz Barnes
27
Saya tidak mengerti. Mengapa Anda menginstal di direktori temp dan kemudian memindahkannya ke direktori aplikasi? Mengapa tidak menginstal di direktori aplikasi? Apa yang kulewatkan di sini?
joniba
1
Ini mungkin sudah mati, tapi saya pikir saya menyebutkannya untuk pembaca selanjutnya. @joniba salah satu alasan untuk melakukan ini adalah untuk me-mount folder temp sebagai volume tetap dalam compose tanpa mengganggu node_modules sistem file host lokal. Yaitu saya mungkin ingin menjalankan aplikasi saya secara lokal tetapi juga dalam wadah dan masih mempertahankan kemampuan agar node_modules saya tidak terus-menerus diunduh ulang ketika package.json berubah
dancypants
41

Aneh! Tidak ada yang menyebutkan pembangunan multi-tahap .

# ---- 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 yang luar biasa di sini: https://codefresh.io/docker-tutorial/node_docker_multistage/

Abdennour TOUMI
sumber
2
Ada apa dengan COPYpernyataan setelahnya ENTRYPOINT?
lindhe
Hebat, itu juga memberikan keuntungan yang baik ketika Anda menguji Dockerfile Anda tanpa menginstal ulang dependensi setiap kali Anda mengedit Dockerfile Anda
Xavier Brassoud
31

Saya telah menemukan bahwa pendekatan paling sederhana adalah memanfaatkan semantik salinan Docker:

Instruksi COPY menyalin file atau direktori baru dari dan menambahkannya ke sistem file container di jalur.

Ini berarti bahwa jika Anda pertama kali menyalin package.jsonfile secara eksplisit lalu menjalankan npm installlangkah yang dapat di-cache dan kemudian Anda dapat menyalin seluruh direktori sumber. Jika package.jsonfile telah berubah, maka itu akan menjadi baru dan itu akan menjalankan kembali npm install caching itu untuk build masa depan.

Cuplikan dari akhir Dockerfile akan terlihat seperti ini:

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

# install application
COPY     . /usr/app
J. Fritz Barnes
sumber
7
Daripada cd /usr/appAnda bisa / harus menggunakan WORKDIR /usr/app.
Vladimir Vukanac
1
@VladimirVukanac: +1: tentang menggunakan WORKDIR; Saya telah memperbarui jawaban di atas untuk memperhitungkannya.
J. Fritz Barnes
1
@ user557657 WORKDIR menyetel direktori di dalam gambar yang akan datang dari mana perintah akan dijalankan. Jadi dalam kasus ini, menjalankan npm install dari /usr/appdalam image yang akan membuat /usr/app/node_modulesdengan dependensi yang diinstal dari npm install.
J. Fritz Barnes
1
@ J.Ritnes terima kasih banyak. isnt COPY . /usr/appakan menyalin package.jsonfile lagi /usr/appdengan sisa file?
pengguna557657
1
Docker tidak akan menjalankan kembali npm installperintah jika ada package.jsonperubahan, ia menyimpan hasil perintah RUN dan mengasumsikan bahwa perintah RUN yang sama menghasilkan hasil yang sama. Untuk membatalkan cache Anda harus menjalankan docker builddengan --no-cache flag, atau mengubah perintah RUN bagaimanapun caranya.
Mikhail Zhuravlev
3

Saya membayangkan Anda mungkin sudah tahu, tetapi Anda bisa menyertakan file .dockerignore di folder yang sama berisi

node_modules
npm-debug.log

untuk menghindari gambar Anda membengkak saat Anda mendorong ke hub buruh pelabuhan

usrrname
sumber
1

Anda tidak perlu menggunakan folder tmp, cukup salin package.json ke folder aplikasi penampung Anda, lakukan beberapa pekerjaan instalasi dan salin semua file nanti.

COPY app/package.json /opt/app/package.json
RUN cd /opt/app && npm install
COPY app /opt/app
Mike Zhang
sumber
jadi Anda menjalankan npm install di direktori container / opt / app lalu menyalin semua file dari mesin lokal ke / opt / app?
pengguna557657