> [!date] published: 2022-10-26
[\[Docker\] Dockerfile 개념 및 작성법](https://wooono.tistory.com/123)
[Dockerfile reference \| Docker Docs](https://docs.docker.com/engine/reference/builder/)
## 🌟 Dockerfile
도커 이미지를 만들기 위한 명령어들이 적혀있는 스크립트 파일.
도커파일 명령어 한줄 한줄마다 이미지를 구성하는 레이어가 생긴다.
장점? :
- 사이즈가 큰 이미지 파일 자체를 배포하는 것 보다 더 용량이 작은 도커파일을 배포하는 것이 더 비용이 적게 든다.
- 어떤 식으로 도커 이미지가 구성되어있는지를 도커파일을 보고 이해할 수 있다.
## 🌟 Format
```docker
# Comment
INSTRUCTION argument
```
명령어는 항상 대문자일 필요는 없는데 대문자로 적는 쪽이 더 구별하기 쉬움
도커는 도커파일의 명령어를 순서대로 읽는다.
반드시 `FROM` 명령으로 시작해야 함. (Parser directive, Comments, 전역 ARGs가 먼저 올 수 있긴 함)
---
`FROM` 은 Parent Image를 나타냄.
📍 Parent Image : 이미지의 Parent Image를 `FROM` 명령을 이용해서 지정하면, 그 이후의 command들은 parent image에 기반하여 작동한다. 만약에 `FROM` 명령을 쓰지 않았다면, parent image를 사용하지 않는다는 뜻이므로 base image를 만든다.
[Glossary \| Docker Docs](https://docs.docker.com/glossary/#parent-image)
`#` 로 시작하는 줄은 유효한 parser directive가 아닌 이상 comment로 처리한다.
`#` 는 Dockerfile의 명령이 실행되기 전에 모두 삭제되기 때문에 중간에 들어와 있어도 문제가 없다.
## 🌟 Parser directives
Dockerfile의 내용들이 어떻게 처리될지에 영향을 미친다.
근데 이미지에 레이어를 추가하지는 않고, 빌드 단계에도 보여지지 않는다. (?)
특별한 형식으로 적혀 있어야 Parser directive로 처리되는 것 같다. (앞서서 유효한 Parser directive가 아니라면 그냥 comment로 처리된다고 했었음)
```docker
유효함
# directive=value
유효하지 않음 (형식에 맞지 않음)
# direc
유효하지 않음 (두번 등장)
# directive=value1
# directive=value2
유효하지 않음 (명령어 뒤에 등장)
FROM baseimage
# directive=value
유효하지 않음 (코멘트 뒤에 등장)
# this is comment
# directive=value
유효하지 않음 (알려지지 않은 directive)
# unknowndirective=value
공백은 상관없다곤 하는데 걍 왠만하면 맞추는게 좋지 않을까
```
## 🌟 `FROM`
새로운 build stage를 만들고, 이후 명령을 위한 Base Image를 설정한다.
유효한 Dockerfile이라면 반드시 `FROM` 명령으로 시작해야 한다.
Base image는 어떤 유효한 이미지라도 상관은 없지만 Public Repositories에 있는 이미지를 당겨오는 쪽이 편함.
- `ARG`는 Dockerfile에서 FROM보다 앞서서 등장할 수 있는 유일한 명령어.
- `FROM` 은 여러 이미지를 만들거나, 의존성이 있는 build stage를 만들 때 하나의 Dockerfile에서 여러번 등장할 수 있다.
각각의 `FROM` 명령은 기존의 state를 모두 clear하기 때문에 새로운 `FROM` 명령 전에 기존 상태를 노트해두면 된다.
- 그 밖에도 `FROM` 명령에 `AS name`을 붙여서 새로운 build stage에 이름을 붙일수도 있고, `tag`, `digest` 값을 넣어줄 수 있다는 것 같다.
~~(플랫폼 관련 내용은 나중에 필요하면 읽어보기…)~~
(나중에 읽어보기 : [https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact](https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact))
## 🌟 `RUN`
RUN 명령은 현재 이미지에서 command를 실행하고, 그 결과를 커밋한다.
이렇게 커밋된 결과는 다음 명령에서 사용되는 것
(이렇게 명령어를 쌓아서 만드는 도커의 컨셉이 장점이 많다는 이야기)
두가지 형태로 사용할 수 있다.
- `RUN <command>`
`RUN echo hello hi`
- `RUN [”executable”, “param1”, “param2”]`
`RUN [”echo”, “helllo”, “hi”]`
backslash 이용해서 줄 구분할 수 있음
이 아래로 RUN의 수많은 옵션들… 일단 해 보고 필요하면 읽어봄
[Dockerfile reference \| Docker Docs](https://docs.docker.com/engine/reference/builder/#run---mount)
## 🌟 `CMD`
가능한 CMD 형태
- `CMD ["executable","param1","param2"]`
- `CMD ["param1","param2"]`
- `CMD command param1 param2`
Dockerfile 안에 오직 하나의 CMD 명령만 들어갈 수 있음. 만약에 여러개 들어가있으면 가장 마지막 명령만 보인다.
주된 목적은 실행중인 컨테이너에게 “기본값”을 주는 것
이 기본값은 어떤 명령 실행이 될 수도 있고, (첫번째, 세번째 form) 아니면 다른 명령 실행에 주어질 기본 인자가 될 수도 있다. (이렇게 인자를 줘야 할 때는 당연히 `ENTRYPOINT` 명령으로 기본적으로 실행될 명령을 지정해줘야 한다.)
먄약에 CMD 명령으로 기본 인자를 지정할 때에는 `ENTRYPOINT` 명령 역시 JSON Format 으로 적어야 한다.
---
1, 2번 형태는 Shell로 실행되지 않기 때문에 3번째 형태와 같이 적어줘도 동일하게 동작하지 않을 수도 있다.
## 🌟 `ENTRYPOINT`
가능한 ENTRYPOINT 형태
- `ENTRYPOINT ["executable", "param1", "param2"]`
- `ENTRYPOINT command param1 param2`
컨테이너에서 실행될 명령을 지정.
> The *shell* form prevents any `CMD` or `run` command line arguments from being used, but has the disadvantage that your `ENTRYPOINT` will be started as a subcommand of `/bin/sh -c`, which does not pass signals. This means that the executable will not be the container’s `PID 1` - and will *not* receive Unix signals - so your executable will not receive a `SIGTERM` from `docker stop <container>`.
시그널을 받아서 뭔가를 해야 하는 상황이라면 shell form 을 사용하면 안될 것 같다.
- ENTRYPOINT와 CMD
[Dockerfile Entrypoint 와 CMD의 올바른 사용 방법](https://bluese05.tistory.com/77)
## 🌟 `VOLUME`
```docker
VOLUME ["/data"]
```
VOLUME 명령은 마운트 지점을 만들고, 컨테이너가 외부로 연결된 어떤 공간을 보유한다고 표시 (?)
JSON array 형태로 적어도 되고 일반 문자열로 적어도 된다.
```docker
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
```
## 🌟 `EXPOSE`
```docker
EXPOSE <port> [<port>/<protocol>...]
```
EXPOSE 명령으로는 만들고자 하는 컨테이너가 열어 둘 특정한 네트워크 포트를 지정할 수 있다.
TCP/UDP를 지정할수도 있고, 기본값은 TCP이다.
단순히 열어두기만 하는 것이지 실제로 그 포트를 사용하기 위해서는 docker run 시에 -p 플래그를 넣어서 하나 이상의 포트를 매핑해줘야 하는 것 같다.
```bash
docker run -p 80:80/tcp -p 80:80/udp ...
```
이런 식으로
더 자세한 내용은 [여기](https://docs.docker.com/network) 를 참고하라는 것 같다. (언젠간 필요할테니 나중에 읽어보는걸로…)
## 🌟 `COPY`
두가지 형태
- `COPY [--chown=<user>:<group>] <src>... <dest>`
- `COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]`
`COPY` 명령어로는 `<src>` 에 있는 파일이나 디렉토리를 컨테이너의 파일시스템의 `<dest>`로 복사할 수 있다.
`<dest>` 는 절대경로로 줄 수도 있고, `WORKDIR` 에 대한 상대경로로 줄 수도 있다.
## 🌟 `WORKDIR`
```docker
WORKDIR /path/to/workdir
```
`RUN`, `CMD`, `ENTRYPOINT`, `COPY`, `ADD` 명령의 working directory를 설정할 수 있다.
만약에 설정해둔 `WORKDIR`이 존재하지 않는다면 생성된다.
`WORKDIR` 명령은 여러번 등장할 수 있는데, 뒤에 나오는 경로가 상대경로일 경우에 기준은 앞선 `WORKDIR`이다.
기본값은 `/` 이다.
## 🌟 `USER`
```docker
USER <user>[:<group>]
# USER <UID>[:<GID>]
```
기본 user name과 group을 설정할 수 있음 (이건 옵션)
이렇게 설정한 user가 이후 나오는 RUN 으로 지정된 명령들을 실행한다.
```docker
FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick
```
RUN 명령으로 생성한 user를 지정해서 다음 명령을 실행하게 할 수도 있는 것 같다.
## 🌟 `ARG`
```docker
ARG <name>[=<default value>]
```
docker build 커맨드로 값을 지정할 수 있는 변수들을 정의하는 명령
기본값을 설정할 수 있다. 만약에 docker build 커맨드의 인자로 값을 지정하지 않았다면, 기본값을 사용한다.
~~(Scope 생략)~~
### ✨ ARG variable 사용하기
```docker
FROM ubuntu
ARG CONT_IMG_VER
RUN echo $CONT_IMG_VER
```
### ✨ 사전 정의된 ARGS
- `HTTP_PROXY`
- `http_proxy`
- `HTTPS_PROXY`
- `https_proxy`
- `FTP_PROXY`
- `ftp_proxy`
- `NO_PROXY`
- `no_proxy`
- `ALL_PROXY`
- `all_proxy`
## 🌟 Docker image 만들어보기
[도커 시작하기 7 : Dockerfile을 이용한 이미지 생성 \:\: 자바캔(Java Can Do IT)](https://javacan.tistory.com/entry/docker-start-7-create-image-using-dockerfile)
```docker
FROM alpine:3.10
ENTRYPOINT ["echo", "hello"]
# ENTRYPOINT echo hello
```
```bash
docker build --tag echoalpine:1.0 .
docker images
docker run --rm echoalpine:1.0
```
![[73fd56d5-b315-4a48-818b-53b02b87af35.png]]