Docker Image

도커는 기존에 pull 받은 Base 이미지를 변경하여 새로운 상태의 이미지를 만들고 배포할 수 있습니다. 새로운 상태의 이미지를 만드는 방법에는 docker commit을 이용한 방법과 Dockerfile & docker build 를 이용한 방법이 있습니다.

두 가지 방법으로 자바가 기본으로 설치되어있는 새로운 우분투 이미지를 만드는 과정을 알아봅니다.

docker commit

Image Pull

가장 latest 한 버전의 Ubuntu 이미지를 pull 받고 컨테이너를 생성 및 실행합니다. 설치한 이미지가 Base Image가 됩니다.

docker run --name test_new_image -it ubuntu:20.04 bash

다른 터미널에서 docker ps를 실행하면 방금 시작한 컨테이너를 확인할 수 있습니다.

Install Java

일반적인 ubuntu 이미지는 어떠한 바이너리도 설치되어있지 않기 때문에 Java 를 설치해야 합니다. 아래 코드를 통해 최신상태의 패키지들을 불러오고 Java 17 를 설치할 수 있습니다.

apt-get update && apt-get install -y openjdk-17-jdk

Java 를 설치를 확인합니다.

docker commit

docker commit 으로 새로운 이미지를 만들어줄 수 있습니다. docker commit 명령으로 새로운 상태의 이미지를 만듭니다. 아래 코드는 test_new_image 이름의 컨테이너를 ubuntu:java-17 이라는 Tag 로 커밋하여 새로운 이미지를 만드는 명령입니다.

docker commit test_new_image ubuntu:java-17

Confirm New Image

docker images 를 통해 로컬에 존재하는 images 들을 확인해보면 ubuntujava-17 Tag 가 달려있는 Image 가 생성된것을 확인할 수 있다.

Create New Container

생성한 새로운 이미지로 컨테이너를 생성하여 실행해보면, 별도의 설치없이 Java 가 설치되어 있는것을 확인할 수 있습니다.

docker run --name new_java_image -it ubuntu:java-17 bash

Conclusion

docker commit 을 통한 새로운 이미지 생성은 수동적인 방법입니다.

docker build

앞서 설명한 docker commit로 새로운 이미지를 만드는 방법은 직접 이미지를 pull 받고, 컨테이너 내부에서 직접 java 를 설치해주는 과정을 거치는 번거로운 과정을 거칩니다. 하지만 Dockerfiledocker build 를 사용하면 위 과정을 자동화하여 보다 쉽게 새로운 이미지를 만들 수 있습니다.

보다 정확히는 Dockerfile 파일에 새로운 이미지를 만들기 위해 필요한 과정 및 흐름(Flow)을 명시하고 docker build 로 Dockerfile을 실행하여 새로운 이미지를 자동으로 생성하게 하는 원리입니다.

Dockerfile

새로운 이미지를 만들기 위해서는 Dockerfile을 작성해야 합니다. 아래 코드는 docker commit 을 통한 새로운 이미지 생성 흐름을 Dockerfile에 명시한 것입니다.

FROM ubuntu:latest
 
RUN apt-get update
RUN apt-get install -y openjdk-17-jdk
  • FROM : Base Image. 즉 새로운 기능을 덧붙일 기반 이미지를 명시합니다. Dockerfile이 실행되면 기반이 되는 이미지가 설치되어있지 않아도 자동으로 해당 이미지를 pull 하고 컨테이너를 생성합니다.
  • RUN : 생성된 컨테이너에서 실행할 명령어를 명시합니다.

Dockerfile 에서 사용되는 명령어는 아래와 같습니다.

CommandDescription
FROM베이스가 되는 기본 이미지(Base Image)를 지정합니다.
RUN이미지 빌드 시 실행할 명령어를 작성합니다. 여러 개 작성할 수 있습니다.
CMD컨테이너 시작 시 실행할 명령어 또는 스크립트를 지정합니다. 여러 번 작성 가능하지만, 마지막 CMD만 실행됩니다.
컨테이너가 실행될 때마다 실행되며, ENTRYPOINT의 인자로도 사용됩니다.
ENTRYPOINT컨테이너 시작 시 한 번만 실행되는 명령어 또는 스크립트를 지정합니다.
EXPOSE컨테이너에서 외부로 노출할 포트를 지정합니다.
ENVDockerfile 내에서 사용할 환경 변수를 설정합니다.
ADD파일 또는 디렉터리를 이미지에 추가합니다. URL이나 압축 파일도 지원합니다.
COPY로컬 파일이나 디렉터리를 이미지 내 특정 경로로 복사합니다.
VOLUME외부 저장소(호스트)와 연결되는 마운트 포인트를 생성하여, 컨테이너 내부 데이터가 호스트에 저장되도록 합니다.
USERRUN, CMD, ENTRYPOINT 명령을 실행할 사용자를 지정합니다.
WORKDIR작업 디렉터리를 설정합니다. 여러 번 쓰면 이전 WORKDIR를 기준으로 상대 경로로 이동합니다.
ARG빌드 시 사용할 빌드 타임 변수(환경 변수)를 설정합니다.
LABEL이미지에 메타데이터(key-value 형태)를 추가합니다.
ONBUILD다른 이미지가 이 이미지를 베이스로 빌드할 때 실행될 명령어를 지정합니다.
MAINTAINER이미지를 만든 사람의 정보를 기입합니다. (현재는 LABEL로 대체 권장)

RUN, CMD, ENTRYPOINT는 모두 Dockerfile에서 명령어를 지정할 때 사용하지만, 역할과 실행 시점이 다릅니다.

  • CMD

    • Dockerfile 내에서 여러 번 선언할 수 있으나, 오직 마지막에 선언된 CMD만 컨테이너 실행 시 사용됩니다.
    • 컨테이너가 시작될 때 기본 실행 명령을 지정하며, 컨테이너 실행 시 다른 명령어로 덮어쓸 수 있습니다.
    • 즉, CMD는 컨테이너가 실행될 때 실행되는 기본값이며, 필요에 따라 변경 가능합니다.
  • RUN

    • Docker 이미지 빌드 과정에서 실행되는 명령어를 지정합니다.
    • 여러 번 사용할 수 있으며, 각 RUN 명령어는 이미지에 새로운 레이어로 저장됩니다.
    • 컨테이너가 실행될 때가 아니라 이미지가 만들어질 때 실행되는 점이 CMD와 가장 큰 차이입니다.
    • RUN은 이미지를 빌드할 때 실행되며 CMD는 컨테이너가 시작될 때 실행됩니다.
  • ENTRYPOINT

    • Dockerfile 내에서 한 번만 선언하며, 컨테이너가 실행될 때 항상 실행되는 명령어 또는 스크립트를 지정합니다.
    • CMD와 함께 사용할 경우, CMD는 ENTRYPOINT 명령어에 전달되는 기본 인자(arguments) 역할을 합니다.
    • ENTRYPOINT는 컨테이너가 어떤 실행 파일처럼 동작하게 만들고 싶을 때 주로 사용합니다.
    • 컨테이너 실행 시 명령어를 덮어쓰더라도 ENTRYPOINT는 유지되며, CMD 또는 명령줄 인자가 ENTRYPOINT에 전달됩니다.

docker build

docker buildDockerfile 을 실행하여 새로운 이미지를 생성합니다. 사용되는 옵션은 아래와 같습니다.

OptionDescription
-f다른 파일명을 가진 Dockerfile을 사용해야 할 때 사용합니다.
-t도커 Image 이름을 지정합니다. {네임스페이스}/{이미지이름}:{태그} 포맷입니다.

Build Context

docker build의 기본 Syntax 는 아래와 같습니다. -t 옵션으로 생성할 이미지명과 태그를 명시해주고 빌드 컨텍스트를 명시해주면 됩니다. 빌드 컨텍스트는 이미지를 생성하는 데 필요한 각종 파일, 소스코드, 메타데이터 등을 담고 있는 디렉터리를 의미합니다. 한마디로 이미지를 빌드할 기준 디렉터리를 명시하면 됩니다.

docker build -t {생성할_이미지명:이미지태그} {빌드 컨텍스트}

Note

Build Context가 Dockerfile의 위치를 말하는것이 절대 아닙니다.

도커는 기본적으로 이미지를 빌드할 때 빌드 컨텍스트 경로에서 Dockerfile 이라는 이름을 갖는 파일을 찾아 실행합니다. 덕분에 빌드 컨텍스트 경로에 Dockerfile 이 존재한다면 따로 Dockerfile의 경로를 명시할 필요가 없습니다.

docker build -t test:v1 .

빌드 컨텍스트 경로와 Dockerfile이 위치하는 달라야할 경우가 있습니다. 이 경우 -f 옵션으로 Dockerfile 이 있는 위치를 지정해야 합니다. 아래의 명령을 해석하면 Docker Build를 수행할 때 현재경로의 ./docker/Dockerfile 을 실행하되, 이미지가 빌드되는 폴더는 현재 위치 로 하겠다는 의미입니다.

docker build -f ./docker/Dockerfile -t test:v1 .

본론으로 돌아와 docker build로 새로운 이미지를 생성합니다. 아래의 코드는 새롭게 생성될 이미지와 태그를ubuntu:java-17-2로 지정하고 빌드 컨텍스트는 현재 위치 지정하겠다는 의미입니다. Dockerfile의 경로를 지정하는 -f 옵션을 사용하지 않았기 때문에, docker build 시 자동으로 빌드 컨텍스트의 경로인 현재 위치 (.) 에서 Dockerfile을 찾아 이미지 빌드를 시작합니다.

docker build -t ubuntu:java-17-2 .

이미지 빌드가 시작되어 나타나는 콘솔들이 끝날 때까지 기다리면 됩니다.

Confirm New Image

docker images 를 통해 새롭게 생성된 이미지를 확인해보면 정상적으로 ubuntu:java-17-2 이름의 이미지가 생긴 것을 확인할 수 있습니다.

Create New Container

새로운 이미지로 컨테이너를 생성 및 실행하면 기본으로 java17가 설치되어있는 것을 확인할 수 있습니다.

Conclusion

  1. docker commit을 사용한 이미지 생성은 수동적이고 번거로운 과정입니다.
  2. 반면, Dockerfiledocker build를 활용하면 이 과정을 자동화할 수 있어 훨씬 쉽고 편리하게 이미지를 만들 수 있습니다.
  3. 또한, Build Context는 단순히 Dockerfile이 위치한 경로가 아니라, 빌드가 실행되는 기준이 되는 디렉터리를 의미합니다.

docker build 시 캐시 활용이나 이미지 크기 최적화 기능도 있지만, 여기서는 생략합니다.