본문 바로가기
도커(Docker)

[Docker] Dockerfile 작성하는 방법

by codeyaki 2023. 6. 29.
반응형

도커파일 작성 방법에 대해 학습하기 위해서 공식문서를 참고하여 학습하는 시간을 가져보았다.

https://docs.docker.com/engine/reference/builder/

 

Dockerfile reference

 

docs.docker.com

도커파일(DockerFile)이란?

도커의 이미지를 생성할 때 수행할 코드들을 작성해 두는 텍스트 파일이다.

즉, 템플릿 같은 역할을 하게 된다.

예로 java 17 버전을 이용한 spring boot jar 파일을 실행시킬 이미지를 만드는 dockerfile이다.

FROM openjdk:17
ARG JAR_FILE=./build/libs/*.jar
COPY ${JAR_FILE} /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

사용 방법

Dockerfile은 다음 사항들을 지켜줘야 한다.

  • 대소문자를 가리지는 않지만 보통 인수와 구별하기 위해서 지시어는 대문자로 작성해 주는 편이다.
  • Dockerfile은 위에서부터 실행된다.
  • Dockerfile은 반드시 from부터 시작해야 한다.

주석 (#)

Dockerfile을 작성할 때 주석이 필요한 경우 #을 사용하면 된다.

# Comment
INSTRUCTION arguments

ENV

ENV를 사용해서 이미지 내에 환경 변수로 선언하면 특정 지시어들에서 변수로 사용할 수 있다.

선언할 때에는 key = value 형태로 선언하게 된다.

후에 지시어에서 사용할 때에는 ${변수이름} 혹은 $변수이름으로 작성할 수 있다.

또한 실행할 때 docker run --env <key>=<value>.와 같은 형태로 env를 변경해 줄 수 있다.

기존 이미지에 선언된 환경 변수는 모두 상속되기 때문에 남용하지 않도록 주의해야 한다. (linux와 같은 운영체제의 기본 환경 변수 포함)

따라서 빌드 시에만 사용하는 경우 후에 기술한 ARG를 사용하는 것이 바람직하다.

예시)

FROM busybox
ENV FOO=/bar
WORKDIR ${FOO}   # WORKDIR /bar
ADD . $FOO       # ADD . /bar
COPY \$FOO /quux # COPY $FOO /quux

ENV가 지원하는 지시어는 다음과 같다

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR
  • ONBUILD (다른 지시어와 결합된 경우)

FROM

FROM은 아래 세 가지 형식 중 한 가지로 사용할 수 있다.

FROM [--platform=<platform>] <image> [AS <name>]
# or
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
# or
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

FROM을 통해서 기본 이미지를 설정할 수 있다. 또한 Dockerfile을 생성할 때 반드시 FROM으로 시작해야 한다.

docker hub를 통해서 공개된 저장소중 하나를 선택하여 가져올 수 있다.

https://hub.docker.com/search?q=

 

Docker

 

hub.docker.com

이와 같은 뱃지가 달려있는 저장소는 도커에서 인증하는 공식 이미지이므로 안심하고 사용할 수 있다.

FROM에서 숙지해야 하는 사항은 다음과 같다.

  • ARG는 유일하게 FROM보다 먼저 사용할 수 있는 지시어이다! (FROM보다 윗줄에 올 수 있다는 뜻)
  • FROM 하나의 Dockerfile에 여러 번 사용할 수 있다. 이 경우 여러 개의 이미지를 생성하거나 앞서서 사용한 FROM을 통해서 생성한 이미지를 바로 다음 이미지의 종속성으로 사용할 수 있다. FROM을 사용하면 이전에 작업했던 내용을 전부 제거한다. 따라서 각 작업별 ID를 따로 기록해두어야 한다.
  • AS name을 사용하여 새 빌드 단계에 이름을 지정할 수 있다. 또한 이렇게 지정한 이름은 후에 COPY --from=을 통해서 이전 단계에서 빌드 이미지를 참조할 수 있다.
  • 태그를 따로 작성하지 않는다면 latest를 기본적으로 사용한다. (가장 최신 버전)
  • --platform을 통해서 바탕이 될 이미지의 플랫폼을 지정할 수 있다. (linux/amd64, linux/arm64, windows/amd64 등)

RUN

RUN은 현재 이미지 위에 있는 새 레이어에서 모든 명령을 실행하는 지시어이다.
쉽게 말해 빌드 시에 이미지 내에서 터미널을 조작하는 지시어라고 생각하면 된다.
RUM 지시어는 두 가지 방식이 존재한다.

RUN <command>( 쉘 방식, 명령은 쉘에서 실행되며 기본적으로 /bin/sh -c (Linux) 또는 cmd /S /C (Windows)으로 실행된다)
# or
RUN ["executable", "param1", "param2"]( exec 방식 )
  • exec 방식은 JSON 형태로 파싱 되기 때문에 반드시 큰 따옴표("")를 사용해야 한다.
  • exec 방식은 쉘을 호출하지 않는다. 따라서 $HOME 같은 쉘에서 처리되는 것들을 지원하지 않는다. 만약 원한다면 쉘 실행도 지시해주어야 한다.
    ex) RUN [ "echo", "$HOME" ]
    -> RUN [ "sh", "-c", "echo $HOME" ]
  • window 환경과 같은 경로 기호가 백슬래시인 경우 JSON으로 파싱 하는 과정에서 오류를 발생할 수 있으므로 이스케이프 처리를 해주어야 한다.
    ex) RUN ["c:\windows\system32\tasklist.exe"]
    -> RUN ["c:\\windows\\system32\\tasklist.exe"]

CMD

Dockerfile에는 한 개의 CMD만 존재할 수 있다. 만약 여러 개의 CMD가 온다면 마지막에 작성한 CMD만 적용이 된다!
CMD를 사용하는 주된 목적은 실행 중인 컨테이너에서 기본 동작을 설정하기 위함이다. 실행 파일을 포함하거나 생략할 수 있는데 이경우에는 ENTRYPOINT도작성해야 한다.

CMD지시어에는 세 가지 방식이 존재한다.

CMD ["executable","param1","param2"]( exec 형식, 이것이 선호되는 형식 )
# or
CMD ["param1","param2"]( ENTRYPOINT에 대한 기본 매개변수 )
# or
CMD command param1 param2( 쉘 형식 )

Do not confuse RUN with CMD.
RUN actually runs a command and commits the result;
CMD does not execute anything at build time, but specifies the intended command for the image.

RUN과 CMD의 차이를 잘 인지해야 한다는 공식문서의 설명이다.
RUN은 빌드하는 동안 실행할 명령이고 CMD는 컨테이너를 실행할 때 동작시킬 지시어이다.

LABEL

라벨은 생성될 이미지에 대한 메타데이터를 생성하는 지시어이다.

LABEL <key>=<value> <key>=<value> <key>=<value> ...

예시)

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
  • 값 내에서 공백을 추가하려면 \을 사용하면 된다.

EXPOSE

EXPOSE는 컨테이너를 실행할 때 포트를 지정하는 지시어이다. 프로토콜 또한 지정할 수 있는데 만약 지정하지 않는다면 tcp가 기본값이다.

EXPOSE는 실제로는 포트가 지정되지 않는다. 이미지를 빌드하는 사람이 컨테이너를 실행하는 사람에게 알려주기 위한 일종의 문서 역할을 한다.

컨테이너를 실행할 때 실제로 포트를 지정하려면 -p 옵션을 사용하여 매핑해주어야 한다.

예시) 80 포트의 udp/tcp 연결

EXPOSE 80/tcp
EXPOSE 80/udp

으로 작성을 한 뒤에 컨테이너로 실행할 때

docker run -p 80:80/tcp -p 80:80/udp ...

으로 사용하면 된다.

만약 -p를 사용하지 않는다면 현재 사용할 수 있는 포트번호 중 랜덤으로 80 포트(내가 지정해 둔 포트)와 매핑이 되게 된다.

참고)https://soft.plusblog.co.kr/139

 

[Docker] docker run -p 옵션과 Dockerfile의 EXPOSE 차이점

도커의 네트워크 관련된 설정 중에 포트와 관련된 설정이 있습니다. Docker를 실행할 때 -p 옵션으로 포트 포워딩을 해주는 것과 Dockerfile에서 EXPOSE를 통해 포트를 노출시키는 것이 어떻게 다른지

soft.plusblog.co.kr

ADD

ADD는 로컬 파일이나 디렉터리 또는 URL에서 이미지 내의 경로에 복사하는 지시어이다.

COPY와 비슷한 역할을 수행하지만 ADD는 URL에서 복사할 수 있는 기능과 로컬파일이 알집 파일인 경우 자동으로 해제하여 복사해 주게 된다.

ADD는 다음 두 가지 형식으로 사용한다.

ADD [--chown=<user>:<group>] [--chmod=<perms>] [--checksum=<checksum>] <src>... <dest>
# or
ADD [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]

두 번째 방식은 경로에 공백을 포함하는 경우 사용한다.

와일드카드를 사용할 수 있다.

예시로 hom으로 시작하는 모든 파일을 /mydir에 추가한다.

ADD hom* /mydir/

만약 Git 저장소에서 추가하는 경우 keep-git-dir 설정을 true 해주면 된다.

ADD [--keep-git-dir=<boolean>] <git ref> <dir>

예를 들어 buildkit의 github 저장소를 가져오는 경우

# syntax=docker/dockerfile:1-labs
FROM alpine
ADD --keep-git-dir=true https://github.com/moby/buildkit.git#v0.10.1 /buildkit

혹시나 저장소가 private인 경우에는 다음과 같이 해주면 된다.

# syntax=docker/dockerfile:1-labs
FROM alpine
ADD git@git.example.com:foo/bar.git /bar

그 뒤 빌드할 때 아래와 같이 빌드해 주면 된다.

docker build --ssh default

COPY

ADD와 비슷한 기능을 수행한다. 하지만 차이점이라면 COPY는 현재 로컬파일을 이미지 내에 단순히 복사하는 기능만을 수행한다.

COPY [--chown=<user>:<group>] [--chmod=<perms>] <src>... <dest>
# or
COPY [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]
  • chown과 chmod는 지원되는 플랫폼에서만 가능하다. (window 불가)
  • 와일드카드 사용 가능 ( : 모든 문자, ?: 한 글자 )
    예를 들어 hom
    .txt로 작성하면 home.txt, homefr.txt 등 hom으로 시작하고 .txt로끝나는 모든 파일을 추가한다. 
    만약 hom?.txt로 작성하면 home.txt와 같이 hom으로 시작하고 아무 한 글자만 포함되고 .txt로 끝나는 모든 파일들을 추가한다.
  • 목적 경로(dest)에는 절대경로 혹은 상대 경로를 사용할 수 있는데 이때 상대 경로는 WORKDIR을 기준으로 사용한다.
    소스 경로(src)의 상대 경로는 dockerfile의 위치 기준이다.

COPY(ADD) --link 옵션이 있는데 이 옵션은 추가할 파일이 자체적인 레이어에서 독립적으로 유지하여 이전 계층의 명령이 변경되어도 동작할 수 있도록 사용하는 옵션이다. 즉 별도의 레이어를 가지게 된다.
만약

# syntax=docker/dockerfile:1
FROM alpine
COPY --link /foo /bar

이 파일은 두 빌드를 수행하는 것과 같게 된다.

FROM alpine

FROM scratch
COPY /foo /bar

이렇게 생성되는 두 이미지를 병합하여 이미지를 빌드하게 된다.
이 옵션을 사용했을 때 생기는 장점으로는 이미 빌드된 레이어를 재사용할 수 있다는 점이다. --cache-from을 사용하는 다중 단계 빌드에서 이전 명령이 변경되면 다시 빌드해야 하는데 --link를 사용하면 이를 막을 수 있다.
자세한 내용은 아래 링크를 통해서 확인할 수 있다.
https://docs.docker.com/engine/reference/builder/#benefits-of-using---link

 

Dockerfile reference

 

docs.docker.com

ENTRYPOINT

엔트리포인트를 통해서 컨테이너가 실행될 때 동작시킬 명령을 설정할 수 있다.

ENTRYPOINT ["executable", "param1", "param2"]
# or
ENTRYPOINT command param1 param2

예시

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

CMD와 ENTRYPOINT 상호 동작 이해하기
CMD와 ENTRYPOINT는 컨테이너가 실행될 때 동작할 명령을 정의하는 명령어이다.
Dockerfile은 최소 한 개 이상의 CMD 혹은 ENTRYPOINT를 명시해야 한다.
컨테이너를 실행파일로 사용할 때에는 ENRTYPOINT를 사용한다.
CMD는 Entrypoint 명령에 대한 기본 인수를 정의하거나 컨테이너에서 애드혹 명령을 실행하는 방법으로 사용해야 한다.
대체 인수를 사용하여 컨테이너를 실행하면 CMD가 재정의됩니다.

아래 테이블은 CMD + ENTRYPOINT 조합했을 때 동작하는 방식입니다.

VOLUME

컨테이너가 사라지면 그 안에 데이터도 함께 사라지게 된다. 따라서 이것을 방지하기 위해서 컨테이너 외부에서 관리할 수 있는 볼륨을 설정하도록 할 수 있다.
또한 해당 볼륨은 다른 컨테이너와 서로 공유할 수 있다.

볼륨으로 사용할 경로를 지정해주기만 하면 된다.

VOLUME ["/data"]

USER

OS의 계정을 변경하는 명령을 수행한다. 따라서 해당 계정이 실제로 존재해야 한다.
그래서 동작전에 useradd [사용자명]을 통해서 계정을 생성해 줄 수 있다.
예시

FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick
  • 해당 컨테이너를 실행할 때 patrick 계정으로 실행하게 된다.

WORKDIR

마치 터미널에서 cd 하는 것과 같이 작업할 베이스 경로를 변경하는 것이다.
사용 방법은 아래와 같다.

WORKDIR /path/to/workdir

사용 예시로

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

를 수행하면 /a/b/c를 확인할 수 있다.
또한 ENV를 통해 환경 변수로 지정한 뒤 WORKDIR에서 사용할 수 있다.

ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

ARG

ARG는 빌드 과정에서 사용할 변수를 정의할 수 있다. 이는 빌드 내부에서만 사용될 변수이다.
빌드 시에 docker build--build-arg <varname>=<value>의 형태로 지정할 수 도 있다. (이때 내부에 해당 변수가 선언된 적이 없는 경우 경고가 발생한다.)

또한 여러 ARG를 사용할 수 있다.

FROM busybox
ARG user1
ARG buildno
# ...

ARG는 기본 값을 사용할 수 있다.

FROM busybox
ARG user1=someuser
ARG buildno=1
# ...

ARG를 사용하기 위해서는 미리 선언해주어야 한다.

FROM busybox
USER ${username:-some_user}
ARG username
USER $username
# ...

해당 dockerfile로 docker build --build-arg username=what_user .를 빌드한다면 아래와 같은 결과를 얻는다.

  • 2번째 라인에서의 username은 선언되어 있지 않기 때문에 빈문자열로 반환되기 때문에 USER는 some_user가 된다.
  • 4번째 라인에서의 username은 선언되어 있기 때문에 USER는 what_user가 된다.

참고
${variable:-word}은(는) 변수가 설정된 경우 결과가 해당 값이 된다는 것을 나타냅니다. 변수가 설정되지 않은 경우 단어가 결과가 됩니다.
${variable:+word}은(는) 변수가 설정되면 단어가 결과가 되고, 그렇지 않으면 빈 문자열이 된다는 것을 나타냅니다.

ARG의 범위는 하나의 레이어이다. 따라서 다른 레이어에서는 사용할 수 없기 때문에 다시 지정해주어야 한다.

FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS

FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS

사용은 ENV와 마찬가지로 $를 붙여주면 된다.
만약 ENV와 동일한 변수명을 사용할 경우 덮어쓰기가 되기 때문에 주의해야 한다.


요약해 보자면 

# 주석
ENV 환경 변수 선언 (컨테이너에서도 유지)
ARG 변수 선언 (빌드시에만 유지)
FROM 베이스가 될 이미지 선택
LABEL 이미지에 대한 메타데이터 선언
RUN 빌드시 동작시킬 명령
EXPOSE 매핑 시켜야할 포트 명시 (문서 기능)
CMD 컨테이너가 실행할때 동작할 명령
(ENTRYPOINT와 조합하여 사용 가능)
ENTRYPOINT 컨테이너가 실행할때 동작할 명령
(CMD와 조합하여 사용 가능)
ADD 로컬파일 혹은 URL에서 이미지내 경로에 파일 복사(알집 자동 풀어줌)
COPY 로컬파일을 이미지내 경로에 파일 복사 (그대로 복사)
VOLUME 컨테이너 외부와 연결할 디렉터리 선언
USER 사용자 계정 변경
WORKDIR 작업 디렉터리 이동 ( cd와 같은 역할 )

작성한 내용들만 숙지하여도 기본적인 Dockerfile의 작성에는 문제가 없어 보인다. 추가적으로 알아보고 싶다면 아래 공식 문서에 접속하여 살펴보면 내가 작성하지 않은 좀 더 많은 명령어와 사용방법을 알 수 있다.
https://docs.docker.com/engine/reference/builder/

 

Dockerfile reference

 

docs.docker.com

 

반응형

'도커(Docker)' 카테고리의 다른 글

컨테이너, Container  (0) 2023.06.10