Dockerfile 작성 방법 (1/2)

seheon99·2020년 9월 13일
0

Dockerfile

목록 보기
1/2

Dockerfile reference

Docker는 Dockerfile 의 구성 요소를 읽으며 자동으로 이미지를 빌드할 수 있다. Dockerfile 은 이미지를 어셈블하기 위해 유저가 커맨드 라인에 입력하는 모든 명령들을 담은 텍스트 문서다. docker build 를 사용하므로써 유저들은 여러개의 커맨드라인 명령어들을 성공적으로 자동으로 실행할 수 있는 빌드를 만들 수 있다.

이 문서는 Dockerfile 안에서 사용할 수 있는 명령어들을 설명한다. 이 문서를 다 읽고난 후 다양한 팁이 있는 Dockerfile Best Practices 가이드를 읽어볼 수 있다.

Usage

Docker의 빌드 명령어는 DockerfilePATHURL 등의 빌드 정보로 지정된 특정 파일 집합을 통해 이미지를 만든다. PATH 는 로컬 파일시스템의 디렉토리에 해당하고 URL 은 Git 레포지토리의 주소에 해당한다.

빌드 정보는 재귀적으로 처리된다. 따라서 PATH 는 모든 하위 디렉토리를 포함하고 URL 은 레포지토리와 모든 하위 모듈을 포함한다. 다음 예제는 현재 디렉토리를 PATH 로 빌드하는 커맨드를 나타낸다.

$ docker build .

Sending build context to Docker daemon	6.51 MB
...

빌드는 CLI가 아닌 Docker daemon에 의해 실행된다. 빌드 과정은 모든 빌드 정보를 daemon에게 전달하는 것으로 시작한다. 대부분의 경우, 빈 디렉토리와 비어있는 빌드 정보로 시작한 후 Dockerfile을 만드는 것이 가장 좋다. Dockerfile을 빌드하기 위해 필요한 파일도 추가해야 한다.

Warning

루트 디렉토리 /PATH 로 사용하면 모든 파일이 Docker daemon으로 전송해 빌드하므로 사용하면 안된다.

빌드 정보 안에 있는 파일을 사용하기 위해 Dockerfile 에서 COPY 와 같은 명령어로 파일을 특정시킨다. 빌드의 성능 향상을 위해 .dockerignore 파일을 통해 파일과 디렉토리 등을 빌드에서 제외시킬 수 있다. .dockerignore 파일을 만드는데에 대한 자세한 정보는 후술한다.

전통적으로, DockerfileDockerfile 로 불리고 빌드 정보의 루트에 위치한다. docker build 명령어와 -f 플래그를 함께 사용하면 Dockerfile의 위치를 지정해줄 수 있다.

$ docker build -f /path/to/a/Dockerfile .

다음과 같이 레포지토리를 특정한 후 빌드가 성공했을 때 저장할 이미지를 지정할 수 있다.

$ docker build -t shykes/myapp .

빌드 후 여러 레포지토리 이미지를 태그시키고 싶다면, 여러 개의 -t 파라미터를 build 명령어 다음에 추가시킬 수 있다.

$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .

Dockerfile 의 명령어들을 실행시키기 전 Docker daemon은 Dockerfile 의 유효성 검사를 진행하고 문법적으로 문제가 있다면 에러를 발생시킨다.

$ docker build -t test/myapp .

Sending build context to Docker daemon	2.048 kB
Error response from daemon: Unknown instruction: RUNCMD

Docker daemon은 Dockerfile 의 명령어들을 하나씩 실행시키고 만약 필요하다면 이미지가 완성되기 전 각각의 명령어들의 결과를 새 이미지에 바로 반영한다. Docker daemon은 받은 빌드 정보를 자동으로 삭제한다.

각각의 명령어들은 독립적으로 작동하고 새로운 이미지를 만든다는 것을 떠올리면 RUN cd /tmp 는 다음 명령어에게 아무런 영향을 끼치지 않는다는 것을 알 수 있다.

Docker는 가능할 때 마다 docker build 의 실행 속도 향상을 위해 중간 위치에 있는 이미지 (캐시) 를 재사용한다. 재사용하면서 Using cache 라는 메시지를 남긴다.

$ docker build -t svendowideit/ambassador .

Sending build context to Docker daemon 15.36 kB
Step 1/4 : FROM alpine:3.2
 ---> 3lf630c65071
Step 2/4 : MAINTAINER SvenDowideit@home.org.au
 ---> Using cache
 ---> 2alc91448f5f
Step 3/4 : RUN apk update &&	apk add socat &&	rm -r /var/cache
 ---> Using cache
 ---> 21ed6e7fbb73
Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
 ---> Using cache
 ---> 7ea8aef582cc
Successfully built 7ea8aef582cc

빌드 캐시는 오직 로컬 부모 체인이 있는 이미지만 사용이 가능하다. 부모 체인이 있는 이미지란 캐시가 만들어진 이미지로부터 만들어진 이미지거나 이미지의 모든 체인이 docker load 로 불러온 이미지란 뜻이다. 만약 특정 이미지로부터 만들어진 캐시를 사용해 빌드하고 싶다면 --cache-from 옵션을 통해 가능하다. --cache-from 옵션을 통해 지정된 이미지는 부모 체인이나 다른 등록 과정이 필요하지 않다.

BuildKit

18.09 버전부터 Docker는 moby/buildkit 프로젝트에서 제공하는 빌드를 실행하기 위한 새로운 백엔드를 지원한다. BuildKit 백엔드는 기존 기능에 비해 많은 해택을 제공한다. BuildKit의 기능은 다음과 같다.

  • 사용되지 않는 빌드 단계를 찾아내고 비활성화 한다.
  • 빌드 단계에 따라 빌드들을 병렬화 한다.
  • 빌드 중 빌드 정보에 따라 변경된 파일만 전송한다.
  • 사용되지 않는 빌드 정보 안의 사용되지 않은 파일들을 찾아내고 비활성화 한다.
  • 다양한 새 기능이 있는 외부 Dockerfile을 사용한다.
  • API의 사이드 이펙트를 예방한다.
  • 자동 빌드 시 빌드 캐시의 우선순의를 결정한다.

BuildKit 백엔드를 사용하려면 docker build 하기 전 CLI에 DOCKER_BUILDKIT=1 의 환경 변수 설정을 해야 한다.

Format

다음은 Dockerfile 의 형식이다.

# Comment
INSTRUCTION arguments

명령어는 대소문자를 구분하지 않는다. 하지만 인자들과 더 쉽게 구분하기 위해 대문자로 작성하는 암묵적인 규칙이 있다.

Docker는 Dockerfile 의 순서에 따라 명령어를 수행한다. Dockerfile반드시 FROM 명령어로 시작 해야 한다. Dockerfile 은 명령문과 주석, 글로벌 변수로 나뉜다. FROM 명령어는 빌드의 원형 이미지 를 정하고 DocerfileFROM 명령어에서 사용할 인자를 정하는 하나 이상의 ARG 명령어와 함깨 사용된다.

Docker는 # 으로 시작하는 올바르지 않은 구문을 주석으로 처리한다. # 은 인자로 처리되는 줄은 주석으로 처리하지 않는다. 다음은 올바른 구문이다.

# Comment
RUN echo 'we are running some # of cool things'

주석은 Dockerfile 명령어들이 실행되기 전에 제거된다. 따라서 다음 코드와 같이 사용된 주석은 쉘에서 echo 명령어를 실행할 떄 처리되지 않는다.

RUN echo hello \
# comment
world

줄을 연장시켜주는 \ 문자는 주석에 적용되지 않는다.

Note on whitespace

호완성을 위해 주석이나 명령어 앞에 오는 빈칸은 무시하고 회피한다. 앞에 오는 빈칸은 적용되지 않고 다음과 같이 변환된다.

		# this is a comment-line
	RUN echo hello
RUN echo world
# this is a comment-line
RUN echo hello
RUN echo world

그러나 다음 RUN 명령어 뒤에 오는 인자와 같이 명령어의 인자 로 들어오는 빈칸은 무시되지 않는다. 따라서 다음은 앞에 빈칸을 포함한 채 ' hello world' 라고 출력될 것이다.

RUN echo "\
	 hello\
	 world"

Parser directives

구문 지시자는 선택적이고 Dockerfile 의 다음 라인부터 어떻게 명령들이 적용될지에 영향을 준다. 구문 지시자는 빌드에 레이어를 추가하지 않고, 빌드하는 단계에서 나타나지도 않는다. 구문 지시자는 # directive=value 와 같이 특별한 주석의 형태로 작성된다. 하나의 지시자는 한 번만 사용된다.

주석을 확인할 때, 빈 줄이나 빌드 명령어가 실행된 후에는 Docker는 더 이상 구문 지시자를 찾지 않는다. 대신 Docker는 모든 구문 지시자 형태를 주석으로 처리하고 더 이상 올바른 구문 지시자인지 확인하지 않는다. 그러므로 모든 구문 지시자는 Dockerfile 의 최상단에 위치한다.

구문 지시자는 대소문자를 구분하지 않는다. 그러나, 소문자로 작성하는 것이 암묵적인 규칙이다. 암묵적인 규칙에는 모든 구문 지시문 다음에 빈 줄 하나를 넣는 것도 포함된다. 구문 지시자에는 다음 줄을 연속해서 사용할 수 있는 문자 \ 의 사용이 제한된다.

이런 규칙들 때문에 다음 예시들은 모두 문법적으로 적절하지 않다.

  • \ 의 잘못된 사용

    # direc \
    tive=value
  • 중복 정의

    # directive=value1
    # directive=value2
      
    FROM ImageName
  • 빌드 명령어 다음에 작성해 주석으로 처리하는 경우

    FROM ImageName
    # directive=value
  • 구문 지시자가 아닌 주석 다음에 작성해 주석으로 처리하는 경우

    # About my dockerfile
    # directive=value
    FROM ImageName
  • 적절하지 않은 구문 지시자로 구문 지시문이 아닌 주석으로 처리되고 따라서 다음 구문 지시자 또한 주석으로 처리되는 경우

    # unknowndirective=value
    # knowndirective=value
  • 구문 지시문에서 라인을 넘기는 문자가 아닌 빈 문자는 허용된다. 따라서 다음 지시문들은 모두 같은 것으로 처리된다.

    #directive=value
    # directive =value
    #		directive= value
    # directive = value
    #		  dIrEcTiVe=value

다음은 지원되는 구문 지시어이다.

  • syntax
  • escape

syntax

# syntax=[remote image reference]

For example:

# syntax=docker/dockerfile
# syntax=docker/dockerfile:1.0
# syntax=docker.io/docker/dockerfile:1
# syntax=docker/dockerfile:1.0.0-experimental
# syntax=example.com/user/repo:tag@sha256:abcdef...

이 기능은 BuildKit을 사용 중일때만 활성화 가능하다.

syntax 지시자는 현재 Dockerfile을 빌드할 때 사용될 Dockerfile 빌더의 위치를 지정해준다. BuildKit은 Docker 이미지에 분산되어 있는 외부 빌드의 구성 요소를 하나인 것 처럼 연속적으로 사용할 수 있게 해주고 컨테이너 샌드박스 환경 안에서 실행시킨다.

사용자 지정 Dockerfile 구성요소로 다음 기능을 사용할 수 있다.

  • 데몬의 업데이트 없이 자동으로 버그를 잡는다.
  • 모든 사용자가 같은 Dockerfile을 같은 환경에서 빌드한다는 것을 보장해준다.
  • 데몬 업데이트 없이 가장 최신 기능을 사용할 수 있다.
  • 실험적인 기능이나 서드파티 기능을 사용할 수 있다.

Official releases

Docker는 Docker Hub의 docker/dockerfile 레포지토리를 빌드하는 Dockerfile이 사용할 수 있는 여러 공식 버전을 제공한다. 배포된 이미지에 접근하는 방법은 두 가지가 있다.

Stable channel은 다음과 같이 의미상 버전을 따른다.

  • docker/dockerfile:1.0.0 - 1.0.0 버전만을 허용한다.
  • docker/dockerfile:1.0 - 1.0.* 의 버전을 허용한다.
  • docker/dockerfile:1 - 1.*.* 의 버전을 허용한다.
  • docker/dockerfile:latest - stable channel 의 가장 최근 배포된 버전을 허용한다.

Experimental channel 은 stable channel 에서 배포된 버전의 구성요소가 포함된 상위 버전을 사용한다.

  • docker/dockerfile:1.0.1-experimental - 1.0.1-experimental 버전만 허용한다.
  • docker/dockerfile:1.0-experimental - 1.0 버전 이후의 가장 최신 experimental 버전을 허용한다.
  • docker/dockerfile:experimental - experimental channel 에 배포된 가장 최근 버전을 허용한다.

두 채널 중 필요에 맞는 채널을 사용해야한다. 만약 버그 수정을 원한다면, docker/dockerfile:1.0 을 사용해야 할 것이다. 만약 실험적 기능의 효과를 사용하고자 한다면, experimental channel을 사용해야 할 것이다. 만약 experimental channel을 사용 중이라면 새 배포 버전이 나올 때 호완되지 않는 상황이 발생할 수 있다. 따라서 전체 버전을 명시하는 것을 권장한다.

escape

# escape=\ (backslash)

또는

# escape=` (backtick)

escape 지시자는 Dockerfile 에서 문자를 escape 문자로 설정한다. 만약 특정되지 않았다면, 기본 escape 문자는 \ 이다.

Escape 문자는 escape 문자를 위해 사용하기도 하고 개행을 위해 사용하기도 한다. Escape 문자는 Dockerfile 명령어에 여러 줄을 작성할 수 있게 해준다. 하지만 명심해야 할 것이 Dockerfileescape parser directive가 있든 없든 RUN 명령에서는 개행을 제외한 escaping은 작동하지 않는다는 점이다.

Escape 문자를 억음 부호 (****) 로 설정하는 것은 ` 가 디랙토리 경로를 분리하는 문자로 사용되는 Windows 환경에서 유용할 수 있다. 억음 부호는 Windows PowerShell 에서 같은 의미로 사용되기도 한다.

다음 Windows 에서 작동하지 않는 예제를 살펴보자. 두 번째 줄의 두 번째 \ 은 첫 번째 \ 에 의해 escape 되지 않고 다음 줄로의 연장의 의미로 해석된다. 또한 세 번째 줄의 마지막 \ 도 명령어의 인자로 사용되지 않고 다음 줄로의 연장을 뜻하는 문자가 되었다. 그 결과 두 번째 줄과 세 번째 줄은 하나의 명령문으로 처리된다.

FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\

결과는 다음과 같다.

PS C:\John> docker build -t cmd .
Sending build context to Docker daemon 3.072 kB
Step 1/2 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/2 : COPY testfile.txt c:\RUN dir c:
GetFileAttributesEx c:RUN: The system cannot find the file specified.
PS C:\John

위 상황에서 하나의 해결책은 COPY 명령어와 dir 명령어의 대상을 / 로 지정하는 것이다. 그러나 이 방법은 좋게 말해서 Windows 경로와 자연스럽지 않고 나쁘게 말해서 Windows 의 모든 명령이 / 를 경로 구분자로 인식하지 않으니 에러가 발생하기 쉬운 조건이다.

escape 구문 지시자를 추가함으로써 다음의 Dockerfile 은 자연스러운 구문을 갖고 성공적으로 실행될 수 있다.

# escape=`

FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\

결과는 다음과 같다.

PS C:\John> docker build -t succeeds --no-cache=true .
Sending build context to Docker daemon 3.072 kB
Step 1/3 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/3 : COPY testfile.txt c:\
 ---> 96655de338de
Removing intermediate container 4db9acbb1682
Step 3/3 : RUN dir c:\
 ---> Running in a2c157f842f5
 Volume in drive C has no label.
 Volume Serial Number is 7E6D-E0F7

 Directory of c:\

10/05/2016  05:04 PM             1,894 License.txt
10/05/2016  02:22 PM    <DIR>          Program Files
10/05/2016  02:14 PM    <DIR>          Program Files (x86)
10/28/2016  11:18 AM                62 testfile.txt
10/28/2016  11:20 AM    <DIR>          Users
10/28/2016  11:20 AM    <DIR>          Windows
           2 File(s)          1,956 bytes
           4 Dir(s)  21,259,096,064 bytes free
 ---> 01c7f3bef04f
Removing intermediate container a2c157f842f5
Successfully built 01c7f3bef04f
PS C:\John>

Environment replacement

Dockerfile 에서 실행되는 일부 명령들은 ENV 문장을 통해 정의된 환경 변수를 매개변수로 사용할 수 있다. 탈출 문자는 문자로 표현할 수 없는 내용을 구문에 포함시키기 위해 사용된다.

환경 변수는 Dockerfile$variable_name 또는 ${variable_name} 의 형식으로 정의될 수 있다. 두 형식은 같은 표현으로 처리되고 중괄호 표현은 ${foo}_bar 과 같은 빈 문자가 없는 변수의 이름에서 생기는 모호함을 해결하기 위해 사용된다.

${variable_name} 의 표현은 또한 다음과 같은 몇 개의 표준 bash 연산자를 지원한다.

  • ${variable:-word}variable 이 정의되어 있다면 그 값으로 대체되고 정의되어있지 않다면 word 로 대체된다.

  • ${variable:+word}variable 이 정의되어 있다면 word 가 그 값으로 대체되고 정의되어있지 않다면 빈 문자열로 대체된다.

두 표현에서 word 는 환경 변수를 포함한 아무 문자열이나 될 수 있다.

Escaping 을 통해 변수를 작성하기 전에 \$foo\${foo} 처럼 \ 를 포함해 작성할 수 있다. 앞의 두 예시는 $foo${foo} 로 대체된다.

예시 (# 다음의 문자로 대체되어 적용된다)

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

환경 변수는 Dockerfile 에서 다음의 명령어에 적용할 수 있다.

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR
  • ONBUILD (위의 명령어들과 함께 사용되었을 때)

하나의 ENV 명령어로 여러 변수를 설정할 때 환경 변수를 사용하면 우선순위는 모두 같다. 예를 들어 다음과 같이 환경변수를 정의한다고 가정해보자.

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

이 때 def 의 값은 bye 가 아닌 hello 가 된다. 하지만 ghi 의 값은 abcbye 로 설정한 명령어와 같이 실행된게 아니므로 bye 가 된다.

.dockerignore file

Docker CLI가 docker daemon에게 내용물을 전달하기 전에 .dockerignore 은 내용물의 루트 디렉토리에 있는지 찾아본다. 만약 .dockerignore 이 루트 디렉토리에 있다면 CLI는 내용물 중 .dockerignore 의 내용과 일치하는 파일이나 디렉토리를 제외시킨다. 이 작업은 민감하거나 쓸모없는 파일이나 디렉토리가 daemon에게 전달되거나 ADD, COPY 의 명령으로 이미지에 추가되는걸 막아준다.

CLI는 .dockerignore 파일을 유닉스 쉘 에서 사용하는 것과 비슷하게 개행으로 구분된 패턴 리스트로 인식한다. 매칭의 목적은 내용물의 루트를 실제 루트 디렉토리처럼 사용할 수 있게 하는 것이다. 예를 들어, /foo/barfoo/bar 은 둘 다 PATHURL 의 git 레포지토리의 루트에서 foo 의 서브 디렉토리에 포함된 bar 의 파일이나 디렉토리를 제외한다. 둘은 다른 것을 제외하지 않는다.

.dockerignore 파일의 첫 번째 줄에 # 로 시작하는 라인이 있다면, 그 라인은 주석으로 처리되고 CLI에 의해 처리되기 전 무시된다.

다음은 .dockerignore 파일의 예제이다.

# comment
*/temp*
*/*/temp*
temp?

이 파일은 빌드할 때 다음 행동을 한다.

| Rule | Behavior |
| ----------- | ------------------------------------------------------------ |
| # comment | 주석으로 인식하고 무시함 |
| */temp* | 루트의 서브 디렉토리 안에서 이름이 temp 로 시작하는 모든 파일과 디렉토리를 제외시킨다. 예를 들어 이름이 /somedir/temporary.txt 인 파일은 제외되고 /somedir/temp 의 디렉토리도 제외된다. |
| */*/temp* | 루트의 두 레벨 아래 있는 서브 디렉토리 안에서 이름이 temp로 시작하는 파일과 디렉토리를 제외시킨다. 예를 들어 /somedir/subdir/temporary.txt 는 제외된다. |
| temp? | 루트 디렉토리에서 이름이 temp 로 시작하고 뒤에 한 글자가 추가된 파일과 디렉토리를 제외시킨다. 예를 들어 /tempa/tempb 는 제외된다. |

매칭은 Go 언어의 filepath.Match 규칙에 따라 진행된다. 전처리 단계에서는 앞이나 뒤에 오는 빈 문자를 제거하고 ... 의 요소를 Go 의 filepath.Clean 을 통해 처리한다. 빈 라인은 전처리 단계에서 제거된다.

Go 의 filepath.Match 규칙에 의해 Docker는 존재하지 않거나 존재하는 모든 디렉토리들을 특정하는 특별한 와일드카드 문자열인 ** 을 지원한다. 예를 들어, **/*.go 는 루트 디렉토리와 그 하위 디렉토리에서 .go 로 끝나는 모든 파일을 제외한다.

! 로 시작하는 라인은 제외되는 파일의 예외 파일이 된다. 다음 예시의 .dockerignore 파일은 이 메커니즘을 따른다.

*.md
!README.md

README.md 를 제외한 모든 마크다운 파일은 내용물에서 제외된다.

! 의 제외 규칙의 존재는 .dockerignore 의 마지막 줄에 포함시키는 파일과 제외시키는 파일이 겹칠 때 포함시킬지 제외시킬지에 영향을 끼친다. 다음 예제를 살펴보자.

*.md
!README*.md
README-secret.md

모든 마크다운 파일이 포함되지 않지만 README-secret.md를 제외한 README 마크다운 파일은 포함된다.

이제 다음 예제를 살펴보자.

*.md
README-secret.md
!README*.md

모든 README 파일이 포함된다. 또한 !README*.md 에 두 번째 줄인 README-secret.md 가 포함되므로 두 번째 줄은 아무런 영향도 끼치지 않는다.

.dockerignore 파일은 Dockerfile.dockerignore 도 제외시킬 수 있다. 이 파일들은 제외되어도 daemon이 동작하는데 필요하기 때문에 전달은 되지만 이미지를 위한 명령어인 ADD, COPY 명령어에 사용할 수는 없다.

마지막으로 특정 파일을 내용물에 제외시키는 것이 아닌 포함시켜야 할 때가 있다. 이 때는 첫 번째 패턴으로 * 를 사용하고 그 뒤에 ! 를 붙여 제외시킬 패턴을 추가한다.

Note

관례적으로 . 패턴은 무시된다.

FROM

FROM [--platform=<platform>] <image> [AS <name>]

또는

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

또는

FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

FROM 명령어는 새로운 빌드 단계를 준비하고 다음 명령어들의 기본 이미지 를 지정한다. 따라서 유효한 DockerfileFROM 명령어로 시작해야 한다. FROM 의 이미지는 유효한 이미지면 어떤 이미지든 상관 없다. Public Repositories 에서 이미지를 pull 해서 사용한다면 아주 간단하다.

  • ARGDockerfile 에서 FROM 앞에 올 수 있는 유일한 명령이다.
  • FROMDockerfile 에서 여러개의 이미지를 만들기 위해 또는 하나의 빌드 단계를 다른 단계들과 독립시키기 위해 여러번 사용될 수 있다. 다른 FROM 명령이 실행되기 전 마지막으로 출력된 이미지 ID 커밋을 기록해두면 된다. 각각의 FROM 명령은 이전 명령어로 만들어진 모든 상황을 지워버린다.
  • 선택적으로 ASFROM 뒤에 붙여 빌드 단계에 이름을 줄 수 있다. 이름은 FROM 의 이어지는 다음 FROM 에서 COPY --from=<name|index> 명령과 같이 해당 단계의 이미지를 부를 때 사용할 수 있다.
  • tagdigest 값은 선택적이다. 만약 둘 모두 생략했다면, 빌더는 latest 태그를 기본적으로 적용할 것이다. 만약 빌더가 tag 값을 찾지 못한다면 에러를 반환한다.

선택적으로 FROM 명령이 멀티 플랫폼 이미지를 참조할 때 --platform 플래그가 이미지의 플랫폼을 특정하기 위해 사용될 수 있다. 예를 들어 linux/amd64 , linux/arm64 , 또는 windows/amd64 가 있을 수 있다. 빌드에서 요구한 플랫폼을 기본으로 사용한다. 전역 빌드 인자는 이 플래그의 값으로도 사용될 수 있다. 예를 들어 자동 플랫폼 인자는 빌드 단계를 네이티브 빌드 플랫폼으로 강제할 수 있게 허용하기도 하고 크로스 컴파일을 하게 할 수도 있다.

Understand how ARG and FROM interact

FROM 명령은 FROM 전에 ARG 명령어로 선언된 변수를 사용할 수 있다.

ARG		CODE_VERSION=latest
FROM	base:${CODE_VERSION}
CMD		/code/run-app

FROM	extras:${CODE_VERSION}
CMD		/code/run-extras

FROM 전에 선언된 ARG 명령어는 빌드 단계 밖에서 실행된다. 따라서 FROM 전에 선언된 변수는 FROM 명령어 다음의 빌드 단계에서는 사용할 수 없다. 빌드 단계에서 FROM 전에 선언된 변수를 값을 설정하지 않은 상태에서 이름만 재정의하면 사용할 수 있다.

ARG		VERSION=latest
FROM	busybox:$VERSION
ARG		VERSION
RUN		echo $VERSION > image_version

RUN

RUN 은 두 가지 형태가 있다.

  • RUN <command> (기본적으로 리눅스에서는 /bin/sh 로 실행되고 윈도우에서는 cmd /S /C 로 실행된다. 따라서 커맨드는 그 형식에 맞게 작성되어야 한다)
  • RUN ["executable", "param1", "param2"] (exec 형식)

RUN 명령어는 현재 이미지의 위 새 래이어에서 실행되고 결과를 커밋한다. 커밋된 이미지의 결과물은 Dockerfile 의 다음 스탭에서 사용된다.

RUN 명령어를 레이어링하고 커밋을 생성하는 것은 소스 컨트롤처럼 자주 커밋하고 이미지의 어느 부분에서든지 새로운 파생 이미지를 만들 수 있다는 Docker의 핵심 컨셉과 일치한다.

exec 형식은 쉘 명령어가 멍잉(munging, wrangling) 되는 것을 방지해주고 쉘을 사용할 수 없는 기본 이미지를 사용하는 RUN 명령어가 동작할 수 있게 해준다.

shell 의 형식을 갖고 있는 기본 쉘은 SHELL 명령을 통해 바꿀 수 있다.

shell 형식에서 \ 을 사용하면 한 줄인 RUN 명령어를 다음 줄로 이을 수 있다. 예를 들어 다음 두 줄을 살펴보자.

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

위 명령은 다음 한 줄의 명령으로 바꿀 수 있다.

RUN /bin/bash -c 'source $HOME/.bashrc;	echo $HOME'

'bin/sh' 가 아닌 다른 쉘을 사용하고 싶을 때 exec 형식을 사용할 수 있다.

RUN	["/bin/bash", "-c", "echo hello"]

Note

exec 형식은 JSON array에서 파생된 것이다. 따라서 단어들을 묶을 때는 작은 따옴표 ( ' ) 가 아닌 큰 따옴표 ( " ) 로 묶어야 한다.

shell 형식과는 다르게 exec 형식은 명령 쉘을 통해 실행되지 않는다. 따라서 보통의 쉘의 작동이 실행되지 않는다. 예를 들어 RUN ["echo", "$HOME"]$HOME 의 변수를 참조하지 못한다. 만약 쉘의 작동을 원한다면 shell 형식이나 쉘의 디렉토리를 RUN ["sh", "-c", "echo $HOME"] 와 같이 사용해야 한다. exec 형식을 shell 형식과 같은 방법으로 쉘 디렉토리와 사용한다면, Docker가 아닌 쉘의 환경 변수를 사용한다.

Note

JSON 형식에서 escape 문자를 사용하기 위해서는 \ 가 필요하다. 이 내용은 백슬래시를 경로 구분자로 사용하는 윈도우 환경에서 관련이 많다. 다음 명령줄은 JSON을 사용하지 않는 shell 형식과는 다르게 처리되고 예상치 못한 방법으로 실행에 실패할 것이다.

RUN ["c:\windows\system32\tasklist.exe"]

위 예제에 대한 올바른 표현은 다음과 같다.

RUN ["c:\\windows\\system32\\tasklist.exe"]

RUN 명령의 캐시는 다음 빌드에서 자동으로 삭제되지 않는다. RUN apt-get dist-upgrade -y 와 같은 명령의 캐시는 다음 빌드에서 재사용하게된다. RUN 명령어에 의한 캐시는 docker build --no-cache 와 같이 --no-cache 플래그를 통해 삭제가 가능하다.

RUN 명령에 의한 캐시는 또한 ADDCOPY 명령어에 의해 초기화 가능하다.

Dockerfile Best Practices guide 에서 자세한 정보를 확인 가능하다.

Known issues (RUN)

  • 783 이슈는 AUFS 파일 시스템에서 파일 권한과 관련해 발생하는 문제다. 파일을 제거하기 위해 rm 명령을 사용할 때 발생한다.

    최신 dirperm1 옵션이 추가 가능한 AUFS 버전의 시스템에서는 docker가 이 이슈를 자동으로 해결하기 위해 레이어를 dirperm1 옵션을 추가하며 마운팅한다. dirperm1 과 관련된 자세한 정보는 aufs man 페이지에서 확인할 수 있다.

    만약 시스템이 dirperm1 옵션을 지원하지 않는다면, 그 이슈는 해결 방법을 제시할 것이다.

CMD

CMD 명령어는 다음 세 가지 형식이 있다.

  • CMD ["executable", "param1", "param2"] (exec 형식, 이 형식을 자주 사용한다)
  • CMD ["param1", "param2"] (기본 파라미터를 ENTRYPOINT 로 갖는다.)
  • CMD command param1 param2 (shell 형식)

Dockerfile 에는 단 하나의 CMD 명령어가 존재할 수 있다. 만약 여러 개의 CMD 명령어를 사용한다면 마지막의 CMD 명령어만 실행될 것이다.

CMD 명령의 주 목적은 실행중인 컨테이너에 기본 환경을 제공하기 위함이다. 기본 환경에는 실행 가능한 파일을 포함하거나 올바른 ENTRYPOINT 명령어가 실행됐다면 포함하지 않을 수 있다.

만약 CMDENTRYPOINT 명령어로의 기본 인자를 전달하는데 사용했다면, CMDENTRYPOINT 명령어들은 JSON 배열 형식으로 특정되어야 한다.

Note

Exec 형식은 JSON 배열 형식으로 해석되므로 문자열을 작은 따옴표 (') 가 아닌 큰 따옴표 (") 로 감싸야 한다.

shell 형식과는 다르게 exec 형식은 커맨드 쉘을 통해 실행되지 않는다. 따라서 보통의 쉘 동작이 실행되지 않는다. 예를 들어 CMD ["echo", "$HOME"]$HOME 변수를 참조하지 못한다. 만약 쉘 동작을 작동시켜야 한다면 shell 형식을 사용하거나 CMD [ "sh", "-c", "echo $HOME"] 과 같이 쉘의 디렉토리를 통해 실행해야 한다. exec 형식을 쉘 디렉토리를 통해 사용한다면 Docker의 환경 변수를 사용하는 것이 아닌 쉘의 환경 변수를 사용할 것이다.

shell 이나 exec 형식을 사용한다면, CMD 명령어는 이미지를 실행할 때 실행될 명령어를 작성할 수 있다.

만약 shell 형식을 CMD 에 사용한다면, <command>/bin/sh -c 를 통해 실행 될 것이다.

FROM ubuntu
CMD echo "This is a test." | wc -

만약 <command> 를 쉘 없이 실행 하고 싶다면 실행 파일의 전체 경로와 명령어를 JSON 배열 형식으로 표현해야 한다. 이 배열 형식은 CMD 의 바람직한 형식이다. 다른 추가적인 인자들은 반드시 배열에 독립적으로 표현되어야 한다.

FROM ubuntu
CMD ["/usr/bin/wc", "--help"]

만약 컨테이너를 매번 같은 실행파일로 실행시키고 싶다면, ENTRYPOINTCMD 와 같이 사용하는 것을 고려해 볼 수 있다. 자세한 내용은 ENTRYPOINT 에서 확인할 수 있다.

만약 유저가 docker run 명령어의 매개 인자를 특정했다면 CMD 로 정의된 내용을 대체한다.

Note

RUNCMD 를 혼동하지 말자. RUN 은 명령을 실행하고 그 결과를 커밋하는 반면 CMD 는 빌드 타임에서 아무 것도 실행하지 않고 이미지의 초기 명령을 특정할 뿐이다.

LABEL

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

LABEL 명령어는 이미지에 메타데이터를 추가한다. LABEL 은 키-벨류 쌍이다. LABEL 로 세부사항을 추가하기 위해서는 따옴표와 백슬래쉬를 커맨드라인에 사용할 수 있다. 다음은 몇 가지 예시들이다.

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."

이미지는 하나 이상의 라벨을 가질 수 있다. 여러 개의 라벨을 하나의 라인에서 정할 수도 있다. Docker 1.10 이전 버전에서는 최종 이미지의 사이즈를 감소시켰지만 더이상 적용되는 이야기는 아니다. 여전히 여러 라벨을 한 명령구에서 정할 수 있고 다음은 여러 라벨을 정의하기 위한 두 가지 방법이다.

LABEL	multi.label1="value1" multi.label2="value2" other="value3"

LABEL	multi.label1="value1" \
		multi.label2="value2" \
		other="value3"

라벨은 기본 혹은 상위 이미지의 라벨을 현재 이미지에 상속받는다. 만약 라벨이 다른 값으로 존재한다면 가장 최근 적용된 값이 그 전의 값을 대신하게 된다.

이미지의 라벨을 확인하기 위해 docker image inspect 명령어를 사용할 수 있다. 라벨을 확인하기 위해 --format 옵션을 사용할수도 있다.

docker image inspect --format='' myimage
{
	"com.example.vendor": "ACME Incorporated",
	"com.example.label-with-value": "foo",
	"version": "1.0",
	"description": "This text illustrates that label-values can span multiple lines.",
	"multi.label1": "value1",
	"multi.label2": "value2",
	"other": "value3"
}

MAINTAINER (deprecated)

MAINTAINER 명령은 만들어진 이미지의 작성자 필드 값을 설정한다. LABEL 명령어는 상황에 아주 유연하고 MAINTAINER 대신 LABEL 을 사용할 수 있다. 또한 LABEL 명령어로 여러 필요한 메타데이터를 설정할 수 있고 docker inspect 같은 명령으로 쉽게 확인할 수 있다. MAINTAINER 필드에 맞는 라벨을 다음과 같이 설정할 수 있다.

LABEL maintainer="SvenDowideit@home.org.au"

이 명령은 다른 라벨들과 함께 docker inspect 로 확인할 수 있다.

EXPOSE

EXPOSE 명령은 Docker에게 컨테이너가 런타임에서 어떤 네트워크 포트에 대기중인지 알려줄 수 있다. 대기 중인 포트가 TCP 이든 UDP 이든 설정할 수 있고, 프로토콜을 따로 설정하지 않는다면 TCP가 기본으로 설정되어있다.

EXPOSE 명령어는 실제로 포트를 개방하지는 않는다. EXPOSE 는 단지 이미지를 빌드한 사람과 컨테이너를 실행하는 사람 간의 소통을 위한 문서로 활용되며 컨테이너를 실행하는 사람은 포트를 개방해야 할 것이다. 컨테이너가 실행될 때 포트를 실제로 개방하기 위해서는 docker run-p 플래그를 사용하고 하나 이상의 포트와 연결시킨다. 또는 -P 플래그로 expose 된 모든 포트를 내림차순으로 개방할 수 있다.

기본적으로, EXPOSE 는 TCP를 기반으로 동작하지만 UDP를 다음과 같이 특정할 수 있다.

EXPOSE 80/udp

TCP와 UDP로 둘 다 개방하기 위해서는 다음 두 줄이 포함되어 있어야 한다.

EXPOSE 80/tcp
EXPOSE 80/udp

이 경우 docker run-P 플래그와 사용한다면, TCP로 한 번, UDP로 한 번 개방한다. -P 는 호스트의 포트 중 삭제된 포트를 내림차순으로 사용한다는 것을 명심하자. 따라서 TCP와 UDP 포트는 한 번에 개방되지 않는다.

EXPOSE 세팅과 별개로, 그것 모두를 -p 플래그를 통해 런타임에서 개방할 수 있다.

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

호스트 시스템에서 포트 리디렉션을 설정하기 위해 -P 플래그를 사용할 수 있다. docker network 명령은 컨테이너간 커뮤니케이션을 위한 특정 포트 개방이 필요 없는 네트워크를 만들 수 있게 한다. 해당 네트워크는 컨테이너들끼리 서로 네트워크에 연결되어 서로의 어느 포트로든 소통할 수 있다. 자세한 정보는 이 기능의 프리뷰 를 통해 확인할 수 있다.

ENV

ENV <key> <value>
ENV <key>=<value> ...

ENV 명령은 환경 변수 <key> 를 값 <value> 로 설정하는 명령어이다. 그 값은 빌드 단계에 있는 다른 하위 명령어들과 사용될 수 있고 많은 부분에서 대체될 수 있다.

ENV 명령은 두 가지 형식이 있다. 첫 번째 형식인 ENV <key> <value> 는 하나의 변수에 값을 설정한다. 첫 번째 빈칸 이후 빈칸을 포함한 모든 문자열은 <value> 로 처리된다. <value> 는 다른 환경 변수를 대체하기 때문에 따옴표 문자는 escape 된 문자가 아니라면 제거될 것이다.

두 번째 형식인 ENV <key>=<value> ... 는 여러 개의 변수를 한 번에 설정할 수 있다. 두 번째 형식은 첫 번째 형식과는 다르게 등호 기호를 문법에서 사용한다. 명령줄 파싱처럼, 따옴표와 백슬래시는 값에 빈 칸을 사용하기 위해 사용할 수 있다.

다음은 그 예시들이다.

ENV myName="John Doe" myDog=Rex\ The\ Dog \
	myCat=fluffy
ENV	myName John Doe
ENV mydog Rex The Dog
ENV myCat fluffy

두 방식 모두 최종 이미지에서는 같은 결과를 갖게된다.

환경 변수는 ENV 명령을 통해 설정되고 컨테이너가 결과 이미지를 만들 때 까지 유지된다. docker inspect 를 통해 변수의 값을 확인할 수 있고 docker run --env <key>=<value> 를 통해 값을 바꿀 수 있다.

Note

시스템 환경 유지는 예상치 못한 사이드 이펙트를 발생시킬 수 있다. 예를 들어, ENV DEBIAN_FRONTEND noninteractive 가 설정되어있는 상황에서 데비안 파생 이미지를 사용하는 사용자는 apt-get 이 불확실해질 수 있다. 하나의 명령어에 대한 변수 값을 설정하기 위해서는 RUN <key>=<value> <command> 를 사용할 수 있다.

ADD

ADD 는 두 가지 형식을 갖고있다.

ADD [--chown=<user>;<group>] <src>... <dest>
ADD [--chown=<user>;<group>] ["<src>",... "<dest>"]

빈 칸을 포함하는 경로는 첫 번째 형식을 사용할 수 없으므로 두 번째 형식을 사용한다.

Note

--chown 기능은 리눅스 컨테이너를 사용하는 Dockerfile 에서만 사용 가능하고 윈도우 컨테이너에서는 사용할 수 없다. 유저와 그룹 권환에 대한 개념이 윈도우와 리눅스 간에 변환될 수 없기 때문이다. 따라서 /etc/passwd/etc/group 에서 추출한 유저와 그룹 이름을 ID로 사용하는 이 기능은 오직 리눅스 OS 파생 컨테이너에서만 확인할 수 있다.

ADD 명령어는 새 파일, 디렉토리, 리모트 파일의 URL 주소 등을 <src> 로 받고 이미지의 파일 시스템의 <dest> 에 그것들을 추가한다.

여러 개의 <src> 리소스가 작성될 수 있고 만약 리소스가 파일이나 디렉토리라면, 그 주소는 빌드 내용물의 소스와 관련된 것으로 인식된다.

각각의 <src> 는 와일드카드를 포함하고 있을 수 있고 파일 매칭은 Go 의 filepath.Match 규칙에 의해 진행된다. 예를 들면 다음과 같다.

"hom" 으로 시작하는 모든 파일을 추가하려면 다음과 같다.

ADD hom* /mydir/

아래 예제에서, ? 는 단일 문자를 대체한다. 예를 들면 "home.txt" 가 해당한다.

ADD hom?.txt /mydir/

<dest> 는 절대 경로이거나 WORKDIR 와 연관된 상대경로이고 소스 파일들이 모두 대상 컨테이너 안으로 복사된다.

다음은 상대 경로를 사용한 예시이다. "test.txt" 를 <WORKDIR>/relativeDir/ 에 추가한다.

ADD test.txt relativeDir/

다음 예제는 절대 경로를 사용하고 "test.txt" 를 /absoluteDir/ 에 추가한다.

ADD test.txt /absoluteDir/

특수한 문자 ([, ] 등) 가 포함된 파일이나 디렉토리를 추가할 때에는 매칭 패턴으로 처리되지 않게 그 경로를 Go 언어의 규칙에 따라 escape 시켜줘야 한다. 다음은 arr[0].txt 를 추가하는 예시이다.

ADD arr[[]0].txt /mydir/

모든 새로운 파일과 디렉토리는 --chown 플래그를 통해 유저 이름과 그룹 이름을 제공하지 않았다면, UID와 GID가 0인 상태로 만들어지고 제공했다면 UID/GID가 해당 파일을 추가하기 위해 요구된다. --chown 플래그의 형식은 각각의 유저 이름, 그룹 이름 문자열이나 UID, GID의 번호의 조합이 필요하다. 유저 이름을 그룹 이름 없이 또는 UID를 GID 없이 플래그 값에 적용하면 GID를 UID와 같은 숫자로 적용할 것이다. 만약 유저 이름과 그룹 이름이 주어진다면, 컨테이너의 루트 파일시스템 /etc/passwd/etc/group 파일은 이름과 숫자 형식의 UID, GID 를 번역하는데 사용된다. 다음 예제는 올바른 --chown 플래그의 사용법이다.

ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/

만약 컨테이너 루트 파일시스템이 /etc/passwd/etc/group 파일에 --chwon 플래그에 사용된 유저나 그룹의 이름이 포함되어 있지 않다면, 빌드는 ADD 연산자에서 에러가 발생한다. 숫자로 된 ID는 루트 파일시스템 파일을 찾아볼 필요도 없고 의존적이지도 않다.

profile
STECH, Sophomore in Computer Science. Cadet of 42Seoul.

0개의 댓글