Docker가 Dockerfile을 빌드 할 때 RUN npm 설치 지침을 캐시하는 방법


86

현재 내 애플리케이션을위한 노드 백엔드를 개발 중입니다. 도킹 할 때 ( docker build .) 가장 긴 단계는 RUN npm install. 이 RUN npm install명령은 작은 서버 코드 변경 시마다 실행되므로 빌드 시간이 늘어나 생산성이 저하됩니다.

응용 프로그램 코드가있는 곳에 npm 설치를 실행하고 ADD 명령을 사용하여 컨테이너에 node_modules를 추가하면이 문제가 해결되지만 모범 사례와는 거리가 멀습니다. 그것은 Dockerizing에 대한 전체 아이디어를 깨뜨리고 컨테이너의 무게를 훨씬 더 많이 유발합니다.

다른 해결책이 있습니까?

답변:


125

그래서 도커 파일을 작성할 때 효율성에 관한 이 훌륭한 기사를 찾았습니다 .

다음은 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 설치 명령을 실행하면 코드 변경으로 인해 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/

모든 도커 빌드에서 npm 설치 단계를 피하려면 해당 행을 복사하고 ^ / opt / app ^를 앱이 컨테이너 내부에있는 위치로 변경하십시오.


2
작동합니다. 그래도 몇 가지 포인트. , afaik ADD에 찬성하지 않습니다 COPY. COPY훨씬 더 효과적입니다. IMO, 마지막 두 단락은 중복되기 때문에 필요하지 않으며 앱의 관점에서도 설정되어있는 한 파일 시스템에서 앱이 어디에 있는지는 중요하지 않습니다 WORKDIR.
eljefedelrodeodeljefe

2
더 나은 방법은 모든 apt-get 명령을 apt-get clean. 또한 ./node_modules를 .dockerignore에 추가하여 작업 디렉토리가 빌드 된 컨테이너에 복사되는 것을 방지하고 빌드의 빌드 컨텍스트 복사 단계를 가속화하십시오.
Symmetric

1
동일한 접근 방식이지만 package.json최종 휴식 위치에 추가 하는 것만 으로도 잘 작동합니다 (cp / mv 제거).
J. Fritz Barnes

27
이해가 안 돼요. 임시 디렉토리에 설치 한 다음 앱 디렉토리로 이동하는 이유는 무엇입니까? 앱 디렉토리에 설치하지 않는 이유는 무엇입니까? 내가 여기서 무엇을 놓치고 있습니까?
joniba

1
이것은 아마도 죽었을 것입니다. @joniba이 작업을 수행하는 한 가지 이유는 로컬 호스트 파일 시스템의 node_modules를 방해하지 않고 compose에서 영구 볼륨으로 임시 폴더를 마운트하는 것입니다. 즉, 로컬에서뿐만 아니라 컨테이너에서도 내 앱을 실행하고 package.json이 변경 될 때 node_modules가 지속적으로 다시 다운로드되지 않도록하는 기능을 유지하고 싶을 수 있습니다.
dancypants

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

멋진 튜토리얼 : https://codefresh.io/docker-tutorial/node_docker_multistage/


2
COPY이후 성명서를 작성 하면 ENTRYPOINT어떻게됩니까?
lindhe

좋습니다. Dockerfile을 편집 할 때마다 종속성을 다시 설치하지 않고 Dockerfile을 테스트 할 때도 좋은 이점을 제공합니다
Xavier Brassoud

31

가장 간단한 접근 방식은 Docker의 복사 의미 체계를 활용하는 것입니다.

COPY 명령은 새 파일 또는 디렉토리를 복사하여 경로에있는 컨테이너의 파일 시스템에 추가합니다.

즉, 먼저 package.json파일을 명시 적으로 복사 한 다음 npm install캐시 할 수있는 단계 를 실행 한 다음 나머지 소스 디렉토리를 복사 할 수 있습니다. package.json파일이 변경된 경우 새 파일이며 향후 빌드를 위해 npm 설치 캐싱을 다시 실행합니다.

Dockerfile 끝의 스 니펫은 다음과 같습니다.

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

# install application
COPY     . /usr/app

7
대신 cd /usr/app당신이 할 수있는 / 사용해야합니다 WORKDIR /usr/app.
Vladimir Vukanac

1
@VladimirVukanac : +1 : WORKDIR 사용시; 이를 고려하여 위의 답변을 업데이트했습니다.
J. Fritz Barnes

1
@ user557657 WORKDIR은 명령이 실행될 향후 이미지 내에서 디렉토리를 설정합니다. 따라서이 경우 /usr/app이미지 내에서 npm install을 실행하여 npm 설치에서 /usr/app/node_modules설치된 종속성 을 생성합니다 .
J. Fritz Barnes

1
@ J.FritzBarnes 감사합니다. 밤은은 COPY . /usr/app복사합니다 package.json다시 파일을 /usr/app파일의 나머지?
user557657

1
Docker는 변경된 npm install명령을 다시 실행 하지 않고 package.jsonRUN 명령 결과를 캐시하고 동일한 RUN 명령이 동일한 결과를 생성한다고 가정합니다. 캐시를 무효화하려면 docker build--no-cache 플래그로 실행 하거나 RUN 명령을 어떻게 든 변경해야합니다.
Mikhail Zhuravlev

3

이미 알고 계실 것 같지만 같은 폴더에 .dockerignore 파일을 포함시킬 수 있습니다.

node_modules
npm-debug.log

Docker 허브로 푸시 할 때 이미지가 팽창하는 것을 방지하려면


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 설치를 실행 한 다음 모든 파일을 로컬 시스템에서 / opt / app으로 복사합니까?
user557657
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.