도커를 이용한 프로젝트 패키징
연구·개발이 끝난 프로그램을 의뢰인에게 넘길 때, 가장 골치 아픈 부분은 코드 자체가 아니라 실행 환경이다. MySQL, Redis, Ollama, GPU 드라이버, Python 패키지 버전까지 맞추라고 하면 설치만으로 며칠이 나갈 수 있다.
그래서 이 프로젝트를 의뢰인에게 제공하는 방법으로 Docker 를 선택했다. 코드뿐 아니라 OS, 라이브러리, 설정까지 한 이미지에 담아, 개발 PC와 서버·클라이언트 환경에서 같은 실행 결과를 기대할 수 있게 하기 위함이다.
Docker 도입 이유, Compose 기반 다중 컨테이너 구성, 그리고 배포 과정에서 겪은 GPU 하드웨어 호환성 이슈와 대응을 정리한다.
Docker 사용 시 장점
Docker는 애플리케이션을 컨테이너 단위로 패키징하는 배포 도구이다. 컨테이너 안에는 프로그램 코드와 그것이 의존하는 실행 환경이 함께 들어 있다. 그래서 개발 환경이 Windows이고 운영·납품 환경이 Linux여도, 이미지만 같으면 동일한 동작을 목표로 할 수 있다.
flowchart LR
subgraph Dev["개발 환경 (Windows 등)"]
D1[Docker] --> P1[프로그램]
end
subgraph Client["사용자 / 서버 환경 (Linux 등)"]
D2[Docker] --> P2[프로그램]
end
P1 -. 동일 이미지 .-> P2
실무에서 체감한 장점은 단순하다.
- DBMS, Ollama 추론 엔진, 임베딩·LLM 모델 등 미들웨어를 호스트에 하나씩 설치할 필요가 없다.
- 이미지를 pull 하거나 tar로 전달한 뒤
docker compose up하면 기동까지 이어갈 수 있다. - 의뢰인 측 온보딩이 "환경 구축"이 아니라 "컨테이너 실행"에 가까워진다.
웹 앱, Celery 워커, Ollama처럼 GPU·CUDA에 묶인 구성요소가 많은 RAG 시스템일수록, 이 이점은 더 커진다.
Docker Compose
한 컨테이너에 모든 것을 우겨 넣는 방식도 가능하지만, 이 서비스는 성격이 다른 프로세스들이 공존한다. DB는 상태를 오래 보관하고, Redis는 메시지 브로커로 가볍게 돌며, Ollama는 GPU를 점유하고, Django와 Celery는 애플리케이션 로직을 나눈다.
Docker Compose 는 이런 구성을 다중 컨테이너 로 선언하고, 하나의 앱처럼 올리고 내리는 도구이다. 백그라운드에서 API로 요청을 처리하는 MySQL, Redis, Ollama 같은 데몬형 구성요소는 공식·검증된 이미지를 쓰는 편이 배포와 유지보수에 유리하다. 또 실행 환경을 컨테이너별로 분리할 수 있어, Celery 워커처럼 비동기·GPU 작업을 웹 프로세스와 떼어 관리하기도 쉽다.
flowchart TB
subgraph Compose["docker compose"]
Web[web<br/>Django]
Worker[worker<br/>Celery]
DB[(db<br/>MySQL)]
Redis[(redis)]
Ollama[ollama<br/>LLM / VLM / Embedding]
end
Web --> DB
Web --> Redis
Worker --> Redis
Web --> Ollama
Worker --> Ollama
Worker --> DB
현재 Compose에 정의된 서비스는 다섯 개이다.
| 서비스 | 역할 |
|---|---|
web | Django 웹 앱. 관리 UI, API, 리랭커 로드 |
worker | Celery 워커. Docling 분석, 크롤링, 임베딩 반영 등 비동기 작업 |
ollama | LLM / VLM / Embedding 추론 엔진 |
redis | Celery 브로커 + Channels 메시지 중계 |
db | MySQL. 사용자·소스·RAG 설정 등 운영 데이터 |
web 와 worker 는 같은 Dockerfile로 빌드하고 실행 명령만 다르게 둔다. ollama 는 별도 Dockerfile에서 필요한 모델을 미리 pull 해 두었다.
의뢰인 전달 시에는 GHCR 이미지를 쓰거나, 오프라인 환경을 위해 docker save / docker load 로 이미지 묶음을 넘기는 방식을 함께 준비했다. 호스트에는 NVIDIA 드라이버와 Docker만 갖추면, Compose가 나머지 스택을 올린다.
이미지 준비
웹·워커·Ollama 이미지를 빌드하거나, 전달받은 tar를 load 한다.환경 변수 검토
.env의 DB·Redis·관리자 비밀번호 등을 운영 환경에 맞게 수정한다.기동
docker compose up -d로 다섯 컨테이너를 올린다.확인
docker compose logs -f와nvidia-smi로 웹·워커·GPU 상태를 점검한다.
MySQL·Redis처럼 검증된 공식 이미지를 그대로 쓰고, GPU를 쓰는 Ollama·워커는 독립적으로 스케일·재시작할 수 있다. Celery를 웹과 같은 프로세스에 묶지 않은 것도, 앞 포스트에서 본 비동기 아키텍처를 Compose 수준에서 그대로 반영한 결과이다.
하드웨어 호환성 오류
Docker로 소프트웨어 환경은 맞춰도, GPU 하드웨어 차이 까지 없애 주지는 않는다. 배포·교차 테스트 과정에서 두 가지 호환성 문제를 만났다.
1. 부동소수점 캐스팅 문제 (Tesla T4)
개발 노트북에서 bge-m3 임베딩을 문제없이 돌린 뒤 서버에 올렸더니, 서버에서는 Ollama가 임베딩 요청을 중단하는 현상이 생겼다. 코드와 이미지는 Docker로 같았으므로, 남는 차이는 하드웨어뿐이었다.
Ollama 컨테이너 로그를 보면 GPU 연산 중 NaN(Not a Number) 이 발생했다. 나눗셈에서 분모가 극소값으로 무너져 0에 가까워질 때 생기는 전형적인 증상이고, 원인으로는 FP32 값을 FP16으로 줄이는 과정에서 정밀도가 무너지는 부동소수점 언더플로로 파악되었다.
서버 GPU는 Tesla T4 였다. bge-m3 는 FP32 기준으로 다루어지는데, Ollama가 가속을 위해 FP16 경로(Fast Attention 등)로 압축을 시도하면서 T4의 연산 한계와 맞물린 것으로 보았다. 조치로는 Flash Attention 옵션을 비활성화 해 NaN 없이 임베딩이 이어지도록 했다.
Ollama 쪽 Fast Attention은 Compose 환경 변수 OLLAMA_FLASH_ATTENTION 으로 제어한다. 최신 GPU에서는 켜 두는 편이 이득일 수 있지만, T4처럼 구형·데이터센터 GPU에서는 끄고 안정성을 우선하는 선택이 필요했다.
2. Blackwell 아키텍처 GPU 호환 문제 (RTX 5070)
다른 팀원 PC에서 문서 분석이 실패하는 사례도 있었다. 로그를 보니 PyTorch CUDA 빌드가 해당 GPU와 맞지 않는 문제였다.
팀원 GPU는 RTX 5070, NVIDIA Blackwell 아키텍처이다. 당시 쓰던 Torch 휠에는 이 아키텍처용 실행 바이너리(커널 이미지)가 없었다. Blackwell용 compute capability인 sm_120 이 포함된 CUDA 12.6 계열 PyTorch로 재설치한 뒤에야 Docling 등 GPU 의존 작업이 정상 동작했다.
flowchart TD
A[증상] --> B{환경}
B -->|서버 Tesla T4| C[임베딩 중 NaN]
B -->|팀원 RTX 5070| D[문서 분석 실패]
C --> E[FP32→FP16 정밀도 문제]
D --> F[PyTorch에 sm_120 커널 없음]
E --> G[Flash Attention 비활성화]
F --> H[CUDA 12.6 / Blackwell 지원 Torch로 교체]
| 이슈 | 환경 | 원인 | 대응 |
|---|---|---|---|
| 임베딩 NaN | Tesla T4 서버 | FP16 가속 경로의 정밀도 한계 | Flash Attention 비활성 |
| Docling 실패 | RTX 5070 | Torch가 Blackwell(sm_120) 미지원 | CUDA 12.6 계열 Torch로 재설치 |
Docker는 "어느 머신에서나 같은 소프트웨어 스택"을 보장하지만, GPU 세대가 갈리면 커널·정밀도·드라이버 조합을 다시 검증해야 한다. RAG처럼 추론 엔진과 문서 분석이 GPU에 기대는 시스템에서는, 이미지 패키징만큼이나 대상 하드웨어 매트릭스 를 운영 문서에 남기는 일이 중요하다.
이 시리즈에서는 벡터 검색 이론부터 PDF 분석, VLM 캡셔닝, Django·비동기·웹 소켓, 검색 품질 개선, 그리고 Docker 배포까지 RAG 챗봇을 실제로 만들고 넘기기까지의 과정을 정리했다. 컨테이너로 포장하는 순간이 끝이 아니라, 그 위에서 돌아가는 하드웨어까지 염두에 둔 배포가 마지막 관문이었다.