# Docker image & Container 빌드, 관리

# 1\. 이미지 & 컨테이너

## 1.1. 이미지 vs 컨테이너

### 1.1.1. 이미지

도커에서 서비스 운영에 필요한 서버, 소스코드, 라이브러리 등 필요한 것들을 묶어서 가지고 있는 상태를 `Docker image`라고 한다. 그런데 이 이미지는 설정값과 파일들을 가지고 있지만 실행중이라는 것을 의미하지는 않는다.

### 1.1.2. 컨테이너

그 이미지를 실제로 실행하고 있는 것은 컨테이너라고 한다. 단순히 실행 안 하면 이미지, 실행하면 컨테이너라기보단 이미지가 어떠한 틀이라면 그걸로 컨테이너를 찍어낼(실행) 할 수 있는 것이다. 그래서 이미지 하나로 여러 컨테이너를 생성하는 것도 가능하고, 컨테이너를 삭제해도 이미지는 변하지 않는다.

---

## 1.2. Dockerfile

아까 설명한 이미지를 만들기 위해 작성하는 파일이다. 예시로

```Dockerfile
FROM node

WORKDIR /app

COPY . /app

RUN npm install

EXPOSE 80

CMD ["node", "server.js"]
```

이렇게 생긴 도커파일이 있을 수 있다. 위 도커파일을 살펴보면 처음에 `FROM`이 있는데,

### 1.2.1. FROM

FROM은 도커파일에서 베이스 이미지를 지정하는 지시이다. 도커 이미지를 만들 때 무의 상태에서 만들어가는 게 아니라 어떤 베이스를 가지고 그 위에 쌓는 게 일반적인데, 이런 베이스를 지정하는 것이다. 위의 코드에서는 node를 베이스로 하는 이미지를 만들 것이라는 뜻이다.

### 1.2.2. WORKDIR

컨테이너 내부의 작업 디렉토리를 /app 으로 설정한다는 의미이다. 이후의 모든 명령은 /app에서 실행되게 된다.

### 1.2.3. COPY

호스트 머신의 현재 디렉토리를 컨테이너의 /app디렉토리로 복사하는 것이다. 로컬에서 작업한 코드들이 이 과정을 통해 컨테이너로 들어가게 된다.

### 1.2.4. RUN

컨테이너 내부에서 실행할 명령이다. 위 도커파일에서는 `npm install` 명령을 실행해서 컨테이너 내부에 의존성들을 설치하게 된다.

### 1.2.5. EXPOSE 80

컨테이너가 80번 포트를 사용함을 명시한다. 그런데 이것은 실제로 기능하는 것이 아니라, 문서화 목적으로 쓰이는 것이다. 그래서 이 명령어를 쓴다고 실제 포트 매핑이 되는 것은 아니다. 실제 포트 매핑은 도커 실행 시 `-p` 옵션을 써서 진행해야한다.

### 1.2.6. CMD

컨테이너가 시작될 때 실행될 기본 명령이다. RUN은 빌드 시점에 실행되지만, CMD는 컨테이너 실행 시 작동한다. 그래서 RUN의 결과는 이미지에 포함되게 되고, 설치와 설정 작업에 이용된다. CMD는 컨테이너가 실행될 때 어떤 프로세스를 시작할지 지정하는 명령어로, CMD 여러 개를 써도 마지막에 지정한 프로세스가 시작된다.

---

## 1.3. Dockerfile로 빌드하기

도커파일이 준비되면 `docker build` 명령을 사용하여 이미지를 빌드할 수 있다.

```sh
docker build .
```

도커파일이 있는 위치에서 위 커맨드를 실행하면 이미지가 빌드된다.

빌드된 이미지를 바탕으로 컨테이너 실행은 `docker run`으로 진행할 수 있다. 빌드된 이미지는 읽기 전용이기 때문에 변경사항이 생기면 이미지를 다시 빌드하고 돌려야한다.

아까 말했듯 EXPOSE80 자체는 문서화 목적이라 실제 포트 매핑이 되지 않는다. 그래서 옵션 없이 실행하면 [localhost](http://localhost)로 빌드한 웹 앱에 접속할 수 없다.

```sh
docker run -p 3000:80
```

---

## 1.4. 이미지 레이어

도커 이미지는 레이어들로 구성되는데, 이 구조가 있어서 도커가 매번 명령을 처음부터 실행할 필요가 없다. 도커는 매번 명령을 캐싱해두었다가 이미지를 다시 빌드할 때 명령을 다시 실행할 필요가 없으면(변경사항이 없으면) 캐싱된 결과를 다시 가져와서 사용한다.

따라서 그 원리를 이용하면

```dockerfile
FROM node

WORKDIR /app

COPY . /app

RUN npm install

EXPOSE 80

CMD ["node", "server.js"]
```

도커파일에서

```dockerfile
FROM node

WORKDIR /app

COPY package.json /app

RUN npm install

COPY . /app

EXPOSE 80

CMD ["node", "server.js"]
```

으로 바꾸게 되면 npm install이 재실행되는 것을 막을 수 있다는 것이다.

---

# 2\. 이미지 & 컨테이너 관리

## 2.1. 컨테이너 생성, 실행, 중지

```bash
docker [command] --help
```

도커 명령어들에 `--help`을 달게 되면 사용법이 출력된다. `run`명령어와 `start`명령어는 다르다. `run`은 컨테이너를 생성하고 실행하지만, `start`는 기존에 존재하는 컨테이너를 시작하는 것이다. 따라서 `run`에는 이미지명이 들어가야하고 `start`는 컨테이너명이 들어간다. 컨테이너를 중지하려면 `stop`명령어를 사용하면 된다.

```bash
docker run [옵션] [이미지명]
docker start [컨테이너명]
docker stop [컨테이너명]
```

---

## 2.2. Attached & Detached

한국어로는 포그라운드 & 백그라운드로 번역하는 것 같다. attach detach 개념은 실행 여부와는 별개의 것인데, `attached`일 때 현재 실행하는 터미널에 컨테이너의 동작 상태를 출력하게 된다. `detached`라면 실행은 하고 있지만, 현재 터미널에 출력되고 있지는 않다.

`docker run`명령어 실행 시 디폴트로 `attached`, `docker start` 시 디폴트로 `detached`가 된다. 디폴트를 따라야 하는 것은 아니고

```bash
docker run -d [이름]
docker start -a [이름]
```

와 같이 옵션을 붙여주면 디폴트로 `attached`가 되어야하지만 `detached`로 실행된다.

실제 실행해보면

```bash
  ~/devops/study_docker/nodeapp ─────────────────────────────────────────────────────────────────────────────────────────────────  max-env at  21:22:49 ─╮
❯ docker run -p 3000:80 -d a1bab505873c                                                                                                                     ─╯
6be1ec3c5c838336604d90598df4c2798ae4a6e65e5232812a59e30d7b5a33e1
```

이런 식으로 나온다.

만약 다시 터미널에서 동작 상태를 보고 싶다면, 두 가지 방법이 있는데

### 2.2.1. 다시 Attach

```bash
docker attach [이름]
```

으로 다시 포그라운드에서 돌게 할 수 있다.

### 2.2.2. Log 가져오기

또 다른 방법으로는 컨테이너에 출력되는 로그를 `log` 명령어로 가지고 올 수 있다.

```bash
docker logs [이름]
```

으로 attached상태에서 출력되는 것들이 나오게 된다.

---

## 2.3. 인터렉티브 모드로 들어가기

[localhost](http://localhost)로 접속할 수 있는 형태가 아니라 터미널에서 상호작용하는 프로그램을 처리하려면 어떻게 해야하는가?

```python
from random import randint

min_number = int(input("Please enter the min number: "))
max_number = int(input("Please enter the max number: "))

if (max_number < min_number):
    print("Invalid input - shutting down..")
else:
    rnd_number = randint(min_number, max_number)
    print(rnd_number)
```

만약에 이런 코드를

```dockerfile
FROM python

WORKDIR /app

COPY . /app

CMD ["python", "rng.py"]
```

로 돜파일을 작성해 이미지를 빌드했을 떄, `docker run`을 그냥 실행해버리면 에러만 출력된다. 그 이유는 현재 입력을 터미널 외부에서 줄 수가 없는 상황이기 때문이다. `detached`로 바꿔도 입력을 줄 수 없는 것은 마찬가지라 터미널에서 상호작용이 가능하도록 하는 다른 옵션이 필요하다.

도움이 될 수 있는 옵션은 `-i`와 `-t`인데 각각 인터렉티브 모드와 터미널 생성을 의미한다. 이 옵션은 같이 쓰는 것도 가능하다.

```sh
docker run -it 023043bd53c8                                                                                                                               ─╯
Please enter the min number: 1
Please enter the max number: 1
1
```

```sh
docker start -a -i trusting_easley
```

로도 인터랙티브 모드를 쓸 수 있다.

---

## 2.4. 이미지 & 컨테이너 삭제하기

```bash
docker rm [컨테이너명]
```

으로 컨테이너를 삭제할 수 있지만 실행중인 컨테이너는 삭제할 수 없기 때문에 중지해야 한다.

이미지 삭제는

```bash
docker rmi [이미지]
```

를 입력하면 레이어를 비롯해서 이미지가 삭제된다. 그런데 이미지를 쓰고있는 컨테이너가 존재하는 경우 이미지를 삭제할 수 없기 때문에 컨테이너를 먼저 제거해야한다.

컨테이너가 중지되면 자동으로 삭제되게 할 수 있는데

```bash
docker run --rm [컨테이너명]
```

으로 중지될 때마다 자동 삭제되게 할 수도 있다. 노드 서버와 같은 것들은 코드 변경 같은 상황이 아니면 중지하지 않는 경우가 많은데, 이런 것들은 어차피 다시 빌드해야하기 때문에 자동삭제 옵션은 꽤 유용한 편이다.

---

## 2.5. 이미지 검사

```bash
docker image inspect [이미지]
```

로 이미지에 대한 정보를 확인할 수 있다. 엔트리 포인트, 도커 버전, 사용중인 운영체제에 대한 정보들도 포함한다. 레이어에 대한 정보들도 여기서 확인할 수 있는데, 도커 파일의 명령어 개수보다 많을 수 있다. 이것들은 베이스에 있는 것들이 포함된 것으로 볼 수 있다.

---

## 2.6. 컨테이너 파일 복사하기

호스트에 있는 파일을 컨테이너로 복사하려면

```docker
docker cp [복사할 파일 경로] [컨테이너명]:[컨테이너 내부 파일 경로]
```

로 복사할 수 있다.

반대로 컨테이너에서 호스트로 복사하려면

```bash
docker cp [컨테이너명]:[컨테이너 내부 파일 경로] [복사할 파일 경로]
```

로 쓰면 된다.

---

## 2.7. 컨테이너와 이미지 이름&태그

컨테이너를 만들면 아무 의미 없는 이름이 붙는다. 원하는 이름을 붙여주기 위해서는

```bash
docker run --name [원하는 이름] [이미지]
```

로 `--name`옵션을 이용하여 이름을 붙여줄 수 있다.

이름 변경 시에는

```bash
docker rename [과거 이름] [변경할 이름]
```

으로 바꿀 수 있다.

이미지도 마찬가지로 그냥 생성하면 숫자로 구성된 ID만 나오게 된다. 이미지는 이름:태그 로 구성할 수 있다.

```bash
docker build -t imagename:ver1 .
```

와 같이 `-t`옵션을 이용하면 된다.

이미지 이름, 태그 변경은

```bash
docker image tag [과거 이름:태그] [새로운이름:태그]
```

로 변경할 수 있다.

---

# 3\. 이미지 공유하기

## 3.1. Dockerhub에 이미지 push하기

도커 이미지는 개인 레포에도 올릴 수 있고, 도커허브에도 push 가능하다. 도커에는 이미지 공유를 위해 내장된 명령어가 있다.

Dockerhub에 가입하고 웹에서 리포지토리를 생성하고 여기에 이미지를 푸시하려면 `docker push`명령어를 사용하면 되는데, 이때 Dockerhub에서 생성한 리포지토리 이름과 로컬 이미지 이름이 같아야한다. 유저네임까지 포함해서 `username/dockerhubrepo`의 형식이어야 한다.

해당 이름의 이미지를 다시 빌드해도 되지만 이미지 이름을 변경해서 푸시해도 된다.

로그인을 안 하고 push하려고 하면 permission denied가 뜨는데, `docker login`으로 로그인해주면 된다.

## 3.2. 공유 이미지 가져오기

반대로 남이 Dockerhub에 올린 이미지도 가져올 수 있다. 명령어는

```bash
docker pull username/repo
```

로 로컬에 이미지를 가져올 수 있다.
