【问题标题】:How to cache the RUN npm install instruction when docker build a Dockerfiledocker构建Dockerfile时如何缓存RUN npm install指令
【发布时间】:2016-06-16 22:17:29
【问题描述】:

我目前正在为我的应用程序开发 Node 后端。 当对它进行 dockerizing (docker build .) 时,最长的阶段是 RUN npm installRUN npm install 指令在每次小的服务器代码更改上运行,这会通过增加构建时间来阻碍生产力。

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

还有其他解决方案吗?

【问题讨论】:

    标签: node.js docker dockerfile


    【解决方案1】:

    好的,所以我发现this great article 关于编写 docker 文件时的效率。

    这是在运行RUN npm install 指令之前添加应用程序代码的错误 docker 文件示例:

    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 指令,任何代码更改都不会触发运行 npm install 指令,只有 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 build 上的 npm install 阶段,只需复制这些行并将 ^/opt/app^ 更改为您的应用在容器内的位置。

    【讨论】:

    • 这行得通。不过有几点。 ADD 不赞成 COPY,afaik。 COPY 更有效。 IMO,最后两段不是必需的,因为它们是重复的,而且从应用程序的角度来看,只要设置了WORKDIR,应用程序在文件系统上的位置并不重要。
    • 更好的方法是将所有 apt-get 命令组合到一个 RUN 中,包括 apt-get clean。此外,将 ./node_modules 添加到 .dockerignore 中,以避免将工作目录复制到构建的容器中,并加快构建的构建上下文复制步骤。
    • 同样的方法,但只是将package.json 添加到最终的静止位置也可以正常工作(消除任何 cp/mv)。
    • 我不明白。为什么要安装在临时目录中,然后将其移动到应用程序目录中?为什么不直接安装在 app 目录中呢?我在这里错过了什么?
    • 这可能已经死了,但我想我会为未来的读者提及它。 @joniba 这样做的一个原因是将临时文件夹挂载为 compose 中的持久卷,而不会干扰本地主机文件系统的 node_modules。 IE。我可能想在本地运行我的应用程序,但也可以在容器中运行,并且仍然能够在 package.json 更改时不经常重新下载我的 node_modules
    【解决方案2】:

    奇怪!没有人提到多阶段构建

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

    【讨论】:

    • ENTRYPOINT 之后有一个COPY 语句是怎么回事?
    • 太好了,当您在每次编辑 Dockerfile 时无需重新安装依赖项即可测试 Dockerfile 时,这也提供了一个很好的优势
    【解决方案3】:

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

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

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

    Dockerfile 末尾的 sn-p 如下所示:

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

    【讨论】:

    • 您可以/应该使用WORKDIR /usr/app,而不是cd /usr/app
    • @VladimirVukanac :+1: 关于使用 WORKDIR;我已经更新了上面的答案以考虑到这一点。
    • @user557657 WORKDIR 设置未来映像中运行命令的目录。因此,在这种情况下,它会从映像中的/usr/app 运行 npm install,这将创建一个 /usr/app/node_modules,其中包含从 npm install 安装的依赖项。
    • @J.FritzBarnes 非常感谢。 COPY . /usr/app 是否会再次将 package.json 文件与其余文件一起复制到 /usr/app 中?
    • 如果package.json 发生变化,Docker 不会重新运行npm install 命令,它会缓存 RUN 命令结果并假设相同的 RUN 命令产生相同的结果。要使缓存无效,您应该使用 --no-cache 标志运行 docker build,或者以某种方式更改 RUN 命令。
    【解决方案4】:

    我想你可能已经知道了,但你可以在同一文件夹中包含一个 .dockerignore 文件,其中包含

    node_modules
    npm-debug.log
    

    为了避免在推送到 docker hub 时使图像膨胀

    【讨论】:

      【解决方案5】:

      您不需要使用 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 ?
      • 确保将 node_modules 添加到 .dockerignore
      猜你喜欢
      • 2014-07-19
      • 1970-01-01
      • 1970-01-01
      • 2015-05-23
      • 2020-08-10
      • 1970-01-01
      • 2021-05-08
      • 1970-01-01
      • 2021-05-16
      相关资源
      最近更新 更多