1. Dockerfile 이란?
공식문서의 정의(Dockerfile reference)
Docker can build images automatically by reading the instructions from a
Dockerfile. ADockerfileis 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/workdircd /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/distdevDependencies가 제거된/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 특정 버전을 찾아서 내려 받기 실행하기 (2) | 2023.12.04 |
|---|---|
| 4. Docker 명령어 알아보기 (4) | 2023.12.04 |
| 3. Docker 사용을 위한 환경 이해하기 (3) | 2023.12.01 |