1. Dockerfile 이란?
공식문서의 정의(Dockerfile reference)
Docker can build images automatically by reading the instructions from a
Dockerfile
. ADockerfile
is a text document that contains all the commands a user could call on the command line to assemble an image. This page describes the commands you can use in aDockerfile
.
쉽게 말하면 Dockerfile
은 도커 이미지를 생성하기 위한 명령어 모음이다.
- Dockerfile은 자체적인 명령어를 사용하여 정의한다.
- 명령어는
FROM
,ENV
,WORKDIR
,COPY
,RUN
,CMD
… 등이 있다. - 참고: Docker docs | Dockerfile
- 명령어는
- 작성된 명령은 절차적으로 실행된다.
- 정의된 Dockerfile은
docker build ...
명령을 통해 이미지로 생성된다.
1.1. Dockerfile
을 사용하여 이미지 빌드하는 방법
docker build [OPTIONS] PATH | URL | -
- ex) 현재 경로에 도커 파일이
Dockerfile.user
명으로 있는 경우
## -t: 이미지 이름 정의, -f: 사용할 Dockerfile 이름 명시
docker build -t test_images -f Dockerfile.user .
- ex) 현재 경로에 도커 파일이
Dockerfile
명으로 있는 경우
## 도커파일명이 "Dockerfile"인 경우 -f는 생략 할 수 있다.
docker build -t test_images .
- ex) 이미지에 태그를 부여하여 생성
# -t <name:tag>
## tag가 생략되는 경우 'latest'로 자동 부여된다.
docker build -t test_images:tag-v2 .
2. FROM
명령어
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
FROM
은 신규로 생성될 이미지의 Base 이미지를 지정하는 역할을 한다.- 도커의 이미지는 레이어 형식의 구조를 사용한다.
- OS 커널 이미지를 생성하는 경우를 제외하고, 사용자가 생성하는 모든 이미지에는 FROM이 정의된다.
- 참고: 3. Docker 사용을 위한 환경 이해하기 | 도커 라이브사이클
ARG
명령을 제외하고 FROM은 항상 맨 위에 나와야 한다.--platform
옵션은 이미지의 플렛폼을 지정할 때 사용된다.linux/amd64
: 인텔 아키텍처인 x86 기반의 리눅스 플렛폼 지정linux/arm64
: arm 아키텍처 기반의 리눅스 플렛폼 지정windows/amd64
: 인텔 아키텍처인 x86 기반의 윈도우 플렛폼 지정
[AS <name>]
FROM은 여러개 정의가 가능하며, 이 경우 구분하기 위해 가명을 지원한다.- FROM은 다음 FROM이 나오기 전까지 하나의 네임스페이스를 가지는 코드 블록으로 이해해도 괜찮다.
- 조금 더 비슷한 비유는 Github Action의 JOB 하나가 FROM 하나로 볼 수 있다.
3. RUN
명령어
# 사용법 1. shell 명령 방식으로 전달
RUN command param1 param2
# 사용법 2. exec 실행 형식 *큰따옴표(")만 사용가능
RUN ["executable", "param1", "param2"]
RUN
명령은 이미지 빌드에 필요한 스크립트를 명령을 수행하기 위해 사용된다.RUN
명령은 shell에서 명령을 그대로 사용할 수 있다.- 사용 가능한 명령은 결국
FROM
에 정의된 Base 이미지에 있는 패키지만 가능하다.
- 사용 가능한 명령은 결국
RUN
명령은 빌드 로그에 진행 상황으로 표시된다.- Github Action에서 step과 비슷하게 로그에 진행 상황이 표시된다.
RUN
명령이 실행되면 결과는 커밋이 된다.- 커밋된 결과는 다음
FROM
에서 접근하여 사용이 가능하다.
- 커밋된 결과는 다음
- '사용법 2' 경우
$환경변수
에 접근 하는 방식의 명령을 사용하는 경우 약간의 차이가 발생한다.RUN echo $HOME
가능RUN [ "echo", "$HOME" ]
불가능RUN [ "sh", "-c", "echo $HOME" ]
가능- 또한 exec 실행 방식은 json 배열이다. 때문에 작은따옴표(')는 사용할 수 없다.
예시1 - RUN
단순 사용
- Dockerfile
# ubuntu 호스트에 이미지가 없다면 자동 설치
FROM ubuntu
# case 1) 단순 사용
RUN echo $HOME
# case 2) 여러줄로 사용
RUN /bin/bash -c 'source $HOME/.bashrc && \
echo $HOME'
# case 3) exec 실행형식 사용
RUN [ "sh", "-c", "echo $HOME" ]
docker build -t test_run .
로그 확인
1) [ 1 / 3 ] : FROM ubuntu 명령으로 인해 docker 레지스트리에서ubuntu:latest
를 내려 받는다.
2) [ 2 / 4 ], [ 3 / 4 ], [ 4 / 4 ] : RUN 명령의 경우 빌드 로드에 진행 상황이 표시되는 것을 확인 할 수 있다.
예시2 - 이전 FROM
의 RUN
에서 수행된 결과를 다음 FROM
에서 사용하기
- Dockerfile.RUN
FROM ubuntu as job1
RUN echo 'echo "echo 1"' > echo.sh
RUN echo 'echo "echo 2"' >> echo.sh
FROM ubuntu as job2
RUN mkdir ./app
# `--from` 옵션으로 job1에서 생성한 echo.sh를 가져와 복사한다.
COPY --from=job1 /echo.sh ./app/echo.sh
RUN sh ./app/echo.sh
docker build -t test_run:tag2 -f Dockerfile.RUN .
로그 확인
1) [ job 1 / 3 ] : job1 과 job2는 동일한 이미지를 사용한다.- 때문에 [ job2 1 / 3 ] 로그는 출력되지 않는다.
CACHED
가 붙는 이유는 '예시1)'에서 설치된 레이어를 사용하기 때문이다. (아래 사진으로 추가 설명)
2) [ job1 2 / 3] ~ [ job2 4 / 4 ] 로그를 보게되면job1
과job2
는 순차적으로 실행되지 않는다.- 하지만
COPY --from=job1
처럼job1
을 의존하는 경우라면job1
종료후 순차적으로 수행한다.
새로운 가상환경에서 '예시2'를 다시 빌드한 로그 확인
1) 새로운 가상환경
2) [job1 1/3]에서CACHED
가 붙지 않고 신규로 설치된다.
4. CMD
명령어
# 사용법 1. shell 명령 방식으로 전달
CMD command param1 param2
# 사용법 2. exec 실행 형식 *큰따옴표(")만 사용가능
CMD ["executable","param1","param2"]
# 사용법 3. ENTRYPOINT 와 같이 사용시
CMD ["param1","param2"]
CMD
명령의 주된 목적은 컨테이너가 정상 실행될 때 "기본값(명령)"을 전달(실행)하는 목적으로 사용된다.- 여기서 "기본값"이란 2가지를 의미한다.
- 실행파일(명령어)을 전달한다.
- 인자를 전달한다. 전달된 인자는
ENTRYPOINT
명령에서 사용하게 된다.
CMD
명령은Dockerfile
에서 1개만 사용할 수 있다.- 여러개 사용하는 경우 가장 마지막 항목만 적용된다.
예시1 - CMD
단순 사용
FROM ubuntu as job1
RUN echo 'echo "echo 1"' > echo.sh
RUN echo 'echo "echo 2"' >> echo.sh
FROM ubuntu as job2
RUN mkdir ./app
# job1에서 생성한 echo.sh를 가져와 복사한다.
COPY --from=job1 /echo.sh ./app/echo.sh
# 실행 권한 추가
RUN chmod +x ./app/echo.sh
# 컨테이너 실행시 수행될 명령
CMD ["sh", "-c", "./app/echo.sh"]
docker build -t test_cmd .
로그 확인
docker inspect test_cmd
결과에서 "Cmd" 확인
5. ENTRYPOINT
명령어
*도커를 많이 사용하지 않았다면 ENTRYPOINT
없이 CMD
를 주로 활용하는 것을 추천합니다.
# 사용법 1) 비권장 사항
ENTRYPOINT command param1 param2
# 사용법 2) 권장 사항 *큰따옴표(")만 사용가능
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT
는 컨테이너 실행시 수행할 "명령"을 지정하는데 사용한다.ENTRYPOINT
는 주로CMD
와 같이 사용된다.- 주로
ENTRYPOINT
에는 실행할 명령을 넣고CMD
에는 파라미터를 정의 방법으로 사용한다.
- 주로
- 이러한 방법을 사용하는 큰 이유는
CMD
의 경우 쉽게 재정의 할 수 있게 했기 때문이다.ENTRYPOINT
를 재정의 하려면docker run
명령에--entrypoint
옵션을 사용해야 한다.- 반면
CMD
의 경우docker run <image> param1 parma2
이러한 방식으로 재정의가 가능하다.
ENTRYPOINT
명령은Dockerfile
에서 1개만 사용할 수 있다.- 여러개 사용하는 경우 가장 마지막 항목만 적용된다.
예시1 - ENTRYPOINT
로 실행 환경 지정
1) 빌드 수행: docker build -t node_entry .
FROM node:18.19.0-alpine
# 컨테이너 실행시 실행될 값 - 변경할 수 없음
ENTRYPOINT ["node"]
# 컨테너 실행시 실행될 값 - 변경 할 수 있음
CMD ["app.js"]
3) docker inspect node_entry
결과에서 "Cmd", "Entrypoint" 확인
3) 호스트에 실행시킬 /root/test.js
파일 준비
console.log('Hello world');
console.log(`process.argv:\n\t${process.argv}`);
console.log(`new Date().toString():\n\t${new Date().toString()}`);
4) /root/test.js
을 마운트 하고 컨테이너 실행
docker run -d \
--name node_app \
-v $(pwd)/test.js:/app.js \ # 컨테이너에 생성될 파일 app.js로 정의
node_entry
- 결과
예시2 - run
명령으로 실행시 CMD
를 재정의
- docker run 명령
docker run -d \
--name node_app \
-v $(pwd)/test.js:/test.js \ # 컨테이너에 생성될 파일 test.js로 정의
node_entry \
test.js param1 # <image> 이후에 오는 인자는 CMD를 재정의 한다.
- 결과
예시3 - run
명령으로 실행시 ENTRYPOINT
를 /bin/sh
로 재정의
- 컨테이너 실행시
ENTRYPOINT
를/bin/sh
로 재정의
docker run \
--name node_app \
-v $(pwd)/test.js:/app.js \
--entrypoint "/bin/sh" \ # ENTRYPOINT 재정의: 실행 환경을 쉘로 변경
node_entry \
-c "cat app.js" # CMD 재정의: 쉘 명령인 cat 실행
- 결과
💡ENTRYPOINT
는 재정의가 어렵다
docker run --entrypoint ...
를 사용한 재정의에는 제약이 많다.docker run --help
를 해보면 알지만--entrypoint
뒤에는 문자열만 가능하다.- 인자로 전달된 문자열에 띄어쓰기가 포함되는 경우 다양한 문제가 발생한다.
- 결론: 재정의가 어렵기 때문에
CMD
재정의 방법이 더 많이 사용된다.
--entrypoint
실패 사례
# 띄어쓰기로 인해 TZ=Asia/Seoul를 폴더로 인식
## ... runc create failed: unable to start container process: exec: "TZ=Asia/Seoul node": stat TZ=Asia/Seoul node: no such file or directory: unknown.
docker run -d \
--name node_app \
-v $(pwd)/test.js:/app.js \
--entrypoint "TZ=Asia/Seoul node" \
node_entry
# 띄어쓰기로 인해 node 실행 파일을 정상적으로 찾지 못함
## ... runc create failed: unable to start container process: exec: "node -e": executable file not found in $PATH: unknown.
docker run -d \
--name node_app \
-v $(pwd)/test.js:/app.js \
--entrypoint "node -e" \
node_entry \
"process.env.TZ='Asia/Seoul'; require('./app.js')" app.js;
# format 에러
## docker: invalid reference format. See 'docker run --help'.
docker run -d \
--name node_app \
-v $(pwd)/test.js:/app.js \
--entrypoint ["TZ=Asia/Seoul", "node"] \
node_entry
6. ENV
명령어
ENV <key>=<value> ...
ENV
명령은 컨테이너가 사용할 환경 변수를 정의하는 명령이다.ENV
사용시 주의사항- ENV로 지정된 환경변수의 지속성에 대해 이해하고 사용해야 한다.
- 잘못된 ENV 사용으로 이전 레이어 또는 다음 레이어의 환경변수를 오염 시킬 수 있다.
- 참고: Docker docs | commandline | builder#env
docker inspect
명령을 통해 생성된 이미지에 정의된 ENV 목록을 볼 수 있다.docker run --env <key>=<value>
을 사용하여 ENV 재정의가 가능하다.- 여러 환경변수를 주입하려면
--env <key>=<value>
를 연속하여 사용한다. --env <key>=<value>
를 사용하면 ENV로 지정되지 않는 환경 변수도 주입이 가능하다.
- 여러 환경변수를 주입하려면
- 여러 FROM를 사용하는 경우 최종 FROM에 정의된 ENV 최종적으로 적용된다.
- 생성된 컨테이너에 환경변수로 사용
docker inspect
에서 확인가능
예시1) 이미지 ENV
단순 사용
FROM ubuntu as job1
ENV VAR1="test 1"
ENV VAR2=test\ 2
FROM ubuntu as job2
# 생성된 이미지 마지막 FROM에 정의된 ENV만 적용된다.
ENV VAR3=test3
CMD ["sh", "-c", "echo $VAR1, $VAR2, $VAR3 && env"]
docker build -t test_env .
로그 확인
docker inspect test_env
결과에서 "Env" 확인
docker run
실행 결과 확인
예시2) run
명령으로 실행시 환경변수 재정의
docker run --name env_container \
--env VAR3=custom3 \
test_env
- 결과 확인
예시3) run
명령으로 실행시 환경변수 신규 정의
docker run --name env_container \
--env CUSTOM=value1 \
--env VAR1=value1 \
test_env
- 결과 확인
7. WORKDIR
명령어
WORKDIR /path/to/workdir
WORKDIR
는 단순하게 작업할 공간인 디렉토리를 생성하는 명령이다.WORKDIR
는 쉽게 보면 2가지 명령을 수행한다.mkdir /path/to/workdir
cd /path/to/workdir
WORKDIR
명령은 여러번 사용이 가능하다.- 작업 디렉토리를 사용하지 않으면 기본은 루트(/)이다.
- 작업 디렉토리 설정은 하는 것을 권장한다.
- 의존하는 이미지에 의해 작업 디렉토리가 설정될 수 있기 때문이라고 한다.
예시1) WORKDIR
공식 예제
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
- pwd의 결과는
/a/b/c
이다.
8. COPY
명령어
# 사용법 1.
COPY <src>... <dest>
# 사용법 2. path에 공백이 있는 경우 사용한다.
COPY ["<src>",... "<dest>"]
# 사용법 3. 권한 부여 사용
COPY [--chown=<user>:<group>] [--chmod=<perms>] <src>... <dest>
# 사용법 4. 이전 from 결과를 복사
COPY ...권한 [--from=<from>] <src>... <dest>
COPY
는 호스트에 있는 파일(디렉토리)을 복사하기 위해 사용한다.- 복사된 파일은 빌드시 또는 컨테이너에서 사용된다.
<src>
의 경우 히든카드를 통한 찾기도 지원한다.- ex)
COPY hom* /mydir/
- ex)
COPY hom?.txt /mydir/
- ex)
- 일부 파일의 경우 권한이 필요할 수 있기 때문에 권한을 컨트롤하는
--chown
옵션을 제공한다.--chown
옵션의 경우 window 환경을 가진 컨테이너에서는 사용할 수 없다고 한다.- 참고: 참고: Docker docs | commandline | builder#options
- 또한
--from
옵션을 통해 이전 FROM에서 생성된 파일을 복사하여 사용도 가능하다.
9. 실제 사용 예시로 보는 Dockerfile
9.1. node
환경에서 이미지 빌드 - 기초
FROM node:18.19.0-alpine
# 1. 작업 디렉토리 설정 = 'mkdir /usr/app && cd /usr/app'
WORKDIR /usr/app
# 2. 의존성 라이브러리 다운로드를 위해 복사
COPY package*.json ./
# 3. 라이브러리 다운로드 수행
RUN npm ci
# 4. ts 설정 파일 복사
COPY tsconfig.json ./
# 5. 소스코드 포함한 모든 파일 복사
## 이 경우 '.dockerignore' 필수로 사용한다.
COPY . /usr/app
# 6. 빌드 명령 수행
RUN npm run build
# 7. 컨테이너 정상 실행 후 빌드된 결과로 app이 실행되도록 명령어 지정
CMD ["npm","run","start"]
- 위의 Dockerfile는 보완점이 존재한다.
- node 실행시 필요 없는 환경을 제거하지 못했기 때문이다.
- 즉, typescript 환경이나 devDependencies를 제거한 이미지가 되어야 한다.
- 그렇게 되면 더 경량화된 이미지와 컨테이너를 만들 수 있다.
9.2. node
(nestjs
) 환경에서 이미지 빌드 - 고급
### builder: 해당 FROM은 빌드만 수행한다. ###
FROM node:18.19.0-alpine as builder
# builder-1) 작업 디렉토리 지정
WORKDIR /app
# builder-2) 의존성 설치
COPY ./package.json /app
COPY ./package-lock.json /app
RUN npm ci
## 다른 방법도 가능하지만 경량화 작업을 수행하기 때문에 이것도 문제가 없다.
RUN npm i -g @nestjs/cli
# builder-3) node_modules에서 devDependencies 제거
RUN npm prune --production
# builder-4) 빌드 수행
COPY . .
RUN npm run build
### images: 해당 FROM의 결과를 최종 이미지로 생성한다. ###
FROM node:18.19.0-alpine as images
# images-1) 작업 디렉토리 지정
WORKDIR /app
# images-2) 이전 FROM인 'builder'에서 생성된 빌드된 폴더 복사
COPY --from=builder /app/dist /app/dist
# images-3) node_modules 복사
COPY --from=builder /app/node_modules /app/node_modules
# images-4) 컨테이너 정상 실행 후 빌드된 결과로 app이 실행되도록 명령어 지정
CMD ["node", "dist/main"]
- 해당 이미지의 최종 결과는 앱을 켤 수 있는 상태로 충분하다.
/app/dist
devDependencies
가 제거된/app/node_modules
경량화 작업을 수행 비교 결과
latest
: 'images' 단계 없이 'builder'에서CMD
명령을 사용한 이미지로 최종 크기가 '348MB' 이다.slim
: 'images' 단계를 사용통해 경량화를 수행한 이미지로 최종 크기가 '197MB' 이다.
10. 정리
Dockerfile
는 Docker Image를 생성하기 위한 명령을 담은 파일이다.
정의된 Dockerfile
은 docker build
CLI에 파라미터로 전달되어 이미지로 생성된다.
Dockerfile
의 주된 명령들을 간단하게 정의하면 아래와 같다.
FROM
- 신규로 생성될 이미지의 Base 이미지를 지정RUN
- 이미지 빌드에 필요한 스크립트를 명령을 수행CMD
- 컨테이너가 정상 실행될 때 "기본값(명령)"을 전달(실행)하는 목적ENTRYPOINT
- 컨테이너 실행시 수행할 "명령"을 지정ENV
- 컨테이너가 사용할 환경 변수를 정의WORKDIR
- 작업할 공간인 디렉토리를 생성COPY
- 호스트에 있는 파일(디렉토리)을 복사
Dockerfile
은 쉬우면서도 제대로 사용하기는 어렵다.
도커에 익숙하지 않은 사람들은 누군가의 Dockerfile
을 복사하여 사용할 것이다.
필요에 따라 조금에 수정했는데 에러를 만나는 사람도 분명 있을 것이라고 생각한다.
게다가 빌드에 성공 이후 run 과정에서 에러가 발생하는 경우도 많다.
처음에 내가 그랬다 이해하지 않고 될 때까지 몇 번이고 빌드를 돌려가며 헤딩했다.
나와 같은 누군가 내가 정리한 글을 읽고 도움이 되었으면 좋겠다는 마음이다.
그래서 이번 장은 조금 더 신경써 정리하였다.
참고
'{시리즈} > Docker' 카테고리의 다른 글
4. 실습 - Docker Hub에서 node 특정 버전을 찾아서 내려 받기 실행하기 (0) | 2023.12.04 |
---|---|
4. Docker 명령어 알아보기 (2) | 2023.12.04 |
3. Docker 사용을 위한 환경 이해하기 (0) | 2023.12.01 |