제목 : Docker-Compose 란 무엇이고 어떻게 사용하는가? - With Spring & Nginx & MySQL
부제목 : 도커를 명령어 없이 쉽게 사용하기 위한 방법
이 글을 작성하는 이유
나는 NestJS 에서 Spring 으로 백엔드 도메인을 옮기는 상황이다.
현재 Udemy 강의 사이트에서 외국 강사 분의 강의를 들으며 미약했던 Spring 의 기억을 끌어올리고 있다.
이 강의는 데이터베이스를 MySQL, & Workbench 를 통한 쉬운 스키마 변경을 유도하고 있다.
그러나, 나는 서버 운용 상황에서의 유연성을 고려하여 docker container 를 통해
데이터베이스 서버를 3306 로컬 포트에 열고, 이를 연결 할 계획이다.
NestJS 에서 Spring 으로 백엔드 도메인을 바꾼 이유는 이러하다.
단순 트렌드를 넘어서, Web & WAS 간의 개발 생산성을 비약적으로 끌어올리기 위해서라면,
즉, 모든 영역에 JavaScript(with TypeScript) 를 사용하고자 한다면,
NestJS 는 옳은 선택이다.
그러나, 나는 프로그래머스 타입스크립트 풀스택 부트캠프를 수료했으며,
이 과정에서 팀플 2개에서 백엔드 도메인을 맡고, NestJS 로 구현했다.
타입스크립트와 방대한 오픈소스 의존성은 매혹적이었으나,
부트캠프가 끝나고 NestJS 자체의 성능을 더 끌어올릴 수 있는 방안을 모색하면서,
오히려 프로젝트에서 성능을 끌어올리기 위해 저수준 언어보다 개발시간이 더 들어간다는 것을 알게 되었다.
혹은, 비정상적인 서버 Scaling 을 통해 성능을 해결해야 하는 상황이었다.
나는 NestJS 가 가진 고유의 한계성을 깨기 위해
JavaScript의 기능을 분석하고 글을 작성했었다.
- node.js 로 멀티 스레드 구현하기 (Worker)
- WebAssembly 와 Node.js
- JavaScript 개발자를 위한 스프링, NestJS 에 대해서
- 내가 Node.js 에서 Spring 으로 백엔드 영역을 변경하는 이유
- JavaScript, TypeScript 에서 진짜 Private 구현하기
조금의 이유를 덧붙이자면, Spring 은 리소스가 많이 필요하나(메모리),
jvm 을 포기하고 바이너리 파일로 변경하여 70% 정도의 메모리를 절약할 수 있다.(GraalVM Image)
그러나 Node.js 에서는 가능한 일이 아니다.
물론, 웹 어셈블리를 통해서 모듈로 붙일 수는 있겠으나, 이는 Node.js 를 사용하는게 아니라,
저 수준의 타 언어가 베이스가 되어야 하는 역설적인 상황이 벌어진다.
다시 돌아와서
나는 바로 이전의 글에서 Dockerfile 에 대한 문법 기술과 더불어,
이미지 생성과 컨테이너 생성 방식에 대해서 논했다.
Docker 는 어떻게 운용되며 실행할까? (Dockerfile + CLI)
사실상 이 글의 다음편과 동일하다고 생각하고 있다.
Docker Compose 란?
먼저 공식 문서에 나와있는 문구를 읊어보면,
Docker Compose 는 다중 컨테이너를 정의하고 실행하는 "어플리케이션" 이라고 말한다.
이를 진행하는 방법은 바로 YAML 설정 파일이다.
여기서부터는 "설정 파일 조작" 을 통한 인프라 오케스트레이션이 본격적으로 적용된다고 생각한다.
직전의 파일에서 Dockerfile 을 통해 "생성될 이미지" 를 빌드하는 데 초점을 맞췄다면,
이번에는 "생성할 컨테이너" 를 만드는 데 초점을 맞춘다.
Docker Compose 가 유용한 이유
docker 의 compose 기능이 자주 사용된다니까~ 라는 접근법으로 공부하게 되면,
우리는 강의나 공식문서에 철저하게 휘둘릴 수 밖에 없다.
내가 그렇게 몇년을 공부했고, 모든 것을 잊었기 때문이다.
Docker Compose 를 다뤄야만 하는 이유를 아는 것이 중요하다고 판단된다.
단일 프레임워크나, 단일 기능을 수행하는 프로젝트만을 샘플로 보고 싶다면,
Docker Compose 는 그다지 큰 역할을 해 주지는 않는다.
그냥 명령어로 해결하면 되기 때문이다.
예를 들어서,
나는 Nginx + Spring 조합으로,
컨테이너로 띄워진 Spring 이 직접적으로 요청을 받지 않고,
Nginx 가 Reverse Proxy 기능을 수행하여 대신 요청을 받고, 응답하도록 만들고 싶다.
그 이유는, 나중에 내가 클라우드 인프라에 적용하게 될,
- SSL Termination - Nginx 가 인증을 수행함
- Load Balancing - Nginx 가 들어오는 요청을 복제된 서버들에 골고루 분산해줌
- 경로 변환 - 서버 내에 들어있는 몇 가지 프로젝트에 요청이 들어 갈 때, 내부에서 경로를 변경함.
이 말고도 Nginx 는 정적 파일을 매우 빠르게 반환해 준다는 장점이 있다.
클라우드에서 제한된 컴퓨팅 리소스를 절제하는 중요한 인프라 중 하나이다.
Docker Compose 가 "없을 때" 예시
우리는 이미지를 하나씩 커스텀하거나 만들어서 올려야 한다.
이 때, Spring 서버의 이미지는 "이미 만들어져서 docker 에 저장된 상황" 이라고 가정한다.
flowchart TB
Client
subgraph network["docker network"]
Nginx("Nginx - 노출 포트 8080")
Spring("Spring - spring1 이라는 이름으로 nginx 에게 연결됨")
end
Client --> Nginx --> Spring
1. 도커 네트워크 생성
일종의 가상 망을 까는 행위인데, port 가 아닌 내부 IP 로 들어갈 수 있기 때문에,
특정 도커 네트워크에 소속될 컨테이너가 노출 포트를 지정하지 않는 한,
도커 네트워크에 소속된 컨테이너에 요청을 보낼 수 있는 방법은 없다.
프로그램의 논리로 네트워크 차단망을 생성한다는 의미와 동일하게 보면 된다.
example :
# test-spring-network 라는 bridge(default) 형태의 가상 망 생성
$ docker network create test-spring-network
2. 스프링 프로젝트 컨테이너화 및 옵션 입력
아주 간단한 스프링 프로젝트, (/) 만 구현된 형태라고 생각하면 된다.
이 프로젝트는 현재 이미지화 되어, test-spring 이라는 image name 를 가지고 있다.
이제 이 이미지를 실행하며 컨테이너로 만들려고 하는데,
문제는 옵션을 매우 잘 적어 주어야 한다.
- 생성될 컨테이너의
NAME--> nginx 가.conf파일에서 쉽게 인식하기 위함 - 생성될 컨테이너가 속할 도커 네트워크 선언 --> 직접 호출 API 서버가 아님.
- 실행할 이미지 이름
$ docker run -d \
--name=spring1 \
--network=test-spring-network \
test-spring
만약에, 스프링 서버를 단일 컨테이너로 테스팅을 하고 싶었다면,
$ docker run -d -p 8080:8080 test-spring
이렇게 간단히 실행 할 수도 있다.
그러나, 위에 port 정보가 없이,
이 이미지 기반으로 실행될 "컨테이너의 이름" 그리고 "소속될 도커 네트워크 이름"
으로 실행했으므로, 접근 할 수 없다. (물론 하려면 특정 IP 로 가능하긴 하겠지만.)
따라서 실행된 컨테이너는 이러한 형태를 띈다.
flowchart TB
Client("local Client")
subgraph test-spring-network["test-spring-network"]
spring1("Container Name : spring1 <br/> 노출 PORT : 없음")
end
Client -- 아직 호출 불가 --> test-spring-network
3. Nginx 컨테이너 실행 및 도커 네트워크의 노출 포트가 생성됨
Nginx 또한 컨테이너로서 실행되며, 소속 네트워크를 지정한다.
그 과정에서 옵션으로 노출 포트:인식 포트 를 지정하게 되는데,
결과적으로 소속 네트워크 논리는 노출된 포트를 기점으로 Nginx 에 요청을 보낼 수 있게 해 준다.
이 상황에서 예시로 들 것은,
nginx공식 이미지를 사용한다./etc/nginx/conf.d라는 nginx 서버의 폴더에, 나의.conf파일을 넣는다.- 위의
.conf파일은 나의 로컬 볼륨을 참조한다.
그리고 넣게 될 conf 파일의 예제는 아주아주 간단하게 이렇게 생겼다고 가정한다.
server {
# 이 Nginx 는 80 포트로 수신하겠다
listen 80;
# 루트 uri 로 들어왔을 때 행동
location / {
return 200 "Nginx Root URL";
}
# /api/v1/ uri 로 들어왔을 때, "spring1" 컨테이너의 8080 포트로 요청을 보내겠다.
location /api/v1/ {
proxy_pass http://spring1:8080/;
}
}
위의 nginx 설정 파일이 들어간다고 가정한다.
docker 명령어 실행 :
$ docker run -d \
--name=test-nginx-network \
--network=test-spring-network \
-v "$(pwd)/custom.conf:/etc/nginx/conf.d/default.conf" \
-p 8080:80 nginx
위의 명령을 통해 "공식 nginx 이미지" 를 기반으로,
나의 커스텀 파일을 하나 넣어 8080 포트로 spring 에 API 요청을 넣을 수 있게 된다.
현재 상황은,
flowchart TB
Client
subgraph network["docker network"]
Nginx("Nginx - 노출 포트 8080")
Spring("Spring - spring1 이라는 이름으로 nginx 에게 연결됨")
end
Client -- localhost:8080/api/v1/* --> Nginx
Nginx -- spring1:8080/* --> Spring
위와 같은 형태가 구성된다.
Docker Compose 가 있을 때 예시
위에서 간단히, Nginx 와 Spring 을 하나의 도커 네트워크로 묶고,
Nginx 가 Reverse Proxy 로 구성하는 데에 많은 명령과 그 옵션이 필요하다.
심지어 위에서는 이미지가 "이미 만들어진" 경우를 상정 한 것이다.
즉, Dockerfile 내용은 뺀 상황이다.
내가 말하고자 하는 것은 컨테이너를 띄우고, 볼륨이나 네트워크를 조정하고,
재시작을 위해 기존 컨테이너를 정지 및 내려야 하는 불필요한 명령이
굉장히 번거로울 수 밖에 없다는 것을 의미한다.
게다가, Dockerfile 자체에서 사용할 수 없는 명령들이 존재한다.
도커파일은
- 어떤 공식 이미지를 사용 할 것인지 -
FROM - 어떤 디렉토리에서 작업 할 것인지 -
WORKDIR - 어떤 로컬 파일을 내부에 복사 할 것인지 -
COPY - 컨테이너 서버 내부에 어떤 환경 변수를 넣을 것인지 -
ENV - 하나의 컨테이너가 필요한 어떤 의존성을 설치 할 것인지 -
RUN(명령어) - 컨테이너 실행 직후 어떤 명령어를 실행 할 것인지 -
CMD(명령어)
위의 Dockerfile 을 위한 예약어들은,
"하나의 이미지를 만들기 위한" Layer 를 쌓는 것이다.
이는 docker 라는 시스템 안의 인프라를 조정하기 위한 문법이 아니라는 것이다.
따라서, Docker Compose 를 사용하지 않고, 위에서는
각 컨테이너가 필요한 옵션들을 명령어로 직접 넣어주었다.
이는 나중에 유지보수, 및 확장, 수정에 굉장히 불리하다.
이러한 문제를 해결하고 하나의 파일로 일부 인프라 체계를 순식간에 구축 해 주는 것이,
바로 Docker Compose 이다.
먼저, 내가 Docker Compose 를 매우 잘 다루는 것이 아니기 때문에,
상정 할 것이 있다.
Spring 프로젝트가 이미 빌드되어 "이미지화" 되어 있다고 가정한다.
그리고 밑의 yaml 코드는 Gemini 에게 compose 문법에 대해 질문하여 만들었다.
version : '3.8'
networks :
test-spring-network :
# 이 네트워크는 이미 선언 해 놨으므로, driver : bridge 라고 선언하지 않는다.
external : true
# 실행될 컨테이너들을 지정한다.
services :
# 실행될 스프링 컨테이너에 대한 메타데이터 지정
spring-project :
# 이미 빌드된 "test-spring" 을 기준으로 실행한다.
image : test-spring
# 스프링 컨테이너의 이름은 "spring1" 으로 지정한다.
container_name : spring1
# 사용할 네트워크는 위의 네트워크에서 가져와 준 test-spring-network 를 이용한다.
networks :
- test-spring-network
# 실행될 nginx 컨테이너에 대한 메타데이터 지정
nginx-reverse-proxy :
# 위에서 "공식 nginx 이미지" 를 사용했기 때문에, 공식 이미지 최신을 가져오는 방식으로 지정했다.
# 만약에 이미 빌드된 nginx 를 사용한다면,
# image : <빌드한 nginx 이미지 이름> 으로 선언한다.
image : nginx:latest
container_name : test-nginx-network
# nginx 가 test-spring-network 컨테이너 중 유일하게 외부에 노출됨.
ports :
- "8080:80"
# spring 프로젝트와 동일한 네트워크 사용
networks :
- test-spring-network
# 컨테이너 실행 시 참조 할 로컬 폴더와 추가 혹은 수정 및 덮을 파일 매칭
volumes :
- ./custom.conf:/etc/nginx/conf.d/default.conf
위의 파일은 yml or yaml 로 불리는 파일의 형태로 작성 한 건데,
docker-compose 만의 예약어가 존재한다.
하지만, 도커 자체의 내부 논리 인프라를 기억하거나 이해한다면,
어렵지 않게 이해하고 작성 할 수 있다.
이 컴포즈(compose) 파일을 사용하기 전, 먼저 기존에 띄워진 컨테이너를 멈추고 삭제하자.
docker compose 결과
나는 spring 프로젝트가 존재하는 root 에 따로 custom.conf 파일을 위치시켜 놓았다.
한번, 요청으로 nginx 와 spring 이 잘 동작하고 있는지 확인 해 보자 :
Docker Container 상황 :
➜ core-5 docker compose up
[+] Running 2/2
✔ Container test-nginx-network Created 0.0s
✔ Container spring1 Created 0.0s
Attaching to spring1, test-nginx-network
test-nginx-network | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
....
➜ ~ docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e2525cc48a28 test-spring "java -jar app.jar" 7 minutes ago Up 7 minutes spring1
9a12e5e24654 nginx:latest "/docker-entrypoint.…" 7 minutes ago Up 7 minutes 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp test-nginx-network
# nginx 가 동작중인 것을 볼 수 있다.
# location / 해당
➜ ~ curl -X GET "http://localhost:8080"
nginx 게이트웨이는 동작중.
# reverse proxy 가 정상적으로 동작중인 것을 볼 수 있다.
# location /api/v1/ 해당
➜ ~ curl -X GET "http://localhost:8080/api/v1/"
헬로 월드를 도커 내부에서 실행 - 루트 url
# 뒤에 따라붙은 "/result" 가 spring 프로젝트에 잘 전달 된 것을 볼 수 있다.
# location /api/v1/ 에 해당
➜ ~ curl -X GET "http://localhost:8080/api/v1/result"
This is BeanResultSample
docker compose 취소하는 2 가지 방법
첫 번쨰로, Ctrl + c 키보드를 눌러 취소하게 된다면,
언제든 다시 빠르게 컨테이너를 실행 할 수 있게 docker container stop ..
과 같은 상태로 존재한다.
그러나, docker compose up 를 실행한 현재 디렉토리 위치에서
또 다른 터미널을 열어 docker compose down 을 실행하면,
➜ core-5 docker compose down
[+] Running 2/2
✔ Container test-nginx-network Removed 0.2s
✔ Container spring1 Removed
➜ ~ docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
깔끔하게 docker compose up 으로 생겨난 2 개의 컨테이너가
삭제 된 것을 볼 수 있다.
이제 MySQL 만을 위한 compose 파일을 제작 해 보자.
왜 MySQL 을 Compose 파일로 제작해야 할까?
수많은 공식 사이트들이 존재한다.
Docker 의 공식 사이트거나,
혹은 MySQL 커뮤니티의 공식 사이트거나,
유명한 Medium 의 개발 블로그거나, 공신력이 있는 사람이거나, 등등..
그러나, 각 조직이나 개인이 말하는 MySQL 실행에 대한 방법론이 조금씩 다르다.
특히, docker 공식 사이트의 경우 docker run 을 통해 compose 파일 없이 생성하는 경우도 있다.
특정 다른 공식 사이트의 경우, docker-compose.yml 파일을 통해 MySQL 컨테이너를 실행한다.
결과적으로 말하자면, docker run 이나, docker compose 나 틀린 방법들은 아니다.
그러나, 운영체제와 상관없이 동일하고 편리한 배포 환경을 만들고 싶다면,
철저하게 Docker Compose 를 추천한다.
그리고 영어를 사용하는 사람이 Target 이 아니라면, 더더욱이 Compose 를 사용해야 한다.
그 이유는, MySQL 서버에 적용해야 할 변수가 많아지기 때문이다.
- MySQL 서버의 시간
- MySQL 서버의 문자열 인식
- MySQL 서버의 유저이름
- MySQL 서버의 비밀번호
- MySQL 서버의 데이터와 내 로컬 폴더와의 볼륨 마운트 (계속 생성되기 싫다면.)
등등이 있을 수도 있는데, 이러한 정보를 단 하나의 파일로,
한 번의 DB 서버를 컨테이너로 열 때 마다 명령어를 복잡하게 입력하지 않고,
하나의 docker compose 파일로 실행 할 수 있게 만드는 것이다.
따로 docker run, docker start, docker stop, docker rm
등등의 명령어를 입력하여 실행하고, 끌 필요도 없기 때문이다.
그렇다고 위의 명령어가 중요하지 않고, 모든 것을 docker compose 로 해결하라는 말은 아니다.
상황에 따라 옵션과 명령이 많이 들어가는 경우, compose 를 이용하여 해결 할 수 있다는 말이다.
compose 없이 MySQL 컨테이너 다뤄보기
하나 더 말할 것이 있는데, 나는 MySQL 을 다룰 때, 데이터베이스를 한국패치 진행을 하지 않아,
팀프로젝트 도중 모든 시간 기록과 한글 입력이 깨진 적이 존재한다.
따라서, 이를 미리 적용하거나, 방지하는 수단은 필수적이다.
한국어는 메인 언어가 아니다. 즉, 인코딩하는 입장에서
한국어는 소수민족 언어와 동일하게 취급한다고 생각하고 주의깊게 보아야 한다.
그렇다면, 우리가 해야 할 행동은 무엇인가?
- 컨테이너 내부 서버의 시간을 "서울" 로 맞추어야 한다.
- mysql 프로그램 내부에서 utf8 인코딩을 사용해야 한다.
위 2 개를 설정하지 않으면,
아예 다시 DB 서버를 세팅하고 시작하거나,
DB 프로그램에 입장하여 데이터베이스 Table 들에 인코딩을 설정하는 최악의 상황이 나올 수 있다.
(심지어 입력된 데이터가 이미 훼손된 상황일 가능성이 높음)
사용할 이미지는 mysql:latest 이다.
위에서는 도커의 내부 논리 인프라를 사용하는 과정을 복잡하게 다루었는데,
이번에는 DB 는 왜 단순 Dockerfile 이 아닌, docker-compose.yml 파일이 사용되는지
보여줄 것이다.
시작 해 보겠다.
MySQL 은 보통 Dockerfile 로 빌드하지 않고, 곧바로 docker run ... 을 사용한다.
그 이유는, MySQL 은 빌드된 결과물이 필요하다기 보다는,
하나의 서버, 혹은 컴퓨터의 프로그램으로서 실행되기 때문이다.
Spring 을 실행하는 데 최적화 된 빌드 과정이 필요한 반면,
MySQL 은 "이미 완성된 프로그램" 으로서, 우리가 빌드하는 과정이 필요하지 않다.
단, docker 시스템에서 설정하기 어렵거나, 매우 디테일한 설정 변경이 필요할 경우,
Dockerfile 을 사용할 수 있는데, 이는 보통 /docker-entrypoint-initdb.d/
경로에 .sh(명령어 파일), .sql(초기화 sql 명령), 등등과 같은 파일을 통해
완성되어 있는 mysql 프로그램에 특수한 초기화를 수행하기 위해 사용한다.
mysql:8,0 을 사용하는 경우를 상정한다 :
# -d 를 사용하는 이유는 현재 명령어 터미널에서 곧바로 컨테이너 세션 내부로 진입하지 않기 위함이다. (Detached - 떼어낸다 == 백그라운드 실행)
$ docker run -d \
> --name=custom-mysql # 컨테이너 이름은 "custom-mysql" 이다.
> -e MYSQL_ROOT_PASSWORD=1324 # 컨테이너에서 db 접근시 비밀번호 "1324"
# 정확히 말하면 DB 의 Scheme(스키마) 를 생성하는 것이다. (우리는 test_scheme 라는 이름으로 생성하겠다.)
> -e MYSQL_DATABASE=test_scheme
# Root 가 아닌, 유저 접근 권한으로 안전하게 아이디, 비밀번호를 생성한다.
> -e MYSQL_USER=test-user
> -e MYSQL_PASSWORD=test-pw
# 서버의 시간을 서울로 맞춘다 (정말 중요)
> -e TZ=Asiz/Seoul
# 외부 요청시 "3308" 포트로, 내부에서는 "3306" 으로 인식한다.
> -p 3308:3306
# Linux/MacOS 문법으로, 터미널의 현 주소 + 하위 디렉토리와
# 컨테이너 서버 내부의 var/lib/mysql 을 마운트시켜서, 데이터베이스 정보를 로컬에 장착한다.
> -v "$(pwd)/data-db:/var/lib/mysql"
# mysql 8 버전을 사용.
> mysql:8.0 \
# 밑의 2 개는 "명령어 - (command)" 를 의미한다.
# 정확히 밑의 2 개가 서버에서 실행된다라는 의미보다, 필요한 mysql 프로그램 설정을
# docker 가 편하게 설정해 준다고 인식하면 된다.
> --character-set-server=utf8mb4 \
> --collation-server=utf8mb4_unicode_ci
위에 주석이 많아서, 주석을 제거한다면
$ docker run -d \
> --name=custom-mysql \
> -e MYSQL_ROOT_PASSWORD=1324 \
> -e MYSQL_DATABASE=test_scheme \
> -e MYSQL_USER=test-user \
> -e MYSQL_PASSWORD=test-pw \
> -e TZ=Asiz/Seoul \
> -p 3308:3306 \
> -v "$(pwd)/data-db:/var/lib/mysql" \
> mysql:8.0 \
> --character-set-server=utf8mb4 \
> --collation-server=utf8mb4_unicode_ci
<생성된 CONTAINER ID>
중요한 것 : ../data-db 폴더 내부는 무조건 비어 있어야 한다.
# 잘 살아있는 것을 볼 수 있음.
➜ ~ docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
986eb2596fe9 mysql:8.0 "docker-entrypoint.s…" 13 seconds ago Up 13 seconds 0.0.0.0:3308->3306/tcp, [::]:3308->3306/tcp custom-mysql
# 현재 터미널과 컨테이너 서버를 연결하는 Interactive Terminal 선언
# 여기에서 "bin/bash" 내부 터미널 시스템을 이용한다.
➜ ~ docker exec -it custom-mysql bin/bash
# 곧바로 루트가 아닌 유저의 아이디와 비번으로 mysql 서버에 접속한다.
bash-5.1 mysql -u test-user -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.44 MySQL Community Server - GPL
Copyright (c) 2000, 2025, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
# mysql 프로그램에서 내가 선언했던 "test_scheme" 이 생성되었는지 확인한다.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| performance_schema |
| test_scheme |
+--------------------+
3 rows in set (0.01 sec)
# mysql 의 터미널에서 퇴장
mysql> exit
Bye
# 현재 접속한 컨테이너의 터미널에서도 퇴장
bash-5.1 exit
exit
What's next:
Try Docker Debug for seamless, persistent debugging tools in any container or image → docker debug custom-mysql
Learn more at https://docs.docker.com/go/debug-cli/
# 내 터미널로 돌아온다.
➜ ~
위의 형태와 같이, MySQL 내부 서버 설정을 위해 무지막지한 옵션을 추가하게 된다.
더 문제인 것은, MySQL 서버를 실행 해 봤더니 원하는 시나리오대로 되지 않거나,
추가 설정이 필요할 경우, 이 컨테이너를 내리고, 또 다시 위의 명령어를 입력해야 한다.
번거로움의 수준을 넘어 굳이 이렇게까지 해야하나? 의 수준으로 온다.
이를 해결 해 주는 것이 바로 docker-compose.yml 이다.
compose 를 사용하여 MySQL 컨테이너 다루기
위에서는 한 방에 모든 것을 실행 한 것 처럼 보이지만,
내가 선언한 명령어가 잘못 입력되어 컨테이너를 약 4 번 내리고 올렸다.
정말 번거로운 작업일 수 밖에 없었는데, docker-compose 를 사용하면 훨씬 나아진다.
docker-compose.yml --> 위치는 알아서.
version: '3.8'
services:
mysql-db:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=1324
- MYSQL_DATABASE=test_scheme
- MYSQL_USER=test-user
- MYSQL_PASSWORD=test-pw
- TZ=Asia/Seoul
ports:
- 3308:3306
volumes:
- ./data-db:/var/lib/mysql
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
위의 위치에 data-db 폴더를 가지고 있어야 한다.
➜ db-compose docker compose up
WARN[0000] /Users/gongdamhyeong/docs-to-github/docker/example/db-compose/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
[+] Running 1/1
✔ mysql-db Pulled 3.9s
[+] Running 2/2
✔ Network db-compose_default Created 0.0s
✔ Container db-compose-mysql-db-1 Creat... 0.0s
Attaching to mysql-db-1
mysql-db-1 | 2025-11-25 23:39:17+09:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.44-1.el9 started.
...
...
mysql-db-1 | 2025-11-25T14:39:24.804557Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.44' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.
➜ ~ docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
927f0327d158 mysql:8.0 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 0.0.0.0:3308->3306/tcp, [::]:3308->3306/tcp db-compose-mysql-db-1
여기서 컨테이너를 내리고 싶다면, 현재 compose 세션이 실행되고 있는 터미널에서
Ctrl + c 를 누른 후 실행되고 있는 컨테이너를 docker stop 하면 되고,
해당 디렉토리에서 docker compose down 을 입력하면 docker rm .. 과 같이 된다.
정말 좋은 것은, 이 모든 과정이 단순히
docker compose up, docker compose down 으로 이루어진다는 것이다.
마무리 및 요약
Docker 라는 프로그램을 통해 생성되는 Container 라는 프로세스 단위는
현재 인프라 오케스트레이션의 가장 중요한 엘리먼트가 되었다.
컨테이너의 조율은 명령어로 가능하나, 실행하고자 하는 프로그램의 단계가 존재한다.
크게 "빌드 전", "이미지", "컨테이너" 로 나눌 수 있다.
문제는 여러 프로그램의 개발이나 프로덕션 시,
발생하는 경고 및 에러에 대처하기 위해 개별 컨테이너나 이미지를 다시 빌드 및 실행한다는 것이다.
이 과정에서는 위에 선보인 수많은 명령어들은 제일 많은 중간 과정이 생략되었는데,
그 중간 과정은 바로 명령어가 실패하고 나서 컨테이너나 이미지를 삭제하고 다시 빌드하거나 실행하는 명령어이다.
프로그램이 경고를 띄우거나 실패를 할 수 있으나, 이 과정에서 너무나 많은 시간 비용이 발생한다.
이들에 대한 설정을 YAML 파일 하나로 묶어 실행하고, 의존적 실행을 설정할 수 있다는 건
정말 편리한 기능이다.
특정 어플리케이션, nginx, spring 를 예로 들어본다면,
spring 은 DB 가 존재해야 프로그램이 실행되었을 때 DB 커넥션 풀을 성공적으로 형성 할 수 있다.
nginx 는 스케일링 될 수 있는 spring 의 서버 체크를 통해 에러 없이 실행된다.
위의 과정은 의존적이며, 이를 docker-compose 파일에서 작성할 수 있다.
벌써 몇 개 되지도 않은 프로그램들을 docker run 으로 실행한다고 가정했을 때에도 머리아프지만,
프로덕션 수준의 프로그램들을 docker run 으로 전부 띄우고 관리한다고 생각한다면
명령어의 수준과 그 양은 감히 비교도 할 수 없을 정도로 많아질 것이다.
Docker Compose 는, 마치 Docker Swarm 이나, K8S 수준의 오케스트레이션을 하지는 않지만,
인프라의 부분적인 구조에서 인프라 오케스트레이션을 쉽게 할 수 있게 해 준다는 것이다.
참조 사이트
Docker docs 공식 사이트 - Docker Compose
https://docs.docker.com/compose/
Docker docs 공식 사이트 - Use containerized databases
https://docs.docker.com/guides/databases/
Docker docs 공식 사이트 - How Compose works
https://docs.docker.com/compose/intro/compose-application-model/
Docker docs 공식 사이트 - Networking using how network
https://docs.docker.com/engine/network/tutorials/host/
'잡다 지식' 카테고리의 다른 글
| 도커 컨테이너 안의 MySQL 과 MySQL Workbench 는 어떻게 연결할까? (0) | 2025.11.27 |
|---|---|
| 내가 Node.js 에서 Spring 으로 백엔드 영역을 변경하는 이유 (1) | 2025.11.05 |
| 동시성 (Concurrency) 이란 무엇일까? - 부제 : 동시 실행이라는 착각 (4) | 2025.06.04 |
| 프로세스와 스레드 (부제 : 정확한 의미를 알고, 명령어로 검색 해 보자) (0) | 2025.05.20 |
| Domain 과 SSL 에 대하여 (부제 : https 는 뭘까?) (0) | 2025.05.19 |