Docker 기초 (5) - Docker Compose: Commands and YAML
4편의 MySQL run 명령을 compose.yaml 파일로 옮기고, up, ps, logs, exec, down 명령으로 다루는 Docker Compose의 기본을 실습으로 정리합니다.
Docker 기초 시리즈의 5편입니다. 전체 목차는 0편에 있습니다.
What is Docker Compose
Docker Compose는 container 실행에 필요한 설정을 파일에 선언해 두고, 명령 한 번으로 실행하는 도구입니다. 4편에서 MySQL을 띄운 명령을 다시 보겠습니다.
1
2
3
4
5
docker run -d \
--name db \
-e MYSQL_ROOT_PASSWORD=dev \
-v mysql-data:/var/lib/mysql \
mysql:8.4
옵션이 붙으면서 다섯 줄이 됐고, 매번 정확히 다시 쳐야 하고, 팀원에게 공유하려면 따로 적어서 전달해야 합니다. Compose를 사용하면 이 옵션들을 compose.yaml이라는 파일로 관리하고, 실행은 docker compose up 한 번으로 끝납니다. 이번 편에서는 위 MySQL 명령을 Compose로 옮기면서 기본 명령어들을 알아보겠습니다.
참고로 명령 이름은 docker compose(공백)입니다. 예전에는 별도 프로그램인 docker-compose(하이픈)를 따로 설치했는데, 지금은 Docker에 포함되어 있습니다.
What is YAML
Compose가 읽는 파일은 YAML 형식으로 씁니다. YAML은 설정 파일을 적는 문법이고, 규칙 세 개만 알면 읽을 수 있습니다.
1
2
3
4
5
6
Name: Value # 키: 값. 콜론 뒤에 공백 하나
Parent:
Child: Value # 들여쓰기(공백 2칸)가 소속을 표현
List:
- First # 하이픈이 목록의 항목
- Second
괄호나 따옴표 없이 들여쓰기로 구조를 표현합니다. 들여쓰기 자체가 문법이므로 칸 수가 어긋나면 소속이 달라지고 에러가 납니다.
Setting
실습 환경을 아래와 같이 만들어보겠습니다. 파일 이름은 기본적으로 compose.yaml을 사용합니다. 다른 이름으로 저장했다면 docker compose -f server.yaml up -d처럼 -f로 파일을 지정하면 됩니다.
1
2
compose-lab/
└── compose.yaml
기존에 쓰던 run 명령을 compose.yaml로 옮기겠습니다.
1
2
3
4
5
docker run -d \
--name db \
-e MYSQL_ROOT_PASSWORD=dev \
-v mysql-data:/var/lib/mysql \
mysql:8.4
1
2
3
4
5
6
7
8
9
10
services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: dev
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:
run 옵션과 일대일로 대응됩니다.
| run 명령에서 | compose.yaml에서 |
|---|---|
--name db | services 아래의 service 이름 |
mysql:8.4 (image 인자) | image |
-e MYSQL_ROOT_PASSWORD=dev | environment |
-v mysql-data:/var/lib/mysql | volumes |
-d | 파일이 아니라 up 명령의 옵션 |
맨 아래의 volumes: 블록은 named volume의 선언입니다. run 때는 -v에 적으면 자동으로 만들어졌지만, Compose에서는 사용하는 volume을 파일 끝에 한 번 더 선언하는 규칙입니다.
docker compose up
compose-lab 폴더 안에서 실행합니다.
1
2
docker compose up -d
① ② ③ ④
- ①
docker: Docker CLI를 실행합니다 - ②
compose: compose.yaml을 읽어 실행하는 하위 명령 묶음입니다 - ③
up: compose.yaml에 선언된 것을 기준으로 실행합니다. volume이 없으면 만들고, image가 없으면 pull 합니다 - ④
-d: run의 -d와 같은 백그라운드 실행입니다
1
2
3
4
$ docker compose up -d
[+] Running 2/2
✔ Volume "compose-lab_mysql-data" Created
✔ Container compose-lab-db-1 Started
run으로 하나씩 하던 일(volume 생성, container 생성과 시작)이 한 번에 끝났습니다. container 이름은 Compose가 폴더이름-서비스이름-번호 형식으로 자동으로 짓습니다. 이번 실습에는 db 하나뿐이지만, web service를 추가했다면 이름이 아래처럼 붙습니다.
| service 이름 | Compose가 지은 container 이름 |
|---|---|
db | compose-lab-db-1 |
web | compose-lab-web-1 |
이 긴 이름을 쓸 일은 별로 없습니다. compose 하위 명령들은 전부 service 이름으로 대상을 지정하기 때문입니다.
1
2
3
4
5
docker compose logs db
docker compose stop db
docker compose restart db
docker compose exec db bash
docker compose up -d web
docker compose ps, logs, exec
4편에서 배운 명령들이 compose 아래에 같은 이름으로 있습니다. 차이는 container 이름 대신 service 이름을 쓴다는 것입니다.
1
2
3
docker compose ps # 이 파일이 띄운 container만 목록
docker compose logs db 2>&1 | grep "ready" # 준비 확인. 4편과 같은 요령
docker compose exec db mysql -uroot -pdev -e "SELECT 1;"
docker ps가 전체 목록인 것과 달리 docker compose ps는 이 compose.yaml이 띄운 것만 보여줍니다. 다음 실험을 위해 데이터도 하나 넣어 두겠습니다.
1
docker compose exec db mysql -uroot -pdev -e "CREATE DATABASE demo; CREATE TABLE demo.memo (body VARCHAR(100)); INSERT INTO demo.memo VALUES ('from compose');"
docker compose down
1
2
docker compose down
① ② ③
- ①
docker: Docker CLI를 실행합니다 - ②
compose: compose.yaml 기준으로 동작합니다 - ③
down: up이 만든 container와 network를 멈추고 삭제합니다. volume은 남깁니다
stop과 rm을 하나씩 치던 4편의 정리 절차가 명령 하나가 됐습니다. volume이 남는지는 실험으로 확인해보겠습니다.
1
2
3
docker compose down # container 삭제
docker compose up -d # 다시 생성
docker compose exec db mysql -uroot -pdev -e "SELECT * FROM demo.memo;" # 준비 확인 후
1
2
body
from compose
container를 지웠다 새로 만들었는데 데이터가 그대로입니다. 4편의 -v 실험과 같은 원리이고, 그때의 명령 전부가 파일 하나와 up, down 두 마디로 줄었습니다.
실습을 마치면 volume까지 지웁니다. down에 -v를 붙이면 파일에 선언된 volume도 함께 삭제됩니다.
1
2
docker compose down -v # 데이터까지 삭제되므로 실무에서는 확인하고 쓴다
rm -rf compose-lab # 실습 폴더 삭제
부록: 함께 알아둘 docker 명령어
시리즈 실습에는 등장하지 않지만 알아두면 좋은 docker 본체 명령들입니다. 여기서는 목록만 잡아두고, 필요할 때 --help로 확인하면 됩니다.
docker pull
registry에서 image를 받아오는 명령입니다. 2편의 registry 개념이 명령으로는 아래와 같습니다.
1
docker pull python:3.12-slim # image 받기
pull을 직접 칠 일은 생각보다 적습니다. docker run과 docker compose up이 로컬에 없는 image를 알아서 pull 하기 때문입니다. 명시적으로 쓰는 경우는 두 가지입니다.
- 배포 전에 미리 받아둘 때
- latest처럼 움직이는 tag를 쓸 때
로컬에 latest가 있으면 run은 버전을 확인하지 않으므로, 갱신하려면 pull을 직접 실행해야 합니다.
docker tag
1
docker tag hello <Docker계정이름>/hello:1.0 # 올릴 주소 형식으로 이름표 추가
<Docker계정이름> 자리에는 본인의 Docker Hub 계정 ID를 넣습니다. image 이름의 전체 형식이 registry주소/계정이름/저장소이름:태그이고, 앞의 두 조각을 생략하면 각각 docker.io와 library로 채워지기 때문입니다.
mysql:8.4에 계정 이름이 없는 것은 공식 image라 library 계정에 있어서이고, 내가 만든 image를 올리려면 내 계정 이름을 직접 붙여야 합니다. login한 계정과 이 이름이 다르면 push는 권한 오류로 거절됩니다.
docker login, push
1
2
docker login # registry 인증
docker push <Docker계정이름>/hello:1.0 # registry에 올리기
앞의 tag는 로컬에서만 일어나는 일입니다. image를 복사하지 않고 같은 image에 이름표를 하나 더 붙일 뿐이라, 그 시점까지는 아무것도 registry로 나가지 않습니다. 실제로 올라가는 시점이 push입니다.
docker top, diff, history
1
2
3
docker top web # container 안의 프로세스 목록. 메인 프로세스가 PID 1로 보인다
docker diff web # image 대비 container가 바꾼 파일 목록. 쓰기 layer의 내용물
docker history hello # image가 어떤 layer로 쌓였는지와 각 층의 크기
셋 다 바꾸는 것 없이 확인만 하는 명령이고, 각각 4편의 프로세스, 2편의 쓰기 layer, 6편의 layer 구조를 눈으로 확인하는 용도입니다.
docker stop, start, kill
1
2
3
docker stop web # 종료 요청(SIGTERM)을 보내고 정상 종료를 기다린다
docker start web # 종료된 container를 그대로 다시 시작
docker kill web # 즉시 강제 종료(SIGKILL). stop이 안 먹을 때의 최후 수단
4편 정리에서 쓴 stop이 이것입니다. rm과 달리 stop과 start는 container를 지우지 않으므로, start 하면 데이터와 설정이 그대로인 채 다시 뜹니다.
그 외
docker save/docker load: image를 tar 파일로 뽑고 다시 넣습니다. registry 없이 폐쇄망으로 옮길 때 씁니다docker commit: 실행 중인 container를 image로 굳힙니다. 가능은 하지만 Dockerfile 없이 만든 image는 만드는 과정이 기록되지 않아 재현이 안 되므로, 실무 관행은 쓰지 않는 것입니다
다음 편에서는 남이 만든 image를 쓰는 단계를 넘어, Dockerfile로 내 image를 만듭니다.
다음 글: Docker 기초 (6) - Dockerfile: Layers, Cache, and Multi-stage Builds