Docker를 이용해 프로젝트 컨테이너화 하기 - docker-compose.yml 작성

2026. 1. 20. 23:55도커

현재 진행 중인 프로젝트는 로컬에서 컨테이너화 없이 개발하고 있었습니다.

이번에 개발환경 컨테이너화 했는데, 이 과정과 컨테이너화를 하면서 얻은 이점은 어떤 게 있는지에 대해서 소개하겠습니다.


프로젝트 구조

프로젝트는 전형적인 3-tier 아키텍쳐로 구성되어 있습니다.

  • 프론트엔드(Presentation TIer): React18.3.1 + TypeScript (CRA 기반)
  • 백엔드(Application Tier): Java 17 + Spring Boot 3.3.4 (Gradle)
  • 데이터베이스(Data Tier): MySQL, Redis

기존 개발 환경 및 컨테이너화 이유

1. Docker 학습 목적

대략적으로 도커가 어떤 기술이다 라는건 알고 있었지만, 현재 재직 중인 회사에서는 도커를 사용하고 있지 않습니다.

현재 도커는 개발에서는 거의 필수적인 요소가 되었기때문에 개인 프로젝트를 통해 도커 기술을 직접 경험하고 학습하고 싶었습니다.

 

2. 멀티 디바이스 개발 환경의 불편함
저는 집에서 개발을 할 때는 데스크탑(Window)로 진행하고, 카페 같은 외부에서 개발할 때는 노트북(Mac)을 사용합니다.

처음 프로젝트 세팅시에 각 PC에 맞게 동일한 버전의 프로그램을 설치를 해야 하는데, 설치 방법이 운영체재에 따라서 다르다 보니 약간의 불편함이 있었고 Redis는 초기와 다르게 중간부터 적용되어 사용하게 되었는데 그때도 각 PC에 동일한 버전의 Redis를 다른 방법으로 설치해야 했습니다.

 

프로젝트를 진행하면서 기간이 늘어남에 따라 버전이 변경될 수도 있고 추가로 설치 해야하는 일도 분명히 생길 거라고 생각했고

이러한 점을 개선하기 위해서 컨테이너화를 하기로 했습니다.

 

컨테이너화를 통한 예상 기대 효과

위에 언급했듯이 제가 예상한 기대 효과는 아래와 같습니다.

  1. PC가 변경되거나 OS가 변경되어도 동일한 개발 환경 구축
  2. Docker, 컨테이너 기술 체험 및 학습

여러 컨테이너를 띄울 예정이기 때문에 docker-compose.yml 파일을 작성해 여러 컨테이너를 하나의 파일로 관리할 예정입니다.

1단계: MySQL 컨테이너 띄우기

가장 먼저 데이터베이스(MySQL)부터 컨테이너로 만들어보겠습니다.

 

docker-compose.yml

services: # 실행할 컨테이너들을 정의하는 섹션
  mysql-service:  # 서비스 이름 (원하는 이름으로 지정 가능)
    image: mysql:8.0  # Docker Hub에서 가져올 이미지
    container_name: mysql-container  # 실제 컨테이너 이름
    ports:  # 포트 포워딩 설정
      - "3306:3306"  # "호스트포트:컨테이너포트" (로컬 3306포트로 접속하면 컨테이너 3306포트로 연결)
    environment:  # 컨테이너 내부 환경변수
      MYSQL_ROOT_PASSWORD: rootpassword  # MySQL root 비밀번호
      MYSQL_DATABASE: project_db  # 자동 생성할 데이터베이스 이름
      MYSQL_USER: dbuser  # 자동 생성할 사용자
      MYSQL_PASSWORD: dbpassword  # 사용자 비밀번호

 

이렇게 프로젝트 루트에 만들고 docker compose up을 실행하면 MySQL 컨테이너가 정상적으로 생성될 것이고,

이제 접속 테스트를 해보면 됩니다.

docker exec -it mysql-container mysql -u dbuser -p

 

 

위 명령어를 입력하면

\

정상적으로 접속되는걸 확인할 수 있습니다.

이제 정상적으로 접속은 되지만, 다른 문제가 있습니다.

 

지금 위 사진처럼 데이터가 들어가 있는데, 현재 상황에서 컨테이너를 중지하고 다시 시작하면 

docker-compose down  # 컨테이너 중지 및 삭제
docker-compose up    # 컨테이너 재생성 및 시작

 

 

이렇게 이전에 생성했던 테이블과 데이터가 모두 사라진 걸 확인할 수 있습니다.

 

원인과 해결방법은 아래와 같습니다.

  • 원인
    컨테이너는 삭제되면 내부 데이터도 함께 사라짐
  • 해결방법
    volume을 추가해서 마운트
services:
  mysql-service:
    image: mysql:8.0
    container_name: mysql-container
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: project_db
      MYSQL_USER: dbuser
      MYSQL_PASSWORD: dbpassword
      MYSQL_CHARACTER_SET_SERVER: utf8mb4  # 한글 지원을 위한 문자셋
      MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci  # 정렬 규칙
    volumes:  # 볼륨 마운트 설정
      - mysql-data:/var/lib/mysql  # "볼륨이름:컨테이너내부경로"
      # mysql-data 볼륨을 컨테이너의 /var/lib/mysql에 연결
      # MySQL은 데이터를 /var/lib/mysql에 저장하므로 이 경로를 마운트

volumes:  # 사용할 볼륨 정의
  mysql-data:  # mysql-data라는 이름의 볼륨 생성
    # Docker가 관리하는 영역에 데이터 저장
    # 컨테이너가 삭제되어도 볼륨은 유지됨

 

이렇게 설정해주면 컨테이너가 삭제되어도 docker가 관리하는 볼륨에 데이터가 남아있기 때문에 컨테이너를 삭제해도 괜찮습니다.

이제 컨테이너를 삭제하고 다시 올려도 정상적으로 데이터가 남아있는 걸 확인할 수 있을 겁니다.

 

 

 

 

2단계: Redis 추가

이제 Redis도 컨테이너도 추가합니다.

 

docker-compose.yml

services:
  mysql: ...

  redis:  # Redis 서비스 추가
    image: redis:8-alpine  # alpine: 경량화된 리눅스 배포판 (이미지 크기 작음)
    container_name: redis-container
    ports:
      - "6379:6379"  # Redis 기본 포트

volumes:
  mysql-data:

 

저는 Redis를 휴대폰 인증 관련된 기능으로 사용하고 있어서 Redis는 영속적인 데이터 저장이 필요하지 않아

Redis에 대한 볼륨은 추가하지 않았습니다.

 

이제 docker compose up 하면 MySQL과 Redis가 함께 실행됩니다.

 

 

 

 

3단계: 백엔드 추가

이제 Spring Boot 백엔드를 추가합니다.

 

docker-compose.yml

services:
  ...

  backend-service:  # 백엔드 서비스 추가
    image: gradle:8-jdk17  # Gradle 8.x와 JDK 17이 설치된 이미지
    container_name: backend-container
    working_dir: /app  # 컨테이너 내부 작업 디렉토리
    ports:
      - "8081:8081"  # Spring Boot 포트
    volumes:
      - /backend:/app  # 로컬 backend 폴더를 컨테이너 /app에 마운트
    command: sh -c "gradle bootRun --no-daemon"  
    # gradle bootRun: Spring Boot 실행
    # --no-daemon: Gradle 데몬 사용 안 함
    environment:  # 애플리케이션 환경변수
      - SPRING_PROFILES_ACTIVE=DEV_ENV

  ...

 

저는 공통 설정은 application.properties에, 환경별 설정은 application-DEV_ENV.properties에 분리해서 관리하고 있습니다.

따라서 docker-compose.yml에서 SPRING_PROFILES_ACTIVE=DEV_ENV 환경변수를 설정하여 개발 환경 프로파일을 활성화했습니다.

 

이제 다시 docker compose up을 하고 백엔드 로그를 확인해 보니 아래와 같은 에러 메시지가 나왔습니다.

Caused by: java.net.ConnectException: Connection refused

원인과 해결방법은 아래와 같습니다.

  • 원인
    DB 접속정보를 application-DEV_ENV.properties에 저장해놨는데, 여기서 host가 localhost로 되어있습니다.
    각 컨테이너는 독립된 환경이기 때문에, 백엔드 컨테이너 안에서 localhost는 자기 자신(백엔드 컨테이너)를 가리킵니다.
    그래서 localhost:3306으로 찾아도 MySQL은 다른 컨테이너에 있기 때문에 연결이 실패합니다.
  • 해결방법
    MySQL 컨테이너에 접근하려면 컨테이너 간 통신이 필요합니다.
    그래서 컨테이너 간 통신을 할 수 있게 해주는 Network를 추가해서 컨테이너 이름으로 접근하면 됩니다.
services:
  mysql:
     ...
    networks:  # 네트워크 연결
      - app-network # app-network에 이 컨테이너를 연결

  redis:
    ...
    networks:
      - app-network # app-network에 이 컨테이너를 연결

  backend:
    ...
    networks:
      - app-network # app-network에 이 컨테이너를 연결

networks:  # 네트워크 정의
  app-network:  # app-network라는 이름의 네트워크 생성
    driver: bridge  # bridge 드라이버 사용 (기본값)
    # bridge: 같은 호스트의 컨테이너끼리 통신하는 가상 네트워크

volumes:
  mysql-data:

 

이제 백엔드 서버 로그를 확인해 보면 정상적으로 실행된 걸 확인할 수 있습니다.

 

백엔드 컨테이너 추가 설정 (healthcheck 설정)

백엔드 서버는 DB 컨테이너가 정상적으로 실행된 다음 올라가야 DB와의 연결이 되므로

백엔드 컨테이너가 올라갈 때 DB 컨테이너의 정상 실행 상태 확인 후 올라가도록 설정을 추가하겠습니다.

services:
  mysql:
    ...
    healthcheck:  # 컨테이너가 정상 작동하는지 확인
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpassword"]
      # 실행할 명령어: mysqladmin ping (MySQL 서버가 응답하는지 확인)
      # 응답하면 healthy, 응답 없으면 unhealthy
      interval: 10s  # 10초마다 체크
      timeout: 5s    # 5초 안에 응답 없으면 실패
      retries: 5     # 5번 연속 실패하면 unhealthy 상태
    networks:
      - app-network

  redis:
    ...
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]  # redis-cli ping으로 확인
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  backend:
    ...
    depends_on:  # 이 서비스가 의존하는 다른 서비스
      mysql:
        condition: service_healthy # mysql이 healthy 상태가 될 때까지 backend 시작을 대기
      redis:
        condition: service_healthy # redis가 healthy 상태가 될 때까지 backend 시작을 대기
    networks:
      - app-network

...

 

동작 순서는 아래와 같습니다.

  1. MySQL와 Redis 컨테이너 시작
  2. Docker가 10초마다 healthcheck 실행
  3. MySQL와 Redis 서버 초기화 완료 → healthy 상태
  4. 백엔드 컨테이너 시작

 

docker compose up 후에 로그를 보면 백엔드가 MySQL과 Redis가 완전히 준비된 후에 시작되는 걸 확인할 수 있습니다.

 

 

 

 

 

 

4단계: 프론트엔드 추가

마지막으로 React 프론트엔드를 추가합니다.

services:
  ...

  frontend:  # 프론트엔드 서비스 추가
    image: node:20  # Node.js 20 이미지
    container_name: frontend-container
    working_dir: /app  # 작업 디렉토리
    ports:
      - "3000:3000"  # React 개발 서버 포트
    volumes:
      - ./frontend:/app  # 로컬 frontend 폴더 마운트
    command: sh -c "npm install && npm start"  
    # npm install로 의존성 설치 후 서버 시작
    networks:
      - app-network

  ...

 

이렇게 하면 정상적으로 프론트도 실행되는 걸 확인할 수 있습니다.

 

프록시 설정

이제 프론트에서 요청을 보내면 아래처럼 proxy에러가 나는 걸 확인할 수 있습니다.

이는 백엔드와 마찬가지로 localhost가 자기 자신(프론트 컨테이너)를 가리키기 때문입니다.

따라서 프론트 컨테이너가 백엔드 컨테이너의 API를 호출할 수 있도록 프록시 설정을 변경해 줍니다.

 

package.json

{
  "proxy": "http://backend-container:8081"
}

 

이제 모든 서비스가 정상적으로 실행됩니다.