이전 포스팅에서는 Dockerfile을 작성할 때 이미지 크기를 최적화하는 방법에 대해 알아보았다.
이번에는 Dockerfile에서 자주 사용되는 RUN
, CMD
, ENTRYPOINT
등의 지시어들의 차이점과 각각의 사용 방법에 대해 자세히 알아보도록 하자 👀
RUN
은 이미지를 빌드할 때 실행하는 명령어이다.
지금까지 Dockerfile을 생성하면서 많이 봤겠지만, 주로 패키지 설치나 파일 생성 등 이미지를 구성하는데 필요한 커맨드를 실행할 때
사용한다.
이전 포스팅에서도 정리했지만, 도커를 효율적으로 사용하기 위해서는 RUN 명령어를 여러개 사용하여 이미지를 구성하게 되면 레이어가 쌓이게되어 불필요하게 이미지 사이즈가 증가할 수 있다.
따라서, /
으로 개행을 사용하여 개발자가 읽기 쉽도록 구성하고, &&
를 사용하여 여러 명령어를 한번에 사용하도록 하자!
(이전에 정리했으니 간단하게 정리하고 넘어가겠다!)
CMD
와 ENTRYPOINT
는 컨테이너가 시작될 때 실행되는 명령어를 지정하는데 사용된다.
하지만, 여기에는 중요한 차이점이 있다.
우선 간단하게 CMD
명령어를 사용하여 컨테이너를 띄워보자
Sample Code
FROM ubuntu CMD ["echo", "hello"]
Result View
hello가 정상적으로 나오는 모습을 볼 수 있다.
그렇다면 컨테이너 실행 시점에 특정 명령어를 추가하면 어떻게 될까?
우선 Dockerfile은 기존과 동일하게 사용하고, 커맨드만 변경해보자
◉ 커맨드 - 추가 명령어 실행
docker run cmd_test echo world
Result View
결과를 살펴보면 Dockerfile을 생성하는 시점에 정의해둔 "hello"는 사라지고 world만 출력된다.
즉, CMD
명령어를 사용하면 새로운 명령어가 들어올 경우, 기존 명령어를 덮어씌우는 특징이 있다.
다음으로 ENTRYPOINT
명령어를 사용하여 Dockerfile을 구성해보자
Sample Code
FROM ubuntu ENTRYPOINT ["echo", "hello"]
Result View
보다시피 hello echo world가 출력되는 모습을 볼 수 있다.
그 이유는 ENTRYPOINT
의 경우, 추가되는 내용을 argument로 인식한다.
즉, echo world
라는 명령어를 컨테이너 실행 시점에 추가했다면 ENTRYPOINT ["echo", "hello", "echo", "world"]
로 이해하기 때문에 "hello echo world"가 출력되는 것이다.
따라서, ENTRYPOINT
는 Dockerfile로 지정한 명령어가 사라지지 않고 뒤에 추가로 들어온다고 생각하면 된다.
그렇다면 CMD
와 ENTRYPOINT
는 각각 언제 사용하는게 맞을까?
CMD
와 ENTRYPOINT
는 각각 다른 상황에서 사용하는 것이 좋다!
CMD
는 컨테이너 실행 시 명령어를 유연하게 변경해야 하는 경우에 사용한다.
예를 들어 개발 환경과 운영 환경에서 다른 명령어를 실행해야 할 때 유용할 것이다.
반면, ENTRYPOINT
는 컨테이너가 항상 동일한 명령어로 시작되어야 할 때 사용하는 것이 적합합다. (명령어가 불변하기 때문)
웹 서버나 데이터베이스 서버처럼 실행 명령어가 고정적인 경우에 적합할 것이다.
여러 포스팅에서 필자가 작성한 Dockerfile을 살펴보면 명령어를 2가지 방식으로 사용하는 모습을 볼 수 있다.
Sample Code - exec form
CMD ["go", "run", "/goapp/main.go"]
Sample Code - shell form
CMD go run /goapp/main.go
이처럼 Dockerfile에서 명령어를 작성하는 방법이 두가지가 있다.
이 두가지 방식 중에서는 exec form을 사용하는 것이 권장된다.
그 이유는 shell form의 경우 컨테이너의 쉘을 사용하게 되는데, 베이스 이미지에 따라 명령어의 동작이 달라질 수 있기 때문이다.
반면, exec form은 쉘을 거치지 않고 직접 실행되기 때문에 베이스 이미지와 상관없이 일관된 동작을 보장한다.
다만 한 가지 단점이 있다면 쉘의 기능을 사용할 수 없다는 점이다.
예를 들어 CMD ["echo", "$HOME"]
처럼 환경 변수를 사용하는 명령은 exec form에서 직접 사용할 수 없다는 것이다.
물론, 이런 경우에는 CMD ["/bin/bash", "-c", "echo $HOME"]
처럼 명시적으로 쉘을 실행하는 방식으로 우회할 수 있긴 하다.
다음으로 COPY
와 ADD
명령어에 대해서 살펴보려고 한다.
두 지시어 모두 호스트의 파일을 컨테이너로 복사할 때 사용하지만, 세부적인 차이가 존재한다.
COPY는 호스트의 로컬 파일만 이미지에 추가할 수 있다.
단순하고 직관적인 파일 복사 기능만 제공한다고 이해하면 된다.
지금까지 많이 사용했기 때문에 간단하게 예제 코드만 살펴보고 넘어가자
Sample Code
... COPY hello.html /var/www/html/ COPY ./app . ...
ADD
는 COPY
보다 더 많은 기능을 제공하는데, 호스트의 로컬 파일을 넘어서 외부 URL을 사용하여 파일을 다운로드할 수 있다.
또한, tar 파일을 자동으로 압축 해제하면서 복사를 수행할 수 있다.
예시 코드를 살펴보자
Sample Code
ADD https://example.com/file.tar.gz /app/ ADD app.tar.gz /app/
하지만 실제로 대부분의 경우 COPY
를 사용하는 것이 권장된다.
왜냐하면, 동작이 예측 가능하고 명확하기 떄문이다.
또한, ADD
의 자동 압축 해제 기능이 때로는 의도치 않은 결과를 초래할 수 있다는 불확실성이 존재한다.
실제로 URL 다운로드는 RUN curl
또는 wget
으로 직접 처리하는 것이 더 명시적이기 때문이다.
따라서, tar 압축 해제나 URL 다운로드가 필요한 특수한 경우에만 ADD
를 사용하는 것을 권장한다.
컨테이너 환경에서 환경변수는 매우 중요한 역할을 한다.
예를 들어 스프링부트 서버를 컨테이너로 올릴 때 DB 서버의 IP 주소나 포트와 같은 정보를 환경변수로 관리하면 유연하게 설정을 변경할 수 있기 때문이다.
이때, Dockerfile에서 ENV
명령어를 사용하면 환경 변수를 세팅할 수 있는데 우선 코드를 살펴보자
Sample Code
FROM ubuntu ENV WORKSPACE=/myapp RUN mkdir ${WORKSPACE} WORKDIR ${WORKSPACE} RUN touch ${WORKSPACE}/mytouchfile
Result View
컨테이너에서 WORKSPACE 환경변수를 확인해보면 /myapp으로 세팅된 모습을 볼 수 있다.
물론, 컨테이너 실행 시점에 환경변수를 변경할 수도 있다.
◉ 커맨드 - 환경 변수 오버라이드
docker container run -it -e WORKSPACE=/newapp env_test
-e
: 환경 변수를 세팅할 때 사용하는 옵션이다.
Result View
여기서 주의깊게 봐야할 부분은 환경변수는 /newapp
이지만 가상 터미널로 접속한 WORKSPACE는 /myapp
임을 확인할 수 있다.
즉, 이런 차이가 발생하는 이유는 디렉토리 구조는 이미지를 빌드하는 시점에 생성되지만, 환경변수는 컨테이너 실행 시점에 적용되기 때문이다.
따라서, 만약 컨테이너를 생성한 이후 어떠한 작업을 수행한다면 현재 세팅된 환경변수인 /newapp
에서 작업이 수행된다.
이러한 특징을 이해하고 사용하는 것이 중요할 것이다!
이번 포스팅에서는 Dockerfile의 주요 지시어들인 CMD
와 ENTRYPOINT
의 차이, exec form과 shell form의 특징, 그리고 COPY
/ ADD
와 ENV
의 활용법에 대해 자세히 알아보았다.
특히 각 지시어들이 언제 사용되어야 하는지, 그리고 왜 그렇게 사용해야 하는지에 대해 정리해봤다.
실제로 exec form을 사용하는 것이 권장되는 이유나, 환경변수가 실제로 적용되는 시점의 차이 등은 이해하는 것은 실제 개발환경에서 중요한 부분이 될 것 같으니 공부해두자 👊