# Docker-Utility Containers

# 유틸리티 컨테이너

## 유틸리티 컨테이너란?

유틸리티 컨테이너는 애플리케이션이 담긴 컨테이너가 아니라 특정 환경만 포함하는 컨테이너를 의미한다. 예를 들면 NodeJS환경이나 PHP환경같은 것을 말하는 것이다.

## 왜 사용하는가?

기본적으로 호스트 머신에 환경을 구축하지 않고 이를 도커를 통해 사용하기 위해서 이용한다.

---

# 컨테이너에서 명령을 실행하는 다양한 방법

`docker run node`를 터미널에서 입력하면 컨테이너가 그대로 죽는다. interactive모드로 실행해야 정상 작동하기 때문이다.

`docker run -it node` 로 실행하면 터미널에서 interactive모드로 노드 컨테이너를 돌려서 실행할 수 있지만 우리가 원하는 건 detached모드에서(백그라운드에서 실행하면서) 컨테이너가 죽지 않게 하는 것이다. 이것은 간단하게

```bash
docker run -it -d node
```

`-d`옵션 하나만 더 붙여줘도 가능하다.

이를 실행하고 `docker ps`를 통해 실행중인 컨테이너를 확인하면

```bash
docker ps                                                                  ─╯
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
4551887165c2   node      "docker-entrypoint.s…"   4 seconds ago   Up 3 seconds             eager_dubinsky
```

이런식으로 컨테이너가 생성된 것을 볼 수 있다.

그런데 이제 위에 만들어둔 컨테이너 내에 `npm init`이든, 뭐든 컨테이너 내부에 명령을 실행하고 싶을 수 있다. 그런데 이제 interactive모드에서 직접 터미널에서 바로 명령을 주는 것이 아니라, 저렇게 백그라운드에서 둔 상태로.

이를 위해 `docker exec` 명령어를 쓸 수 있다.

`docker exec`는 아까 말한 바와 같이 컨테이너에 특정 명령어를 실행할 수 있게 한다.

아까 `docker run node`는 `-it` 옵션을 붙여서 실행해야했으므로 마찬가지로

```bash
docker exec -it node_container_name npm init
```

을 실행하면 패키지에 대해 입력을 받는 부분들이 나오고 컨테이너에 패키지가 설치된다. 이러면 호스트 머신에 이들을 설치하지 않아도 환경을 만들 수 있다.

---

## 유틸리티 컨테이너 구축하기

만약 아래와 같은 도커파일을 작성했을 때

```dockerfile
FROM node:14-alpine

WORKDIR /app
```

아래에 `CMB npm init`을 한 줄 더 적어줄 수도 있지만 `init`명령만 실행하는 것보다 유연하게 만들고자 한다.

그래서 도커파일은 위 상태 그대로 놔두고 이미지를 빌드해준 후 아까 했던대로 `npm init`명령어를 컨테이너에서 실행하고자 한다.

그런데 여기서 명령어는 컨테이너 내부에서만 돌기 때문에 이를 호스트 머신에서 관측하기 위해 미러링을 하려고 하는데, 이건 바인드 마운트를 이용해줄 수 있다.

```bash
docker run -it -v /home/max/devops/study_docker/section7:/app node-util npm init
```

와 같이 실행해주면 호스트 머신에도 package.json(컨테이너의 npm init 옵션에서 사용한 것들이) 생성된다.

---

## ENTRYPOINT

`ENTRYPOINT`는 `CMD`와 외관상 유사해보이는 명령인데, 작동 방식은 조금 다르다.

`CMD`명령의 경우 `CMD`를 도커파일에 지정해놓아도 위에서 실행한 `npm install`명령을 실행해주면 도커파일에 있는 것을 덮어씌우고 진행되지만,

`ENTRYPOINT`의 경우 덮어씌우는 것이 아니라 그 뒤에 들어가게 된다. 그러니까

```Dockerfile
FROM node:14-alpine

WORKDIR /app

ENTRYPOINT ["npm"]
```

의 도커파일로 이미지를 빌드했다면

```bash
docker run -it -v /home/max/devops/study_docker/section7:/app node-util npm init
```

이렇게 `init`만 붙여도 되는 것이다.

마찬가지로

```bash
docker run -it -v /home/max/devops/study_docker/section7:/app node-util npm install express --save
```

도 같은 방식으로 실행해줄 수 있다.

---

## Docker Compose 사용하기

도커파일이 하나이지만 도커 컴포즈를 여기서도 유용하게 사용할 수 있다.

```yaml
version: "3.8"
services:
  npm:
    build: ./
    stdin_open: true
    tty: true
    volumes:
      - ./:/app
```

아까 엔트리포인트를 사용한 도커파일을 둔 디렉토리에 이런 식으로 도커 컴포즈 파일을 작성했을 때

단순히 `docker compose up`을 하게 되면 `ENTRYPOINT ["npm"]`밖에 없기 때문에 `npm init`이라든가 그 이후의 명령은 바로 실행되지 않는다.

그래서 사용할 수 있는 것이 `docker compose run`인데,

```bash
docker compose run npm init
```

을 실행해주면 우리가 원하는대로 `npm init`을 돌려줄 수 있다. 여기서 `npm`은 컴포즈 파일의 서비스 이름에서 온 것이고, `init`은 엔트리 포인트 이후의 명령이다.

그런데 `docker compose run`은 `docker compose up`과 다르게 `docker compose down`에 대응하는 것이 없다. 기본적으로 컨테이너가 시작되어 명령이 완료되면 종료되지만, 자동으로 제거되지는 않는다.

`docker compose run --rm`으로 `--rm`을 달아주면 종료될 시 자동으로 제거되게 할 수 있다.
