Dockerfile은 DockerImage를 생성하기 위한 레시피(텍스트 파일)이다. 이미지를 구성하는데 필요한 모든 명령어와 설정이 포함됩니다.
이 문서는 이미지 빌더에게 실행할 명령어, 복사할 파일, 시작 명령어 등을 지시합니다.
Dockerfile을 작성한 후 빌드하면 Docker는 Dockerfile에 나열된 명령문을 차례대로 수행하며 DockerImage를 생성해준다.
Dockerfile을 읽을 줄 안다는 것은 해당 이미지가 어떻게 구성되어 있는지 알 수 있다는 의미이다.
Docker 이미지는 읽기 전용 계층(layer)들로 구성됩니다. 각 계층은 Dockerfile의 한 명령어를 대표하고, 이 계층들은 쌓여서 이미지를 형성합니다.
예를 들어, 위의 Dockerfile에서 각 명령어는 다음과 같은 계층을 생성합니다:
FROM ubuntu:22.04 # ubuntu:22.04 Docker 이미지에서 새 계층을 생성합니다.
COPY . /app # Docker 클라이언트의 현재 디렉토리에서 파일을 추가합니다.
RUN make /app # make 명령어를 사용하여 애플리케이션을 빌드합니다.
CMD python /app/app.py # 컨테이너 내에서 실행할 명령어를 지정합니다.
이미지를 실행하여 컨테이너를 생성할 때는 기존 계층들 위에 새로운 쓰기 가능 계층, 즉 컨테이너 계층을 추가합니다. 실행 중인 컨테이너에서 파일을 새로 작성하거나 기존 파일을 수정하고 삭제하는 모든 변경 사항은 이 쓰기 가능한 컨테이너 계층에 기록됩니다.
Dockerfile에는 확장자가 따로 없습니다.
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "./src/index.js"]
위 Dockerfile은 동작하긴 하지만, 좀 더 최적화가 가능합니다.
docker init
명령어로 Dockerfile, compose.yaml, .dockerignore를 한 번에 생성할 수도 있습니다.
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
FROM 명령어는 새로운 빌드 단계를 시작하기 위한 명령어이며, 이후 명령어를 위한 기반 이미지를 설정한다. 즉, Docker 이미지 생성의 시작점(어느 이미지로 시작할 지).
FROM 명령어는 이전 명령어에 의해 생성된 모든 상태를 초기화 한다.
FROM 명령어를 하나의 도커 파일 안에 여러 번 사용하여, 여러 이미지를 생성하거나 하나의 빌드 단계를 다른 단계의 종속성으로 사용할 수 있다.
FROM 명령어 뒤에 AS 이름
을 붙여 빌드 단계에 이름을 부여할 수 있다. 이후 빌드 단계에서 이 이미지를 참조할 수 있다.
FROM <name>
COPY --from=<name>
RUN --mount=type=bind,from=<name>
태그나 다이제스트 값은 선택사항이며, 생략하는 경우 latest를 가정한다.
--platform 플래그를 이용해서 이미지의 플랫폼을 지정하는 데 사용할 수 있다.
https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope
FROM
은 기반 이미지를 설정하기 위해 작성한다.AS
를 사용하여 빌드 단계의 이름을 지정할 수 있다. 이후 빌드 단계에서 해당 이미지를 참조할 수 있다.FROM
은 이전 명령어에 의해 생성된 모든 상태를 리셋한다.ENV <키>=<값> ...
ENV MY_NAME="John Doe" ENV MY_DOG=Rex\ The\ Dog ENV MY_CAT=fluffy
ENV를 사용하여 설정된 환경 변수는 이미지 빌드 타임에도 사용되며, 결과 이미지에서 컨테이너를 실행할 때에도 유지된다.(해당 컨테이너에서 돌아가는 애플리케이션도 접근할 수 있습니다.).
docker inspect를 사용하여 값을 확인하고, docker run --env <키>=<값>을 사용하여 변경할 수 있다.
환경 변수가 빌드 중에만 필요하고 최종 이미지에서는 필요하지 않은 경우, 최종 이미지에 유지되지 않는 ARG를 사용하는 것이 좋다.
ARG <name>[=<default value>]
ARG 명령어는 FROM 명령어 이전에 올 수 있는 유일한 명령어다.
--build-arg <varname>=<value>
플래그를 사용하여 전달한다.
FROM busybox ARG user1 ARG buildno # ...
🚨credentails나 api key 같은 secret 정보를 전달하기 위해 빌드 인수를 사용하는 것은 권장하지 않는다.
docker history
명령어를 통해서 빌드 인수를 볼 수 있기 때문이다.
https://docs.docker.com/reference/dockerfile/#run---mounttypesecret
ex)
FROM busybox ARG user1=someuser ARG buildno=1 # ...
FROM busybox USER ${username:-mingyo} ARG username USER $username # ...
docker build --build-arg username=what_user .
🚨${username:-some_user}는 linux 쉘에서 사용하는 환경변수 대체 문법이다.
username 변수가 설정되지 않았거나, 빈 문자열인 경우 오른쪽에 지정된 기본값(문자열)mingyo
를 사용한다.
두번째 줄 username은 mingyo로 평가되고, 4번째 줄 username은 what_user로 평가된다.
변수 정의 이전에 변수를 사용하면 빈 문자열을 반환한다.
FROM busybox ARG SETTINGS RUN ./run/setup $SETTINGS #### FROM busybox ARG SETTINGS RUN ./run/other $SETTINGS
FROM ubuntu ARG CONT_IMG_VER ENV CONT_IMG_VER=${CONT_IMG_VER:-v1.0.0} RUN echo $CONT_IMG_VER
docker build --build-arg CONT_IMG_VER=v2.0.1 .
여기서 RUN 명령어의 CONT_IMG_VER
는 v1.0.0을 사용한다.
buildkit을 사용하는 경우에만 사용할 수 있는 기능
buildkit 알아볼 예정
ARG
명령어는 빌드 타임에 사용할 수 있는 변수를 정의한다.ARG
명령어와 함께 기본값을 지정할 수 있다.ARG
는 FROM
전에 나오는 것이 가능하다.ARG
는 한 빌드 단계에서만 유효하며, 다음 빌드 단계에서는 리셋되므로 다시 정의해야 한다.ENV
명령어가 ARG
명령어보다 우선한다.WORKDIR은 Dockerfile에서 여러 번 사용될 수 있으며, 상대 경로로 작성하면 이전 WORKDIR 명령어에 적은 경로를 기준으로 상대적으로 지정된다.
WORKDIR /a WORKDIR b WORKDIR c RUN pwd
pwd의 결과는 /a/b/c 이다.
WORKDIR을 지정하지 않는 경우(FROM scratch
일 때)는 /(루트 디렉토리)가 작업 경로이며, 그게 아닌 경우 base 이미지를 사용하여 작업 경로가 지정된다.
따라서 WORKDIR
을 지정하는 것을 권장한다.
WORKDIR
은 RUN, CMD, ENTRYPOINT, COPY, ADD
명령어가 동작할 working directory를 지정한다.WORKDIR
를 지정하지 않는 경우, 루트 디렉토리 혹은 base 이미지 기반 WORKDIR
로 지정된다. 따라서 WORKDIR
를 꼭 지정하는 것이 좋다.COPY [OPTIONS] <src> ... <dest>
COPY [OPTIONS] ["<src>", ... "<dest>"]
COPY
명령어는 <src>
(소스 경로)에서 호스트의 파일 혹은 디렉토리를 복사하고 컨테이너의 파일 시스템의 <dest>
(목적지 경로) 경로에 추가한다.
<src>
경로는 빌드 컨텍스트를 기준으로 해석된다.여러 개의 <src>
리소스를 지정할 수 있으나, 파일과 디렉토리의 경로는 빌드 컨텍스트의 소스를 기준으로 상대적으로 해석됩니다.
🚨빌드 컨텍스트
docker build -t myimage .
빌드를 실행할 때 넘겨주는 경로, 보통은 현재 디렉토리를 빌드 컨텍스트로 사용하도록 한다.
<src>
는 와일드 카드를 포함할 수 있고, Go의 파일 매칭 규칙을 사용한다.COPY hom* /mydir/ COPY hom?.txt /mydir/
<dest>
는 절대 경로이거나, 목적지 컨테이너 내에서 소스가 복사될 WORKDIR에 상대적인 경로다.COPY test.txt relativeDir/
상대 경로를 사용하며 "test.txt"를
<WORKDIR>
/relativeDir/에 추가한다.COPY test.txt /absoluteDir/
절대 경로를 사용하며 "test.txt"를 /absoluteDir/에 추가한다.
디렉토리 자체는 복사되지 않고, 그 내용만 복사된다.
<dest>
가 존재하지 않는 경우, 해당 경로를 생성 후 복사한다.
<dest>
가 슬래시(/)로 끝나면 디렉토리로 간주된다.
<src>
가 파일이고 <dest>
가 슬래시로 끝나지 않으면, <src>
의 내용이 <dest>
파일로 기록됩니다.
기본적으로 COPY 명령어는 빌드 컨텍스트에서 파일을 복사한다.
COPY [--from=<image|stage|context>] <src> ... <dest>
# syntax=docker/dockerfile:1 FROM alpine AS build COPY . . RUN apk add clang RUN clang -o /hello hello.c # FROM scratch COPY --from=build /hello /
빌드 스테이지의 파일을 복사
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
Nginx 공식 이미지의 nginx.conf를 복사.
COPY --from의 소스 경로는 항상 지정한 이미지 또는 스테이지의 파일시스템 루트에서부터 해석된다.
컨테이너가 실행될 때 노출될 포트를 지정합니다.
컨테이너가 어떤 네트워크 포트를 통해 통신할 수 있는지 EXPOSE
명령어를 통해 지정한다.
EXPOSE <포트>
EXPOSE <포트>/<프로토콜>
프로토콜을 지정하지 않으면 기본적으로 TCP 통신을 한다.
TCP와 UDP를 모두 사용하려면 둘 다 적어줘야 한다.
EXPOSE 80/tcp
EXPOSE 80/udp
EXPOSE
명령어는 실제로 포트를 오픈하는 것이 아니다(EXPOSE
명령문으로 지정된 포트는 해당 컨테이너의 내부에서만 유효).
Dockerfile에 EXPOSE
명령어를 통해 어떤 포트를 오픈할 것인지를 알려주는 문서와 역할을 한다.
포트를 실제로 오픈하려면 docker run
명령어에 -p
플래그를 사용하여 하나 이상의 포트를 공개 및 매핑하여 호스트 컴퓨터의 특정 포트를 포워딩해야 한다.
RUN 명령어는 현재 이미지 위에 새로운 레이어를 생성하기 위해 명령어를 실행한다. 추가된 레이어는 Dockerfile의 다음 단계에서 사용된다.
RUN [OPTIONS] <command> ...
RUN <<EOF apt-get update apt-get install -y curl EOF
줄바꿈 이스케이프 또는 heredocs를 사용
CMD
명령어는 컨테이너가 실행될 때 기본으로 실행할 명령을 설정한다.
CMD ["executable","param1","param2"]
CMD
는 특정 빌드 단계에서 한 번씩만 사용할 수 있다.
여러 개의 CMD를 명시할 경우, 마지막으로 명시된 CMD를 사용한다.
docker run을 사용하여 컨테이너를 시작할 때 덮어쓸 수 있습니다. 즉, 사용자가 컨테이너를 시작하면서 추가적인 인수를 제공하면 CMD에 설정된 기본 명령이 대체됩니다.
CMD ["python", "default_script.py"]
사용자가 docker run myimage python another_script.py를 실행하면 default_script.py 대신 another_script.py가 실행됩니다.
컨테이너가 실행될 때 반드시 실행되어야 하는 명령을 설정합니다.
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT는 CMD와 다르게 사용자가 docker run을 통해 전달한 추가 인수에 의해 대체되지 않고, 추가 인수들이 ENTRYPOINT에 정의된 명령 뒤에 이어집니다.
ENTRYPOINT ["nginx", "-g", "daemon off;"]
사용자가 docker run myimage -c custom.conf를 실행하면, Nginx는 daemon off; 옵션과 함께 사용자가 제공한 커스텀 설정 파일 custom.conf를 사용하여 실행됩니다
CMD와 ENTRYPOINT는 모두 컨테이너에서 실행할 명령을 설정하는 데 사용되는데, 이 둘을 조금 다르게 사용합니다.
CMD는 ENTRYPOINT와 함께 사용되어, ENTRYPOINT에 의해 실행될 프로그램에 추가적인 기본 옵션이나 인수를 제공합니다.
하지만 CMD는 docker run을 사용할 때 지정된 다른 인수에 의해 대체될 수 있습니다.
#ENTRYPOINT에 웹 서버를 시작하는 명령을 설정합니다. ENTRYPOINT ["nginx"] #CMD에는 웹 서버에 대한 기본 설정 파일 경로나 옵션을 넣습니다. CMD ["-g", "daemon off;"]
이렇게 설정하면 컨테이너를 시작할 때 항상 nginx -g daemon off; 명령이 실행되어 웹 서버가 시작됩니다.
그런데 만약 컨테이너를 시작할 때
docker run myimage nginx -c custom.conf
위와 같이 다른 설정 파일을 사용하라는 명령을 내린다면, CMD에 있던 -g daemon off;는 사용되지 않고, 대신 custom.conf 설정 파일을 사용하여 nginx가 실행됩니다.
다른 예로,
ENTRYPOINT ["node"] CMD ["index.js"]
node index.js
실행$ docker run test
node main.js
실행$ docker run test main.js
이렇게 ENTRYPOINT와 CMD는 함께 작동하여 컨테이너에서 실행할 명령을 유연하게 설정할 수 있게 해줍니다.
이 정도 명령어만 알면 Dockerfile을 작성하고 이해하는데 큰 무리가 없다.
나머지는 https://docs.docker.com/reference/dockerfile/ 여기서 찾아보자