아... 이게 아니었구나
제가 며칠 전에 정말 바보같은 실수를 하나 발견했어요. 몇 달째 도커 빌드할 때마다 "왜 이렇게 오래 걸리지?"하면서 답답해하고 있었거든요. 특히 회사에서 새로운 마이크로서비스 프로젝트를 도커 컴포즈로 구성하면서부터 빌드 시간이 말도 안 되게 길어졌어요.
처음엔 그냥 "프로젝트가 커져서 그런가보다" 하고 넘어갔는데... 빌드할 때마다 터미널에 뜨는 "Sending build context to Docker daemon: 10.2GB" 이 메시지가 계속 신경 쓰였어요.
음... 10기가라니? 아무리 생각해도 제 코드가 그렇게 클 리가 없는데 말이죠.
범인을 찾아라!
결국 참다못해 프로젝트 폴더를 뒤져보니까... 아, 이런. 😅
my-project/ ├── docker-compose.yml ├── .dockerignore # 🚨 문제의 위치! ├── media/ # 영상, 이미지 파일들이 수 기가씩... ├── app/ │ ├── Dockerfile │ └── src/ └── api/ ├── Dockerfile └── src/
.dockerignore 파일을 프로젝트 루트에 둔 게 문제였어요! 도커 컴포즈와 같은 위치에 놨더니, 각 서비스의 Dockerfile이 있는 위치에서는 이 파일을 읽지 못하는 거였죠.
그래서 app/ 폴더에서 도커 빌드할 때 상위 디렉토리의 media/ 폴더까지 모조리 빌드 컨텍스트에 포함되어 버린 거예요. 몇 달 동안 매번 몇 기가씩 되는 영상 파일들을 도커 데몬에게 보내고 있었던 거죠... 🤦♂️
원리를 알고 나니 뻔한 일이었네
나중에 도커 공식 문서를 다시 찾아보니까, 빌드 컨텍스트는 docker build 명령을 실행하는 위치가 기준이 되더라고요.
도커 빌드가 어떻게 작동하는지 보면:
docker build .
명령을 실행하면 현재 디렉토리(.)가 빌드 컨텍스트가 됨.dockerignore
파일은 빌드 컨텍스트의 루트에 있어야 함- 도커는 빌드 컨텍스트의 모든 파일을 도커 데몬에게 전송
제가 실수한 부분은 도커 컴포즈에서 각 서비스의 build.context를 상위 디렉토리로 설정해놨는데, .dockerignore는 각 서비스 디렉토리에 없었던 거예요.
# docker-compose.yml services: app: build: context: ./app # 빌드 컨텍스트는 ./app dockerfile: Dockerfile
이 경우 ./app/.dockerignore가 있어야 하는데, 저는 계속 루트에만 둬놨으니 당연히 미디어 파일들이 다 포함될 수밖에요.
Docker 공식 문서에서도 ".dockerignore file must be in the root of the build context for it to be effective"라고 명시하고 있고, Google의 컨테이너 베스트 프랙티스에서도 각 서비스별로 적절한 .dockerignore를 배치하라고 권하고 있더라고요.
해결 과정
일단 급한 불부터 끄자
# 각 서비스 디렉토리에 .dockerignore 생성 echo "media/" > app/.dockerignore echo "media/" > api/.dockerignore echo "*.log" >> app/.dockerignore echo "node_modules/" >> app/.dockerignore
더 정교하게 튜닝
# app/.dockerignore media/ uploads/ *.log .git/ .gitignore README.md docker-compose.yml .env node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log*
결과 확인
$ docker build ./app Sending build context to Docker daemon: 234.5MB # 🎉 10GB → 234MB!
와... 진짜 10기가에서 200메가로 줄어들더라고요! 빌드 시간도 5분에서 30초로 단축됐고요.
추가로 배운 팁들
빌드 컨텍스트 크기 미리 확인하기
# 빌드 전에 컨텍스트 크기 확인 du -sh ./app
어떤 파일들이 제외되는지 확인
docker build --no-cache --progress=plain ./app 2>&1 | grep "excluding"
프로젝트 구조별 전략
- 모노레포: 루트에 공통
.dockerignore
+ 각 서비스별 추가 파일 - 멀티레포: 각 서비스별 독립적인
.dockerignore
- 개발/운영 분리: 환경별로 다른
.dockerignore
템플릿 사용
아직도 헷갈리는 부분들
솔직히 말하면 아직도 완벽하게 이해했다고 할 수는 없어요. 특히:
- 복잡한 멀티스테이지 빌드에서의 최적화는 아직 연구 중이고
- CI/CD 파이프라인에서의 캐시 전략도 더 공부해야 할 것 같아요
- 큰 바이너리 파일들을 어떻게 효율적으로 관리할지도 고민이에요
제가 경험한 건 비교적 단순한 Node.js + 미디어 파일 케이스라서, 다른 환경에서는 또 다른 이슈가 있을 수 있겠더라고요.
마무리하며...
이런 실수를 하면서 느낀 건, 기본기의 중요성이에요. 도커를 몇 년째 쓰고 있으면서도 빌드 컨텍스트의 정확한 동작 원리를 제대로 이해하지 못했던 거죠.
혹시 비슷한 경험 있으신 분들 계신가요? 아니면 제가 놓친 다른 최적화 방법이 있다면 댓글로 공유해 주세요!
앞으로는 새 프로젝트 시작할 때마다 .dockerignore 배치를 첫 번째로 체크하는 습관을 들이려고 해요. 여러분도 혹시 빌드가 느리다 싶으면 빌드 컨텍스트 크기부터 확인해보세요! 😊
이 글의 정보는 2025년 8월 기준이며, 도커 버전이나 환경에 따라 다를 수 있습니다. 더 정확한 정보는 공식 문서를 참고해 주세요.
댓글
댓글 로딩 중...