[Docker] 3. docker-compose

김미숙's avatar
Aug 01, 2025
[Docker] 3. docker-compose
  • 여러 개의 Docker 컨테이너를 정의하고, 함께 실행할 수 있도록 도와주는 도구
  • 주로 마이크로서비스 아키텍처개발 환경 구축에서 유용하게 사용됨
  • 여러 서비스를 한 번에 띄울 수 있음
  • 개발 환경 자동화에 용이
  • Docker 명령어보다 간결하고 가독성 좋음
  • 설정 파일로 구성 관리 (버전 관리도 쉬움)
  • 설정은 YAML 파일(docker-compose.yml)에 작성
  • docker 명령어 여러 개를 하나로 통합할 수 있음
  • 같은 컴퓨터여야 결합이 가능하다 - pc가 다르면 결합이 안돔
  • 배포용이 아니라 로컬에서 연습할 때 - 이걸로 배포 못함
  • 로컬 지휘자라고 할 수 있음
 

실습1

docker-compose

version: '3.8' services: my_server: build: context: ./server ports: - "8080:80" restart: always
  • versiondocker-compose 파일의 버전
    • 버전마다 문법이 조금씩 다르다
    • 최신 버전은 문서 확인 필요
  • docker-compose는 컨테이너를 하나의 서비스로 본다
  • services → 컨테이너 단위로 정의되는 실행 단위
  • my_server → 이 서비스(컨테이너)의 이름, docker-compose 명령 시 이 이름으로 구분됨.
  • build: context → 이 컨테이너의 이미지를 직접 빌드하겠다는 의미.
    • context: ./server./server 폴더 안의 Dockerfile을 기준으로 빌드한다는 뜻
    • images 문법은 사용하지 않고, Dockerfile을 통해서 하는게 좋다
  • restart → 컨테이너가 꺼지면 자동으로 다시 시작하도록 설정
    • 의미
      no (기본값)
      자동 재시작 안 함
      always
      항상 재시작
      on-failure
      에러 코드(0이 아님)일 때만 재시작
      unless-stopped
      사용자가 중지하지 않는 한 항상 재시작
  • docker-compose를 실행하면, ./server 폴더 내에 있는 Dockerfile을 기준으로 이미지를 자동으로 빌드하고, 해당 이미지를 사용해 컨테이너를 자동으로 실행할 수 있다
 

Dockerfile

FROM httpd EXPOSE 80 CMD ["httpd-foreground"]
 

docker-compose 실행

docker-compose up -d
notion image
 

컨테이너 확인

docker ps
notion image
 

접속 확인

notion image
 

종료

docker-compose down
notion image
 

실습2

docker-compose

  • docker-compose에서는 services라고 하나 실제로는 pods라고 한다
    • pods: 하나 이상의 컨테이너
version: "3.8" services: my_db: build: context: ./db ports: - "3306:3306" restart: always networks: - backend_network my_server: build: context: ./server ports: - "8080:8080" restart: always depends_on: - my_db environment: - RDS_HOSTNAME=my_db - RDS_PORT=3306 - RDS_USERNAME=ssar - RDS_PASSWORD=ssar1234 - RDS_DB_NAME=blogdb networks: - backend_network networks: backend_network: driver: bridge frontend_network: driver: bridge
  • 컨테이너가 여러 개 있으면 실행 시점이 중요하다
    • db랑 Spring 있으면 db 먼저 안뜨면 Spring이 db 연결할려다가 터짐
      • Spring 애플리케이션이 DB에 접속하려면 DB 컨테이너의 IP 주소(즉, 프로세스 위치)를 알아야 함
      • docker-compose 환경에서는 DB 컨테이너의 IP를 직접 지정하는 대신, 서비스 이름을 호스트명처럼 사용하여 내부 네트워크에서 해당 컨테이너를 찾아서 연결 → 서비스 이름은 Docker Compose가 제공하는 가상 네트워크 상의 DNS 역할을 하며, DB가 재시작되어 IP가 바뀌더라도 자동으로 연결됨
      • depends_on 설정을 통해 Spring 애플리케이션이 DB 컨테이너가 먼저 실행된 이후 시작되도록 보장
  • environment: 환경변수
    • 컨테이너가 뜨고 서버가 실행되기 직전에 docker-compose.yml에 작성한 환경변수가 등록된다
      • 원래는 환경변수가 디스크에 등록 안 되있고, 스프링을 실행하는게 목적, 스프링을 실행하기 직전에 환경변수를 띄운다
      • 실행되기 전에는 환경변수가 없는 상태
    • Dockerfile에 환경변수를 적는 건 다르다 → 환경변수가 이미지에 귀속됨
      • db가 생성될 때 dockerfile에 있는 환경변수를 읽어서 db를 만들기 위함
  • RDS_HOSTNAME → 서비스 명을 적으면 재시작 되면서 갱신된 ip를 알아서 찾음
    • 서비스명 = 컨테이너 DNS 이름 → docker-compose같은 네트워크 내의 서비스끼리는 자동으로 DNS로 서로를 인식할 수 있도록 설정됨
    • my_db서비스명이자 Docker 내부 DNS 이름
    • 컨테이너 내부에서 blogdb로 접근하면, 실제 mydb 컨테이너의 현재 IP 주소를 자동으로 찾아 연결해 준다
    • 즉, blogdb가 재시작되어 IP가 바뀌더라도 app서비스명(blogdb)으로 접근하므로 자동으로 갱신된 IP에 연결할 수 있다
    • docker-compose에서는 같은 네트워크에 있는 컨테이너끼리는 서비스명을 도메인처럼 사용하여 통신할 수 있다. DB 컨테이너가 재시작되어 IP가 바뀌어도, 서비스명을 통해 자동으로 갱신된 IP로 연결되므로 문제가 없다.
  • networks 설정을 명시하지 않으면, default라는 기본 네트워크에 모든 서비스가 연결됨
    • networks: backend_network: driver: bridge frontend_network: driver: bridge
    • 명시적으로 네트워크를 설정할 경우
      • bridge컨테이너 간 통신을 위한 가상 네트워크 드라이버
      • 명시하면 backend_network, frontend_network 두 개의 사용자 정의 네트워크가 생성됨
      • 기본(default) 네트워크를 포함하면 총 3개의 네트워크가 생길 수 있음
  • docker-compose up → 1번으로 build / depends_on은 실행 시에 적용됨
 

db/Dockerfile

FROM mysql COPY init.sql /docker-entrypoint-initdb.d/init.sql ENV MYSQL_USER=ssar ENV MYSQL_PASSWORD=ssar1234 ENV MYSQL_ROOT_PASSWORD=root1234 ENV MYSQL_DATABASE=blogdb CMD ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"] EXPOSE 3306
  • 이미지 빌드시 임시 컨테이너가 한 번 실행되고, 이후 실제 컨테이너가 실행되므로 결과적으로 컨테이너가 두 번 실행됨
  • build 과정에서는 CMD 실행 안됨
    • CMD이미지 빌드 시점이 아니라 이미지로부터 컨테이너를 실행(run)할 때 동작함
    • docker build 시점에는 CMD는 무시되고, docker run 또는 docker-compose up 때 실제로 적용됨
      • 🔍 각 명령어 실행 시점
        명령어
        실행 시점
        COPY, ENV, RUN
        docker build (이미지 빌드 단계)
        CMD, ENTRYPOINT
        docker run (컨테이너 실행 시점)
 

db/init.sql

use blogdb; CREATE TABLE IF NOT EXISTS user_tb ( id integer auto_increment, created_at timestamp, email varchar(20) not null, password varchar(60) not null, username varchar(20) not null unique, profile varchar(100), primary key (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; CREATE TABLE IF NOT EXISTS board_tb ( id integer auto_increment, content varchar(10000), created_at timestamp, title varchar(100) not null, user_id integer, primary key (id), constraint fk_board_user_id foreign key (user_id) references user_tb (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; CREATE TABLE IF NOT EXISTS reply_tb ( id integer auto_increment, comment varchar(100) not null, created_at timestamp, board_id integer, user_id integer, primary key (id), constraint fk_reply_board_id foreign key (board_id) references board_tb (id), constraint fk_reply_user_id foreign key (user_id) references user_tb (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; FLUSH PRIVILEGES;
 

entrypoint.sh

  • Docker 컨테이너가 시작될 때 실행되는 스크립트 파일
  • 주로 컨테이너 초기 설정을 자동화할 때 사용
git clone https://github.com/busanuv/blog-last.git cd blog-last chmod +x gradlew ./gradlew build chmod +x build/libs/*.jar java -jar -Dspring.profiles.active=docker build/libs/*.jar
  • Docker 컨테이너가 docker run 또는 docker-compose up으로 실행될 때 entrypoint.sh실행 초기 단계에서 자동으로 실행
  • 일반적으로 쉘 스크립트이며, 환경 변수 설정, DB 초기화, 파일 복사 등 준비 작업 수행
  • docker-compose에서 runentrypoint.sh만 실행하고 스크립트는 entrypoint.sh에 쓴다
 

server/Dockerfile

FROM openjdk:11-jdk-slim RUN apt-get update RUN apt-get install -y git WORKDIR /app COPY ./entrypoint.sh ./entrypoint.sh RUN ["chmod", "+x", "entrypoint.sh"] EXPOSE 8080 ENTRYPOINT ["/bin/bash", "./entrypoint.sh"]
  • entrypoint.sh 실행을 위한 이미지 빌드 과정 → entrypoint 전까지가 이미지를 build하는 과정임
  • apt-get update → 패키지 목록(메뉴판) 최신화
  • apt-get install -y git → Git 설치 (-y는 자동으로 yes 응답) / -y 옵션 필수
  • RUN ["chmod", "+x", "entrypoint.sh"] entrypoint.sh 실행 권한 부여
  • EXPOSE 8080 → 어떤 포트로 실행될지 알려줌
  • 이미지 빌드만 했을 땐 컨테이너는 아직 실행되지 않음
  • 여기까지의 프로비저닝은 Docker Compose에서
    • my_server: build: context: ./server
      이 설정에 해당한다
    • 이 과정에서 FROM, RUN, COPY, WORKDIR, EXPOSE, ENTRYPOINT 등이 모두 실행되며 이미지가 생성됨
    • 단, ENTRYPOINT는 설정만 되고 아직 실행되지 않음 실제 실행은 docker-compose up 할 때 일어남
 

실행

docker-compose up -d
notion image
notion image
notion image
notion image
네트워크 확인
docker ps
notion image
docker inspect 5e19
notion image
  • 컨테이너의 전체 정보를 JSON 형식으로 출력
  • 컨테이너의 모든 메타데이터를 확인 가능
    • ✅ 주요 확인 가능한 정보 목록
      항목
      설명
      Id
      컨테이너 전체 ID
      Name
      컨테이너 이름 (docker-compose 사용 시 프로젝트_서비스_번호)
      Image
      컨테이너가 사용한 이미지 ID
      State
      실행 상태 (running, exited, paused 등), 시작 시간, 종료 코드 등
      Mounts
      마운트된 볼륨 정보 (호스트 ↔ 컨테이너 연결)
      Config.Env
      환경 변수 (environment:로 설정한 값 포함)
      NetworkSettings.Networks
      네트워크 정보 (IP 주소, 게이트웨이, MAC 등)
      Ports
      포트 매핑 정보 (예: 8080:8080)
      LogPath
      로그 파일 저장 경로 (Docker 엔진 내부용)
      Path + Args
      컨테이너가 실행한 명령어 (ENTRYPOINT, CMD)
 
 
notion image

✅ 라우팅 테이블이란?

  • 네트워크에서 데이터를 어디로 보낼지 결정하는 길잡이 역할
  • 목적지 IP 주소에 따라 어디로 데이터를 보낼지 결정하는 규칙 목록
  • 운영체제나 라우터에 존재하는 IP 기반 경로표
  • 목적지 IP가 주어졌을 때, 다음 홉(Next Hop) 또는 인터페이스를 결정함

📦 구성 요소 (예시 항목)

항목
설명
Destination
목적지 IP 주소 또는 네트워크 대역
Gateway
패킷을 전달할 다음 라우터의 IP (Next Hop)
Genmask
서브넷 마스크 (네트워크 범위 결정)
Interface
어느 네트워크 인터페이스로 보낼지 (예: eth0, wlan0 등)

✅ DNS란?

  • 도메인 이름을 IP 주소로 변환해주는 시스템
  • 사람이 기억하기 쉬운 주소 → 컴퓨터가 이해할 수 있는 주소로 변환
  • 인터넷의 주소록 역할을 하는 아주 중요한 시스템
  • DNS는 도메인 이름을 IP 주소로 바꿔주는 시스템으로, 인터넷 통신이 가능하도록 도와주는 주소 해석기

📦 동작 원리 요약

  1. 클라이언트가 도메인을 입력
  1. 운영체제는 캐시에 해당 IP가 있는지 확인
  1. 없다면 로컬 DNS 서버(보통 ISP 또는 공용 DNS)로 요청 전송
  1. 여러 단계의 권한 있는 DNS 서버들을 거쳐 최종 IP 주소 응답
  1. 클라이언트는 그 IP로 연결
 

✅ 도커 환경에서의 DNS란?

  • 컨테이너 간 통신을 편리하게 하기 위해 Docker가 자체적으로 DNS 기능을 제공
  • 컨테이너 이름이나 Compose 서비스 이름을 도메인처럼 사용하여 자동으로 IP를 찾는 기능

🧠 기본 개념

항목
설명
내부 DNS
Docker는 사용자 정의 브리지 네트워크를 만들면 자동으로 DNS 서버 역할을 수행함
서비스명 또는 컨테이너명으로 접근 가능
my_server, my_db 같은 이름이 내부 DNS 이름으로 작동함
IP 주소는 동적으로 할당되지만, 이름은 항상 고정
호스트 파일 수정 불필요
/etc/hosts 수정 없이 이름 기반으로 통신 가능

🔧 동작 조건

Docker 내부 DNS 기능이 작동하려면
  1. 같은 사용자 정의 네트워크에 컨테이너가 있어야 함
    1. networks: backend_network: driver: bridge
  1. docker-compose.yml에서 services 아래에 이름 정의:
    1. services: my_server: my_db:
→ 이 경우 my_server 컨테이너에서 my_dbDNS 이름으로 접속 가능

✅ 서비스 디스커버리(Service Discovery)란?

notion image
  • 네트워크에 있는 서비스의 위치(IP, 포트 등)를 자동으로 찾아주는 시스템
  • 마이크로서비스나 분산 시스템에서 동적으로 다른 서비스의 위치(IP/Port 등)를 찾는 메커니즘
  • 정적인 IP 주소 대신, 서비스 이름만으로 통신할 수 있게 해준다

🔍 왜 필요한가?

❌ 기존 방식 (정적 설정)
spring.datasource.url=jdbc:mysql://192.168.0.5:3306/blogdb
  • IP가 바뀌면 접속 안 됨
  • 마이크로서비스가 많아지면 관리가 어려움
✅ 서비스 디스커버리 방식
spring.datasource.url=jdbc:mysql://my_db:3306/blogdb
  • my_db는 실제 IP를 몰라도 자동으로 DNS 조회
  • IP가 바뀌어도 항상 연결 가능 (동적 바인딩)

🧭 동작 방식

서비스 디스커버리는 보통 2가지 방식으로 나뉜다
방식
설명
클라이언트 사이드 디스커버리
클라이언트가 직접 등록된 서비스 목록을 보고, 서비스 위치를 선택 (예: Netflix Eureka + Ribbon)
서버 사이드 디스커버리
요청이 먼저 로드밸런서(디스커버리 서버)에 가고, 서버가 위치를 찾아줌 (예: Kubernetes, AWS ELB)

🧠 요약 정리

서비스 디스커버리는 마이크로서비스 간 통신에서 서로의 IP를 고정하지 않고도 이름만으로 통신할 수 있게 해주는 시스템
Docker의 서비스명-DNS, Kubernetes의 CoreDNS, Spring의 Eureka, HashiCorp의 Consul 등이 대표적인 방식
 
Share article

parangdajavous