와, MSA로 전환하시면서 로컬 개발 환경 구축하느라 고생이 많으시네요.
이 질문 정말 많은 분들이 겪는, 그리고 사실 가장 까다로운 지점 중 하나예요.
'도커로 올리는 것'과 '실제 개발 경험(DX)을 유지하는 것' 사이의 간극을 메우는 게 핵심이거든요.
단순히 docker-compose up으로 띄우는 것만으로는 개발 생산성이 확 떨어지는 게 체감되실 겁니다.
제가 몇 년간 다양한 MSA 환경에서 겪어보고, 여러 툴을 조합해본 경험을 바탕으로 몇 가지 관점별로 정리해 드릴게요.
이게 '만능 공식'은 아니라서, 현재 프로젝트의 특성(언어 스택, DB 종류, 통신 프로토콜 등)에 따라 조금씩 조정해야 할 부분이 많습니다.
--- ### 1.
개발 워크플로우 관점에서의 접근 (가장 중요) 일단 가장 먼저 짚고 넘어가야 할 건, '컨테이너 안에서 모든 것을 돌리는 것'이 무조건 정답은 아니라는 점입니다.
개발 환경의 목표가 무엇이냐에 따라 아키텍처가 달라져야 해요.
A.
'Hot Reloading'과 '코드 디버깅'이 최우선일 때 (가장 일반적인 개발 상황) 이 경우, 컨테이너 내부의 서비스 코드는 **'빌드 결과물'**만 컨테이너에 넣고, **'실제 소스 코드'**는 로컬 머신에 두고 볼륨 마운트를 사용하는 게 기본입니다.
- 권장 방식: Docker Compose + Volume Mount (
.:/app/src) * 핵심 팁: 디버거 연결 (The Pain Point) * 문제점: IDE(VSCode, IntelliJ 등)에서 디버거를 띄우고, 이 디버거가 컨테이너 내부의 프로세스에 붙어야 하는데, 네트워크나 권한 문제로 이게 막히는 경우가 흔합니다.
- 해결책 1 (VSCode 기준):
devcontainer.json을 적극적으로 사용하세요.
VSCode 자체에서 개발 컨테이너 환경을 관리하게 해주는 기능입니다.
이 기능을 쓰면, VSCode가 알아서 필요한 포트 포워딩, 권한 설정 등을 상당 부분 처리해 줍니다.
- 해결책 2 (Jupyter/Python 백엔드): 만약 Python이나 데이터 분석 쪽에 가깝다면,
docker-compose exec으로 컨테이너에 접속한 뒤, ssh나 tmux 등을 이용해 디버깅 세션을 유지하는 것이 더 안정적일 때도 있습니다.
- 주의점: 만약 애플리케이션이 특정 OS 의존성(예: 네이티브 라이브러리)을 가지고 있다면, 볼륨 마운트를 해도 컨테이너 OS와 로컬 OS 간의 라이브러리 버전 충돌이 생길 수 있습니다.
이 경우, 개발 전용 '빌드 스테이지' 컨테이너를 따로 두어 의존성을 격리하는 것이 안전할 수 있습니다.
B.
'통합 테스트'와 '배포 환경 유사성'이 최우선일 때 (CI/CD 연동 테스트) 이 경우는 볼륨 마운트를 최소화하고, **'빌드 후 이미지'**를 중심으로 돌아가는 것이 좋습니다.
- 권장 방식: CI 파이프라인과 최대한 유사하게, 로컬에서도
docker compose build를 돌려서 이미지를 만든 후, 해당 이미지를 기반으로 실행합니다.
- 장점: 로컬에서 테스트하는 환경이 실제 스테이징/프로덕션 환경과 가장 가깝습니다.
- 단점: 코드를 수정할 때마다
docker compose build를 돌려야 하므로, 작은 수정에도 피로도가 높습니다.
- 팁: 이 경우, 코드 수정이 잦은 부분(예: 비즈니스 로직)은 볼륨 마운트가 필요하지만, 환경 설정이나 정적 리소스(Nginx 설정 파일, OpenAPI 스키마 등)는 이미지 빌드 시점에 고정하는 것이 좋습니다.
--- ### 2.
볼륨 마운트 최적화 심화 팁 볼륨 마운트는 DX를 망치는 주범이기도 하지만, 동시에 가장 강력한 기능이기도 합니다.
몇 가지 트릭이 있습니다.
1.
delegated vs cached (Docker Desktop 환경에서 중요) 이건 Docker 내부의 파일 시스템 캐싱 관련 옵션인데, 경험상 매우 중요합니다.
delegated: 호스트(로컬 PC)에서 파일을 수정했을 때, 컨테이너가 이 변경을 감지하는 속도가 빠릅니다.
개발 중에는 이게 나을 때가 많습니다.
cached: 컨테이너가 파일을 읽을 때, 로컬 파일 시스템의 변경 사항을 느리게 반영할 수 있습니다.
실험해 보세요: docker-compose.yml의 volume 설정에서 delegated 옵션을 주고 테스트해보는 것이 좋습니다.
(다만, Docker 버전이나 OS에 따라 동작 방식이 다를 수 있다는 점은 감안하셔야 합니다.) 2.
node_modules와 같은 의존성 라이브러리 분리 가장 흔한 실수 중 하나입니다.
잘못된 방식: 모든 서비스의 node_modules 폴더를 볼륨 마운트함.
- 문제: OS마다 패키지 매니저(npm, yarn 등)의 동작 방식이나 경로 구조가 달라서, 로컬에서 설치한 의존성이 컨테이너 환경에서 충돌을 일으킬 확률이 매우 높습니다.
올바른 방식: 1.
package.json과 같은 설정 파일만 볼륨 마운트합니다.
컨테이너 내부에서 npm install 또는 yarn install을 실행하여 의존성을 설치하게 합니다.
3.
이 의존성 폴더(node_modules)는 이미지 빌드 과정에서 포함시키고, 볼륨 마운트 대상에서 제외하거나, 아예 컨테이너 시작 시점에 빌드하도록 워크플로우를 짜는 것이 가장 깨끗합니다.
--- ### 3.
추천 툴 조합 및 워크플로우 (요약) 만약 제가 지금부터 새로운 MSA 프로젝트를 시작한다면, 아래의 조합과 워크플로우를 따를 것 같습니다.
추천 조합: 1.
컨테이너 오케스트레이션: Docker Compose (가장 범용적이고 설정이 직관적임) 2.
개발 환경 관리: VSCode Dev Containers (VSCode 사용자라면 무조건 이걸로 시작) 3.
언어별 런타임: (필요시) Docker Compose의 서비스별 command를 통해 특정 언어의 개발 서버 명령어(예: npm run dev, flask run --debug)를 직접 실행하도록 지정.
추천 워크플로우 순서: 1.
기반 환경 세팅 (Dockerfile/docker-compose.yml): * 각 서비스별로 Dockerfile을 작성하여, 필요한 OS 라이브러리, 기본 의존성 설치 과정을 명시합니다.
(최소한의 빌드 스테이지를 갖도록) * docker-compose.yml에서 서비스 간의 네트워크(브릿지) 연결과 포트 매핑을 정의합니다.
2.
개발 시작 (Dev Containers 사용): * VSCode에서 프로젝트 루트에 .devcontainer/devcontainer.json 파일을 만들고, 사용하려는 기본 이미지를 지정합니다.
- VSCode가 컨테이너를 띄우고, 개발 세션을 시작하게 합니다.
이 단계에서 VSCode가 자동으로 디버거 연결 및 포트 포워딩을 처리해주기 때문에 가장 편리합니다.
코드 수정 및 테스트: * 로컬에서 코드를 수정합니다.
(IDE의 자동 저장 기능 활용) * Hot Reloading이 지원되는 프레임워크(Spring Boot WebFlux, NestJS 등)를 사용하고, docker-compose up 또는 개발 서버 명령어를 통해 실행합니다.
️ 주의: 재시작 시, 볼륨 마운트된 경로의 파일 변경을 런타임이 감지하는지 로그를 주시하세요.
만약 너무 느리거나 무시된다면, 해당 서비스의 재시작 전략을 변경해야 합니다.
--- ###
실무에서 자주 겪는 함정 및 체크리스트 1.
환경 변수 관리: 로컬 개발 시, DB 접속 정보, API 키 등은 절대 코드에 하드코딩하지 마세요. 반드시 .env 파일을 사용하고, 이 .env 파일도 docker-compose가 읽을 수 있도록 구성해야 합니다.
DB/캐시 서비스 분리: Redis나 PostgreSQL 같은 외부 의존 서비스는 '별도의 서비스'로 분리하고, docker-compose.yml에 명시하여 컨테이너 간의 통신을 강제하세요.
(이것이 MSA의 기본 원칙이기도 합니다.) 3.
로깅 중앙화: 각 서비스가 자체적으로 로그를 찍어내기 때문에, 로컬에서 디버깅할 때 로그가 뒤섞이는 경우가 많습니다.
docker-compose logs -f로 묶어서 보긴 하지만, 테스트 단계에서는 **ELK 스택(또는 유사 로깅 툴)**을 가볍게라도 컨테이너로 띄워서 로그를 한곳으로 모니터링하는 습관을 들이는 것이 장기적으로 좋습니다.
결론적으로, 최신 DX를 위한 베스트 프랙티스는 'VSCode Dev Containers'를 사용하여 개발 환경 자체를 컨테이너화하고, '볼륨 마운트'를 통해 소스 코드를 공유하되, '의존성 라이브러리 설치'는 컨테이너 내부에서 실행하는 조합이라고 정리할 수 있겠습니다.
이거 너무 장황하게 느껴지실 수도 있는데, 이 부분들이 하나라도 놓치면 개발 과정에서 엄청난 시간 낭비로 돌아올 수 있거든요.
일단 이 가이드라인을 바탕으로 현재 사용하시는 언어/프레임워크 조합을 말씀해주시면, 그에 맞춰서 더 구체적인 docker-compose 예시나 설정 파일을 짜는 데 도움을 드릴 수 있을 것 같습니다.
개발 환경 구축은 한 번에 끝나는 게 아니라 계속 최적화하는 과정이니까, 너무 스트레스 받지 마시고 단계별로 접근해보세요!
화이팅입니다!