AI를 위한 Docker
컨테이너(container)는 "내 컴퓨터에서는 되는데요"라는 말을 과거의 일로 만듭니다.
유형: Build
언어: Python
선수 지식: Phase 0, Lessons 01 and 03
예상 시간: 약 60분
학습 목표
- Dockerfile에서 CUDA, PyTorch, AI 라이브러리가 포함된 GPU 지원 Docker 이미지(GPU-enabled Docker image)를 빌드합니다.
- 호스트(host) 디렉터리를 볼륨(volume)으로 마운트(mount)해 모델, 데이터셋, 코드를 컨테이너 재빌드(rebuild) 이후에도 유지합니다.
- NVIDIA Container Toolkit을 설정해 컨테이너 안에서 GPU를 사용할 수 있게 합니다.
- Docker Compose로 추론 서버(inference server)와 벡터 데이터베이스(vector database) 같은 다중 서비스(multi-service) AI 애플리케이션을 구성합니다.
문제
내 노트북에는 Python 3.13, PyTorch 2.3, CUDA 12.4가 있습니다. 동료 컴퓨터에는 Python 3.10, PyTorch 2.1, CUDA 11.8이 있습니다. 내 모델이 동료 컴퓨터에서 충돌(crash)합니다. 같은 Dockerfile에서 빌드한 컨테이너는 두 컴퓨터에서 같은 환경을 제공합니다.
AI 프로젝트는 의존성 지옥(dependency nightmare)이 되기 쉽습니다. Python, PyTorch, CUDA 드라이버, cuDNN, 시스템 수준 C 라이브러리, flash-attn처럼 컴파일러 버전에 민감한 패키지가 한 스택 안에 들어옵니다. Docker는 이 환경을 하나의 이미지로 묶어 어디서나 동일하게 실행할 수 있게 합니다.
개념
Docker는 코드, 런타임, 라이브러리, 시스템 도구를 컨테이너라는 격리 단위(isolated unit)로 감쌉니다. 가벼운 가상머신(virtual machine)처럼 생각할 수 있지만, 컨테이너는 호스트 OS 커널을 공유하므로 VM보다 훨씬 빠르게 시작합니다.
graph TD
subgraph without["Docker가 없을 때"]
A1["Your machine<br/>Python 3.13<br/>CUDA 12.4<br/>PyTorch 2.3"] -->|crashes| X1["???"]
A2["Their machine<br/>Python 3.10<br/>CUDA 11.8<br/>PyTorch 2.1"] -->|crashes| X2["???"]
A3["Server<br/>Python 3.11<br/>CUDA 12.1<br/>PyTorch 2.2"] -->|crashes| X3["???"]
end
subgraph with_docker["Docker를 쓸 때 — 모든 곳에서 같은 image"]
B1["Your machine<br/>Python 3.13 | CUDA 12.4<br/>PyTorch 2.3 | Your code"]
B2["Their machine<br/>Python 3.13 | CUDA 12.4<br/>PyTorch 2.3 | Your code"]
B3["Server<br/>Python 3.13 | CUDA 12.4<br/>PyTorch 2.3 | Your code"]
end
AI 프로젝트에서 Docker가 특히 중요한 이유
-
GPU 드라이버는 깨지기 쉽습니다(fragile). CUDA 12.4 코드는 CUDA 11.8 환경에서 그대로 동작하지 않을 수 있습니다. Docker는 컨테이너 안의 CUDA 툴킷을 분리하고, NVIDIA Container Toolkit을 통해 호스트 GPU 드라이버를 공유합니다.
-
모델 가중치(model weight)는 큽니다. 70억 파라미터 모델은 fp16 기준 약 14 GB입니다. 컨테이너를 재빌드할 때마다 다시 다운로드하고 싶지 않습니다. Docker 볼륨은 호스트의 모델 디렉터리를 컨테이너에 마운트합니다.
-
다중 서비스 아키텍처가 흔합니다. 실제 AI 애플리케이션은 Python 스크립트 하나가 아닙니다. 추론 서버, RAG용 벡터 데이터베이스, 웹 프론트엔드(web frontend)가 함께 동작할 수 있습니다. Docker Compose는 이 서비스들을 한 명령으로 실행합니다.
핵심 용어 정리
| 용어 | 의미 |
|---|
| 이미지(Image) | 읽기 전용(read-only) 템플릿입니다. Dockerfile에서 빌드됩니다. |
| 컨테이너(Container) | 이미지를 실행한 인스턴스입니다. |
| Dockerfile | 이미지를 레이어(layer)별로 빌드하는 명령(instruction)입니다. |
| 볼륨(Volume) | 컨테이너 재시작 이후에도 남는 영속 저장소(persistent storage)입니다. |
| docker-compose | 다중 컨테이너 애플리케이션을 YAML로 정의하는 도구입니다. |
AI에서 흔한 컨테이너 패턴
개발 컨테이너(Dev Container)
완전한 툴킷. 에디터 지원. Jupyter. 디버깅 도구.
개발과 실험 중 사용합니다.
훈련 컨테이너(Training Container)
최소 구성. 훈련 스크립트와 의존성만.
GPU 클러스터에서 실행합니다. 에디터나 Jupyter는 보통 없습니다.
추론 컨테이너(Inference Container)
서빙(serving)에 최적화. 작은 이미지. 빠른 콜드 스타트(cold start).
프로덕션 서빙 환경에서 사용합니다.
직접 만들기
Step 1: Docker 설치
brew install --cask docker
open /Applications/Docker.app
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
확인합니다.
docker --version
docker run hello-world
이 도구가 있어야 Docker 컨테이너 안에서 GPU를 사용할 수 있습니다. macOS와 Windows WSL2 사용자는 플랫폼별 GPU 패스스루(passthrough) 방식이 다르므로 Docker Desktop 문서를 확인합니다.
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
컨테이너 안에서 GPU가 보이는지 확인합니다.
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi
GPU 정보가 보이면 툴킷이 동작합니다.
Step 3: 베이스 이미지(Base image) 이해
베이스 이미지 선택은 디버깅 시간을 크게 줄입니다.
nvidia/cuda:12.4.1-devel-ubuntu22.04
완전한 CUDA 툴킷. 컴파일러 포함.
사용처: nvcc가 필요한 패키지 빌드 (flash-attn, bitsandbytes)
크기: 약 4 GB
nvidia/cuda:12.4.1-runtime-ubuntu22.04
CUDA 런타임만 포함. 컴파일러 없음.
사용처: 이미 빌드된 코드 실행
크기: 약 1.5 GB
pytorch/pytorch:2.3.1-cuda12.4-cudnn9-runtime
CUDA 위에 PyTorch가 미리 설치되어 있음.
사용처: PyTorch 설치 단계를 생략
크기: 약 6 GB
python:3.13-slim
CUDA 없음. CPU 전용.
사용처: CPU 추론, 가벼운 도구
크기: 작음
Step 4: AI 개발(development)용 Dockerfile 작성
code/Dockerfile은 CUDA 베이스 이미지 위에 uv로 Python 3.13 환경을 만들고 PyTorch와 AI 라이브러리를 설치합니다.
FROM nvidia/cuda:12.4.1-devel-ubuntu22.04
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
git \
curl \
build-essential \
&& rm -rf /var/lib/apt/lists/*
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
ENV PATH="/root/.local/bin:/opt/venv/bin:${PATH}"
ENV VIRTUAL_ENV=/opt/venv
ENV UV_LINK_MODE=copy
RUN uv python install 3.13 \
&& uv venv /opt/venv --python 3.13
빌드합니다.
docker build -t ai-dev -f phases/00-setup-and-tooling/07-docker-for-ai/code/Dockerfile .
처음에는 CUDA 베이스 이미지와 PyTorch를 내려받으므로 시간이 걸립니다. 다음 빌드부터는 변경되지 않은 레이어(unchanged layer)가 캐시됩니다.
실행합니다.
docker run --rm -it --gpus all \
-v $(pwd):/workspace \
-v ~/models:/models \
ai-dev python -c "import torch; print(f'PyTorch {torch.__version__}, CUDA: {torch.cuda.is_available()}')"
컨테이너 안에서 Jupyter를 실행합니다.
docker run --rm -it --gpus all \
-v $(pwd):/workspace \
-v ~/models:/models \
-p 8888:8888 \
ai-dev jupyter notebook --ip=0.0.0.0 --port=8888 --no-browser --allow-root
Step 5: 데이터와 모델을 위한 볼륨 마운트
볼륨 마운트는 AI 작업에서 중요합니다. 없으면 컨테이너가 멈출 때 내부에 받은 14 GB 모델도 사라질 수 있습니다.
-v $(pwd):/workspace
-v ~/models:/models
-v ~/datasets:/data
훈련 스크립트에서는 마운트된 경로에서 모델을 읽습니다.
from transformers import AutoModel
model = AutoModel.from_pretrained("/models/llama-7b")
모델은 호스트 파일시스템(filesystem)에 남습니다. 컨테이너를 여러 번 재빌드해도 다시 다운로드할 필요가 없습니다.
Step 6: 다중 서비스 AI 앱을 위한 Docker Compose
RAG 애플리케이션에는 추론 서버와 벡터 데이터베이스가 함께 필요합니다. Docker Compose는 둘을 한 명령으로 실행합니다.
code/docker-compose.yml을 봅니다.
services:
ai-dev:
build:
context: .
dockerfile: Dockerfile
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
volumes:
- ../../../:/workspace
- ~/models:/models
- ~/datasets:/data
ports:
- "8888:8888"
stdin_open: true
tty: true
command: jupyter notebook --ip=0.0.0.0 --port=8888 --no-browser --allow-root
qdrant:
image: qdrant/qdrant:v1.12.5
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant_data:/qdrant/storage
volumes:
qdrant_data:
실행합니다.
cd phases/00-setup-and-tooling/07-docker-for-ai/code
docker compose up -d
AI 개발 컨테이너는 http://qdrant:6333 서비스 이름(service name)으로 벡터 데이터베이스에 접근할 수 있습니다. Docker Compose가 공유 네트워크(shared network)를 자동으로 만듭니다.
from qdrant_client import QdrantClient
client = QdrantClient(host="qdrant", port=6333)
print(client.get_collections())
중지합니다.
docker compose down
Qdrant 볼륨까지 지우려면 -v를 붙입니다.
docker compose down -v
Step 7: AI 작업에서 자주 쓰는 Docker 명령
docker ps
docker images
docker system prune -a
docker exec -it <container_id> nvidia-smi
docker cp <container_id>:/workspace/results.csv ./results.csv
docker logs -f <container_id>
docker system prune -a는 사용하지 않는 이미지를 크게 정리합니다. 필요한 캐시까지 지울 수 있으므로 실행 전 무엇을 지우는지 확인합니다.
사용해보기
이제 재현 가능한 AI 개발 환경(reproducible AI development environment)이 있습니다. 이후 과정에서는 아래 기준을 사용합니다.
docker compose up으로 개발 환경과 벡터 데이터베이스를 함께 시작합니다.
- 코드, 모델, 데이터는 볼륨으로 마운트해 재빌드 사이에 사라지지 않게 합니다.
- 강의에서 새 Python 패키지가 필요하면 Dockerfile에 추가하고 재빌드합니다.
- Dockerfile을 동료와 공유하면 같은 환경을 재현할 수 있습니다.
GPU가 없다면
--gpus all 플래그와 NVIDIA deploy 블록을 제거합니다. CPU 기반 강의에서는 컨테이너가 계속 동작합니다. PyTorch는 CUDA가 없으면 CPU로 폴백(fallback)합니다.
연습문제
- Dockerfile을 빌드하고 컨테이너 안에서
python -c "import torch; print(torch.__version__)"를 실행합니다.
- docker-compose 스택을 시작하고 AI 컨테이너에서
http://qdrant:6333/collections에 접근할 수 있는지 확인합니다.
- Dockerfile에
flask를 추가하고 재빌드한 뒤 5000 포트의 간단한 API 서버를 실행합니다. -p 5000:5000으로 포트를 매핑합니다.
docker images로 이미지 크기를 측정합니다. 베이스 이미지를 devel에서 runtime으로 바꿔 크기 차이를 비교합니다.
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| 컨테이너(Container) | 가벼운 VM | 호스트 커널을 공유하는 격리된 프로세스와 파일시스템 |
| 이미지 레이어(Image layer) | 캐시된 단계 | Dockerfile 명령마다 생기는 레이어이며 변경되지 않으면 재사용된다 |
| NVIDIA Container Toolkit | Docker에서 GPU 사용 | --gpus 플래그로 호스트 GPU를 컨테이너에 노출하는 런타임 훅(runtime hook) |
| 볼륨 마운트(Volume mount) | 공유 폴더 | 호스트 디렉터리를 컨테이너 안 경로로 연결해 컨테이너가 멈춰도 데이터를 유지한다 |
| 베이스 이미지(Base image) | 시작점 | Dockerfile의 FROM 이미지이며 미리 설치된 스택(pre-installed stack)을 결정한다 |
더 읽을거리