작은 설정 하나가 운영 서버를 멈추게 합니다

어제까지만 해도 아무 문제 없이 잘 돌아가던 서비스가 갑자기 응답하지 않아요. 원인을 파악하려고 로그를 살펴보니 빌드 과정에서 무언가 잘못되었다는 메시지가 가득합니다. 당황스러운 마음에 설정을 뒤져보지만, 눈에 띄는 오류는 보이지 않아요. 사실 문제는 아주 사소한 곳에 숨어 있었습니다. 컴포즈 빌드 옵션 실수 하나가 거대한 빌드 컨텍스트를 불러왔고, 이로 인해 빌드 서버의 디스크가 가득 차버린 것이죠.
서버 관리자나 데브옵스 엔지니어라면 이런 경험이 한 번쯤은 있을 거예요. 컨테이너 환경은 매우 편리하지만, 설정 파일 하나에 담긴 의미를 정확히 파악하지 못하면 예상치 못한 재앙이 발생합니다. 단순히 ‘코드를 컨테이너로 만든다’는 생각만으로는 부족해요. 어떤 파일이 빌드 과정에 포함되는지, 어떤 경로를 기준으로 동작하는지를 명확히 제어해야 합니다.
운영 환경에서의 작은 실수는 단순히 빌드가 실패하는 것으로 끝나지 않아요. 민감한 설정 파일이 이미지 안에 포함되어 유출되거나, 불필요한 대용량 데이터가 전송되면서 전체 배포 파이프라인이 마비되기도 합니다. 빌드 컨텍스트(Build Context)의 범위를 잘못 지정하는 것은 컨테이너 운영에서 가장 빈번하면서도 치명적인 실수 중 하나예요.
이 글에서는 운영 사고로 이어질 수 있는 구체적인 빌드 설정 패턴들을 분석하고, 이를 방지하기 위한 안전한 대안들을 제시해 드릴게요. 지금 바로 확인해서 여러분의 설정 파일에 위험 요소가 없는지 점검해 보세요.
- 빌드 컨텍스트 설정 오류가 미치는 영향
- 운영 사고를 유발하는 흔한 빌드 옵션 패턴
- 보안과 효율을 모두 잡는 안전한 빌드 설정법
- 실무에서 즉시 적용 가능한 체크리스트
빌드 컨텍스트와 빌드 옵션의 핵심 원리
본격적으로 실수를 찾아내기 전에, 우리가 다루는 용어들이 정확히 무엇을 의미하는지 짚고 넘어가야 해요. 도커 컴포즈에서 빌드를 수행할 때 가장 중요한 개념은 바로 빌드 컨텍스트입니다.
빌드 컨텍스트란 도커 클라이언트가 빌드 프로세스를 시작할 때, 빌드 데몬에게 전달하는 파일들의 집합을 말해요. 우리가 `docker-compose.yml` 파일에 `context: .`이라고 적는 순간, 현재 디렉토리에 있는 모든 파일과 폴더가 빌드 데몬으로 복사되어 전송됩니다. 만약 이 디렉토리에 수십 기가바이트의 데이터나 수만 개의 로그 파일이 있다면 어떻게 될까요? 빌드가 시작되기도 전에 파일을 전송하는 데만 수십 분이 걸리거나, 서버 용량이 부족해 빌드가 중단되는 사고가 발생하게 됩니다.
또한, Dockerfile 경로 설정도 매우 중요해요. `context`로 지정된 폴더 내에서 `dockerfile`을 찾는데, 이 경로를 잘못 지정하면 도커는 파일을 찾지 못해 에러를 내뱉습니다. 단순히 파일이 없다는 에러를 넘어, 엉뚱한 경로의 파일을 참조하여 잘못된 이미지가 생성될 수도 있어요.
빌드 옵션을 결정할 때는 다음의 세 가지 기준을 반드시 고려해야 해요. 첫째는 보안, 둘째는 속도, 셋째는 이미지의 크기입니다. 이 세 가지가 균형을 이루지 못하면 운영 단계에서 반드시 문제가 생깁니다.
| 설정 방식 | 주요 특징 | 주의사항 |
|---|---|---|
| 전체 디렉토리 지정 (context: .) | 설정이 간편하고 모든 파일을 쉽게 참조 가능 | 불필요한 파일까지 모두 전송됨 |
| 특정 하위 디렉토리 지정 | 빌드에 필요한 파일만 한정하여 전송 가능 | 상위 디렉토리 파일 참조가 어려움 |
| 멀티 스테이지 빌드 활용 | 최종 이미지 크기를 획기적으로 줄임 | Dockerfile 구조 설계가 복잡해짐 |
이처럼 빌드 옵션은 단순히 ‘돌아가게 만드는 것’에 그쳐서는 안 돼요. 어떤 파일을 빌드 데몬에 보낼 것인가를 결정하는 전략적인 선택이 필요합니다. 이를 제대로 수행하지 못하면 배포 속도가 느려지는 것은 물론, 보안 취약점을 가진 이미지를 생성하게 됩니다.
운영 사고로 이어지는 단계별 빌드 실수 패턴
이제 실무에서 실제로 빈번하게 발생하는 사고 패턴들을 단계별로 살펴볼게요. 각 단계는 여러분이 현재 작성 중인 설정 파일에 숨어 있는 시한폭탄일 수도 있습니다.
STEP 1. 빌드 컨텍스트 범위 설정 오류
가장 흔하면서도 파괴적인 실수는 빌드 컨텍스트를 너무 넓게 잡는 거예요. 많은 개발자가 관행적으로 `context: .`을 사용하곤 합니다. 프로젝트 루트 디렉토리를 컨텍스트로 지정하면 편리하긴 하지만, 그 안에는 빌드에 필요 없는 수많은 파일이 들어있을 확률이 매우 높아요.
예를 들어, 프로젝트 폴더 안에 대용량 데이터셋, `.git` 디렉토리, 로컬 로그 파일, 혹은 컴파일된 바이너리 파일들이 들어있다면 어떻게 될까요? 도커 클라이언트는 빌드를 시작할 때 이 모든 파일을 압축해서 빌드 데몬으로 보내려고 시도합니다. 이 과정에서 네트워크 대역폭을 엄청나게 잡아먹고, 심하면 빌드 서버의 디스크 공간이 순식간에 바닥나버려요.
실제로 제가 경험한 사례 중 하나는, 한 개발자가 프로젝트 루트에 있는 10GB 크기의 데이터 백업 파일을 인지하지 못한 채 빌드를 실행한 적이 있었어요. 빌드가 시작되자마자 ‘Sending build context to Docker daemon’ 단계에서 멈춘 듯 보였고, 결국 CI/CD 서버의 디스크가 꽉 차면서 전체 배포 시스템이 중단되는 대형 사고로 이어졌습니다.
STEP 2. .dockerignore 파일 누락 및 보안 사고
빌드 컨텍스트의 문제를 해결하는 가장 좋은 방법은 .dockerignore 파일을 사용하는 것이에요. 하지만 이 파일을 깜빡하거나, 혹은 잘못 설정하는 경우가 정말 많습니다. .dockerignore는 빌드 컨텍스트에서 제외할 파일 패턴을 정의하는 파일인데, 이게 없으면 앞서 말한 컨텍스트 과부하 문제가 그대로 발생합니다.
더 심각한 문제는 보안 데이터 유출이에요. `.env` 파일, 개인 인증 키(SSH key), 혹은 데이터베이스 접속 정보가 담긴 설정 파일이 .dockerignore에 등록되어 있지 않다면, 이 민감한 정보들이 그대로 도커 이미지 레이어 안에 포함됩니다. 이미지는 생성되어 레지스트리에 올라가고, 누군가 이 이미지를 가져가서 레이어를 분석하면 여러분의 비밀번호를 그대로 읽을 수 있게 되는 거죠.
.dockerignore 파일은 선택이 아닌 필수입니다. 최소한 `.git`, `.env`, `node_modules`, `*.log` 등은 반드시 포함시켜야 합니다.
STEP 3. Dockerfile 경로 지정 및 상대 경로 혼란
컴포즈 파일에서 `dockerfile` 옵션을 사용할 때 경로를 잘못 지정하는 실수도 잦아요. `context`는 현재 디렉토리를 기준으로 하지만, `dockerfile` 경로가 꼬이면 엉뚱한 파일을 참조하게 됩니다.
흔히 하는 실수는 `context`를 특정 하위 폴더로 지정해 놓고, 그 안의 파일을 참조하기 위해 `dockerfile` 경로를 절대 경로처럼 적거나 상위 폴더로 올라가려는 시도를 하는 거예요. 하지만 빌드 데몬은 지정된 컨텍스트 밖의 파일에는 접근할 수 없습니다. 즉, `context: ./app`이라고 설정했다면, `Dockerfile` 안에서 `COPY ../config.json .`과 같은 명령은 절대로 작동하지 않아요.
STEP 4. 레이어 캐시 최적화 실패로 인한 빌드 지연
빌드 속도가 너무 느리다면, Dockerfile의 명령 순서가 잘못되었을 가능성이 큽니다. 도커는 각 명령어를 레이어로 저장하고, 내용이 바뀌지 않았다면 이전 레이어를 재사용(캐싱)합니다. 그런데 만약 소스 코드를 복사하는 `COPY . .` 명령을 파일 설치 명령(예: `npm install` 또는 `pip install`)보다 먼저 배치한다면 어떻게 될까요?
소스 코드가 단 한 줄만 수정되어도 도커는 모든 소스 코드가 바뀌었다고 판단하여, 그 아래에 있는 모든 설치 과정을 처음부터 다시 수행합니다. 매번 빌드할 때마다 수백 개의 패키지를 다시 다운로드받느라 시간을 허비하게 되는 것이죠. 효율적인 빌드를 위해서는 변하지 않는 의존성 파일(package.json, requirements.txt 등)을 먼저 복사하고 설치한 뒤, 자주 바뀌는 소스 코드를 나중에 복사하는 전략을 써야 합니다.
STEP 5. 빌드 인자와 환경 변수의 혼동
마지막으로, 빌드 시점에 필요한 값을 전달할 때 `build-arg`를 써야 할 자리에 `environment`를 쓰는 실수가 있습니다. `environment`는 컨테이너가 실행될 때 사용하는 변수이고, `build-arg`는 이미지를 만드는 과정에서 사용하는 변수예요.
만약 빌드 중에 특정 버전의 라이브러리를 내려받기 위해 변수가 필요하다면 반드시 `args` 섹션에 정의해야 합니다. 이를 혼동하면 빌드 과정에서 변수가 전달되지 않아 기본값으로 빌드되거나, 빌드가 실패하는 상황이 발생합니다. 또한, 비밀번호 같은 민감한 정보를 `build-arg`로 넘기는 것도 위험해요. 빌드 인자는 이미지의 히스토리에 남기 때문에, 나중에 누구나 확인할 수 있기 때문입니다. 민감 정보는 빌드 인자가 아닌, 컨테이너 실행 시점에 시크릿(Secrets) 기능을 통해 주입하는 것이 가장 안전합니다.
이러한 실수들을 방지하기 위한 올바른 설정 예시를 하나 보여드릴게요.
services:
web:
build:
context: ./app
dockerfile: Dockerfile
args:
- APP_VERSION=1.2.3
# 민감 정보는 build-arg가 아닌 외부 파일이나 secret 사용 권장
자주 하는 실수와 해결법
지금까지 살펴본 내용을 바탕으로, 실무에서 마주칠 수 있는 구체적인 문제 상황과 그에 대한 명쾌한 해결책을 정리했습니다. 비슷한 상황을 겪고 있다면 이 부분을 먼저 체크해 보세요.
- ❌ 실수: 빌드할 때마다 ‘Sending build context…’ 단계에서 시간이 너무 오래 걸림
➡️ 원인: 빌드 컨텍스트에 불필요한 대용량 파일이 포함되어 있음
➡️ ✅ 해결법: .dockerignore 파일을 생성하여 데이터, 로그, .git 폴더 등을 제외하세요. - ❌ 실수: Dockerfile에서 COPY 명령을 사용했는데 파일을 찾을 수 없다고 나옴
➡️ 원인: context 경로와 Dockerfile 내부의 상대 경로가 일치하지 않음
➡️ ✅ 해결법: context를 파일들이 모여 있는 상위 디렉토리로 잡고, 경로를 컨텍스트 기준으로 재설정하세요. - ❌ 실수: 이미지를 배포했는데 로그를 보니 API 키가 노출됨
➡️ 원인: .env 파일이나 설정 파일을 .dockerignore 없이 빌드에 포함함
➡️ ✅ 해결법: 모든 민감 정보 파일은 .dockerignore에 반드시 등록하고, 실행 시점에 환경 변수로 주입하세요. - ❌ 실수: 코드 한 줄 고쳤는데 빌드 시간이 10분 넘게 걸림
➡️ 원인: 의존성 설치 명령(npm install 등) 뒤에 소스 코드 복사 명령을 배치함
➡️ ✅ 해결법: 의존성 파일(package.json 등)을 먼저 복사하여 캐시를 활용하도록 Dockerfile 순서를 바꾸세요. - ❌ 실수: 빌드 인자로 넘긴 변수가 컨테이너 실행 시점에 적용되지 않음
➡️ 원인: build-arg와 environment의 개념을 혼동함
➡️ ✅ 해결법: 빌드 시 필요한 값은 args에, 실행 시 필요한 값은 environment에 정의하세요.
자주 묻는 질문
Q. 빌드 컨텍스트를 최대한 작게 유지하는 것이 왜 그렇게 중요한가요?
빌드 컨텍스트가 커지면 파일 전송 시간이 길어져 전체 파이프라인이 느려질 뿐만 아니라, 빌드 서버의 자원을 과도하게 사용하게 됩니다. 또한, 불필요한 파일이 이미지 레이어에 포함되면 이미지 크기가 커져 저장소 비용이 늘어나고 배포 속도도 떨어집니다. 효율적인 운영을 위해서는 ‘필요한 것만 보낸다’는 원칙을 지켜야 해요.
Q. .dockerignore 파일에 무엇을 넣어야 할지 잘 모르겠어요. 가이드가 있을까요?
기본적으로 프로젝트의 메타데이터인 <.git>, 의존성 폴더인
Q. 멀티 스테이지 빌드를 사용하면 빌드 옵션 실수를 줄일 수 있나요?
직접적으로 실수를 막아주지는 않지만, 결과물의 품질을 높이는 데 매우 효과적이에요. 빌드 단계에서 사용한 무거운 도구들을 최종 이미지에는 포함하지 않도록 설정할 수 있기 때문에, 실수로 컨텍스트를 넓게 잡았더라도 최종 이미지 크기가 커지는 피해를 최소화할 수 있습니다.
Q. 빌드 인자(build-arg)를 사용하면 보안상 위험한가요?
네, 위험할 수 있어요. 빌드 인자는 도커 이미지의 레이어 히스토리에 평문으로 남습니다. 따라서 비밀번호, API 키, 인증 토큰 등을 build-arg로 전달하는 것은 절대 금물입니다. 이런 정보는 컨테이너 실행 시점에 환경 변수나 도커 시크릿(Docker Secrets)을 통해 주입하는 방식을 사용해야 합니다.
Q. Dockerfile의 COPY 명령 순서를 바꾸는 것만으로도 속도가 빨라지나요?
네, 상당한 차이가 납니다. 패키지 매니저를 통한 라이브러리 설치는 시간이 오래 걸리는 작업이에요. 이 작업을 캐싱해두면, 소스 코드가 바뀔 때마다 매번 수백 개의 라이브러리를 새로 받는 낭비를 막을 수 있습니다. 이것만 잘해도 빌드 시간을 수 분에서 수 초로 단축할 수 있어요.
안전한 배포를 위한 마지막 점검
도커 컴포즈를 활용한 컨테이너 운영은 매우 강력하지만, 그만큼 세밀한 설정 관리가 요구됩니다. 오늘 배운 내용들을 머릿속에 담아두고, 배포 직전에 다음 사항들을 반드시 다시 한번 확인해 보세요. 작은 습관이 운영 사고를 막는 가장 강력한 방어선이 됩니다.
- 빌드 컨텍스트는 반드시 필요한 파일만 포함하도록 제한하세요.
- .dockerignore 파일을 작성하여 불필요한 파일과 민감 정보를 차단하세요.
- Dockerfile 내에서 의존성 설치와 소스 코드 복사 순서를 최적화하세요.
- 민감한 정보는 build-arg 대신 runtime 환경 변수나 시크릿을 사용하세요.
- 경로 설정 오류를 방지하기 위해 context와 dockerfile의 상대 경로를 재검증하세요.
- 멀티 스테이지 빌드를 사용하여 최종 이미지 크기를 최소화하세요.
지금 바로 여러분의 프로젝트 폴더에 있는 .dockerignore 파일이 있는지, 그리고 docker-compose.yml의 context 설정이 너무 넓지는 않은지 확인해 보세요. 만약 의심되는 부분이 있다면 지금 수정하는 것이 나중에 발생할 대형 사고를 막는 가장 빠른 길입니다.
오늘 바로 실천할 일: 현재 사용 중인 docker-compose.yml 파일의 build 섹션을 열어보고, context 경로가 프로젝트 루트인지 확인한 뒤 .dockerignore 파일을 업데이트하세요.
관련하여 더 깊이 있는 내용을 알고 싶다면, 도커 컴포즈 기본 개념 완벽 정리 — 개념부터 실무 활용까지 한눈에 보는 가이드를 참고해 보시는 것을 추천합니다.