Docker构建Dockerfile时如何缓存RUN npm安装指令


86

我目前正在为我的应用程序开发Node后端。在dockerizing时docker build .,最长的阶段是RUN npm install。该RUN npm install指令在每一个小的服务器代码更改上运行,这通过增加构建时间来降低生产率。

我发现在应用程序代码所在的位置运行npm install并使用ADD指令将node_modules添加到容器中可以解决此问题,但这远非最佳实践。这有点破坏了将其Docker化的整个想法,并且使容器的重量增加了很多。

还有其他解决方案吗?

Answers:


124

好的,所以我发现了这篇关于写docker文件时效率的精彩文章

这是一个错误的docker文件在运行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个COPY指令(一个用于package.json文件,另一个用于其余文件)并在添加实际代码之前运行npm install指令,任何代码更改都不会触发RUN npm安装指令,只有package.json的更改才会触发它。更好地实践docker文件:

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/

为了避免每个docker构建上的npm安装阶段,只需复制这些行并将^ / opt / app ^更改为您的应用程序在容器中的位置。


2
那个有效。虽然有些要点。ADD不鼓励使用COPYafaik。COPY更有效。IMO,最后两段不是必需的,因为它们是重复的,并且从应用程序的角度来看,只要WORKDIR设置,应用程序在文件系统上的哪个位置都没有关系。
eljefedelrodeodeljefe

2
更好的方法是将所有apt-get命令组合到一个RUN中,包括一个apt-get clean。另外,将./node_modules添加到.dockerignore中,以避免将工作目录复制到构建的容器中,并加快构建的构建上下文复制步骤。
对称

1
同样的方法,只是增加package.json最终的静止位置也可以工作(消除任何cp / mv)。
J. Fritz Barnes

26
我不明白 为什么要安装在临时目录中,然后将其移至应用程序目录?为什么不只安装在app目录中?我在这里想念什么?
joniba '17

1
这可能已经死了,但我想为以后的读者提一下。@joniba这样做的原因之一是将temp文件夹作为持久卷安装在compose中,而不会干扰本地主机文件系统的node_modules。即,我可能想在本地运行我的应用程序,但也要在一个容器中运行,并且仍然能够在package.json更改时不不断重新下载我的node_modules
dantpants

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

很棒的tuto在这里:https ://codefresh.io/docker-tutorial/node_docker_multistage/


2
有什么用具有回事COPY后声明ENTRYPOINT
林德

太好了,这在您每次编辑Dockerfile时都在不重新安装依赖项的情况下测试Dockerfile时也提供了一个很好的优势
Xavier Brassoud

30

我发现最简单的方法是利用Docker的复制语义:

COPY指令从路径复制新文件或目录,并将它们添加到容器的文件系统中。

这意味着,如果您首先显式复制package.json文件,然后运行npm install可以对其进行缓存的步骤,然后可以复制源目录的其余部分。如果package.json文件已更改,则它将是新文件,它将重新运行npm install缓存,以供将来构建。

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
Vladimir Vukanac

1
@VladimirVukanac:+1:使用WORKDIR; 我已经更新了上面的答案以考虑到这一点。
J. Fritz Barnes

是npm install在/ usr / app目录或中运行。?
user557657 '19

1
@ user557657 WORKDIR在将来的映像中设置目录,从该目录将运行命令。因此,在这种情况下,它将/usr/app在映像中运行npm install,这将创建一个/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时使图像肿


1

您不需要使用tmp文件夹,只需将package.json复制到容器的应用程序文件夹,进行一些安装工作,然后再复制所有文件。

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

所以您要在容器目录/ opt / app中执行npm install,然后将所有文件从本地计算机复制到/ opt / app?
user557657 '19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.