본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 19. 14:24

혼돈에서 일관성으로: 현대적 AI 워크플로우를 위한 Docker

요약

AI 프로젝트의 복잡한 의존성 문제를 해결하기 위한 Docker 활용법을 다룹니다. Python 패키지부터 CUDA 스택까지 환경 일관성을 유지하는 방법을 설명합니다.

핵심 포인트

  • AI 프로젝트는 Python, 시스템 라이브러리, CUDA 등 다층적 의존성을 가짐
  • Docker는 OS, 패키지, 설정을 포함한 완전한 스냅샷을 제공함
  • 가상 머신과 달리 컨테이너는 호스트 커널을 공유하여 가볍고 빠름
  • Dockerfile을 통해 팀원 및 서버 간 동일한 실행 환경 구축 가능

당신은 모델을 학습시켰습니다. 노트북(Notebook)은 잘 돌아갑니다. 데모도 작동합니다. 팀원에게 결과물을 보냈는데, 40분 뒤 모든 엔지니어가 두려워하는 메시지가 도착합니다:

"저기, CUDA 에러가 나요. 그리고 torch가 임포트(import)되지 않아요. 파이썬(Python) 버전이 대체 뭐예요?"

그러면 당신은 소프트웨어 역사의 시작부터 엔지니어를 괴롭혀온 여섯 마디 말을 내뱉게 됩니다:

"제 컴퓨터에서는 잘 되는데요."

여기 불편한 진실이 있습니다. _"제 컴퓨터에서는 잘 돼요"_라는 말은 방어가 아닙니다. 그것은 자백입니다. 당신의 코드가 당신의 노트북에 있는 무언가에 의존하고 있다는 뜻입니다. 파이썬(Python) 버전, 시스템 라이브러리 (system library), CUDA 툴킷 (CUDA toolkit), 길을 잃고 헤매는 환경 변수 (environment variable), 혹은 ~/Downloads 폴더에 놓여 있는 모델 파일 같은 것들 말이죠.

Docker는 그러한 자백을 멈추는 방법입니다. 이제 이 문제를 해결해 봅시다.

진짜 문제: AI 프로젝트는 의존성 괴물이다

전형적인 웹 앱은 몇 가지 의존성 (dependencies)을 가집니다. 하지만 AI 프로젝트는 의존성의 '계층'을 가지고 있으며, 각 계층이 당신을 배신할 수 있습니다:

  • Python 패키지 (Python packages): torch, transformers, numpy, 그리고 이들 사이의 버전 충돌.
  • 시스템 라이브러리 (System libraries): libgl1이나 ffmpeg처럼 pip가 대신 설치해주지 않는 것들.
  • CUDA / 드라이버 스택 (CUDA / driver stack): "제 컴퓨터에서는 되는데 다른 곳에선 안 돼요"라는 말이 발생하는 가장 흔한 이유.
  • 모델 가중치 (model weights) 자체: 레포지토리(repo)에 포함되지 않는 수 기가바이트(GB) 단위의 파일들.
  • Python 자체: 노트북에는 3.10 버전, 서버에는 3.12 버전이 설치되어 있어 곳곳에서 미묘한 오류가 발생함.

requirements.txt는 이 다섯 가지 계층 중 '하나'만을 포착합니다. Docker는 이 모든 것을 포착합니다. 이것이 Docker를 사용하는 핵심 이유입니다.

Docker란 실제로 무엇인가?

고래 로고와 유행어들은 잠시 잊으세요.

**Docker 이미지 (Docker image)**는 완전한 컴퓨터의 동결된 스냅샷(snapshot)입니다. 운영체제(OS), Python, 패키지, 코드, 그리고 설정(config)이 모두 하나의 파일로 구워져 있습니다. **컨테이너 (container)**는 그 스냅샷의 실행 중인 복사본입니다.

이해를 돕는 사고 모델(mental model)은 다음과 같습니다. 가상 머신 (Virtual Machine)은 자체 운영체제 커널 (kernel)을 포함하여 컴퓨터 전체를 시뮬레이션하므로 무겁고 느립니다. 반면 컨테이너 (container)는 호스트 머신의 커널을 공유하며 그 '위'에 있는 모든 것만을 패키징합니다. 따라서 컨테이너는 몇 분이 아닌 몇 초 만에 부팅되며, 단 하나의 이미지 (image)가 당신의 노트북, 팀원의 노트북, 그리고 클라우드 GPU 서버에서 동일하게 실행됩니다.

당신은 레시피를 한 번만 작성합니다. 그러면 모두가 정확히 똑같은 주방을 갖게 됩니다.

PyTorch 프로젝트를 위한 첫 번째 Dockerfile

Dockerfile은 바로 그 레시피, 즉 지침이 담긴 일반 텍스트 파일입니다. 다음은 PyTorch 프로젝트를 위한 실제 예시이며, 각 줄에 대한 설명이 포함되어 있습니다:

# 공식 Python 이미지에서 시작합니다. "-slim" 변형은 크기가 더 작습니다.
FROM python:3.11-slim

...

이를 통해 피할 수 있는 두 가지 초보적인 실수는 다음과 같습니다:

1. 버전을 고정(Pin)하세요. requirements.txt는 단순히 패키지 이름만 적는 것이 아니라 다음과 같이 작성되어야 합니다:

torch==2.3.1
transformers==4.41.2
fastapi==0.111.0
...

버전이 명시되지 않은 torch는 미래에 장애가 발생할 가능성을 내포하고 있습니다. Docker의 핵심 목적은 재현성 (reproducibility)입니다. 버전이 유동적으로 변하게 두어 이 목적을 훼손하지 마세요.

2. 코드보다 requirements.txt를 먼저 복사하세요. Docker는 레이어 (layer) 단위로 빌드하며 각 레이어를 캐시 (cache)합니다. 만약 모든 것을 한꺼번에 복사한다면, 코드 한 줄만 변경되어도 매 빌드마다 torch를 다시 설치해야 합니다 (수 분이 소요되는 다운로드 과정). 요구 사항(requirements)을 먼저 복사함으로써, Docker는 캐시된 설치 레이어를 재사용하고 실제로 변경된 단계만 다시 실행합니다. 이를 통해 빌드 시간이 분 단위에서 초 단위로 단축됩니다.

빌드 및 실행 방법:

docker build -t my-model .
docker run my-model

-t my-model은 단순히 이미지에 이름을 붙이는 것입니다. .은 Docker에게 현재 폴더에서 Dockerfile을 찾으라고 지시합니다. 이것으로 끝입니다. 이제 당신은 이식 가능하고 재현 가능한 모델을 갖게 되었습니다.

모델 가중치 (weights)를 이미지에 포함하지 마세요

초보자들은 종종 COPY model.bin을 사용하여 모델 가중치를 이미지에 직접 넣곤 합니다. 그러지 마세요. 5GB 크기의 이미지는 빌드, 푸시 (push), 풀 (pull) 과정이 매우 고통스러우며, 가중치가 변경될 때마다 이미지를 다시 빌드해야 합니다.

대신, 대용량 파일은 이미지
_외부_에 두고, 호스트 머신과 컨테이너 간의 공유 폴더인
**볼륨 (volume)**을 사용하여 런타임 (runtime) 시점에 마운트 (mount) 하세요:

docker run -v $(pwd)/models:/app/models my-model

이렇게 하면 로컬의 models/ 폴더가 컨테이너 내부의 /app/models로 매핑 (mapping) 됩니다. 가중치 (weights)는 디스크에 저장되고, 이미지는 가볍게 유지되며, 아무것도 다시 빌드할 필요 없이 모델을 교체할 수 있습니다.

모델을 API로 서빙하기 (Serving a model as an API)

대부분의 경우 단순히 스크립트를 실행하는 것이 아니라, 앱이 호출할 수 있는 엔드포인트 (endpoint) 뒤에 모델을 두고 싶어 합니다. 여기 최소한의 FastAPI 서버인 app.py가 있습니다:

from fastapi import FastAPI
from pydantic import BaseModel
import torch
...

모델이 predict() 내부가 아니라 서버가 부팅될 때
단 한 번 로드된다는 점에 주목하세요. 매 요청마다 가중치를 로드하면 API 속도가 매우 느려지며, 이는 실제 운영 트래픽이 들어오기 전까지는 놓치기 쉬운 실수입니다.

이제 Dockerfile의 마지막 줄을 스크립트 대신 서버를 실행하도록 수정합니다:

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

--host 0.0.0.0 설정은 매우 중요합니다. 컨테이너 내부에서 기본값인 127.0.0.1은 "이 컨테이너 내부에서만 접근 가능하다"는 의미이므로, 외부에서의 요청은 거부됩니다. 0.0.0.0에 바인딩 (binding) 하면 외부에서 접근할 수 있게 됩니다. 그런 다음 실행할 때 포트 (port)를 매핑하세요:

docker run -p 8000:8000 -v $(pwd)/models:/app/models my-model

-p 8000:8000은 호스트 머신의 8000번 포트를 컨테이너의 8000번 포트와 연결합니다. http://localhost:8000/predict에 접속하면 컨테이너에서 모델이 서빙 (serving) 되고 있는 것을 확인할 수 있습니다.

컨테이너 하나로 부족할 때: docker-compose

실제 AI 앱은 단독으로 실행되는 경우가 드뭅니다. 모델 API 외에도 결과 저장을 위한 Redis 캐시 (cache)나 임베딩 (embeddings)을 위한 벡터 데이터베이스 (vector database) 등이 필요할 수 있습니다. 올바른 플래그 (flag)와 순서를 지키며 세 개의 컨테이너를 일일이 수동으로 시작하는 것은 금방 지치는 일입니다.

docker-compose를 사용하면 전체 스택 (stack)을 하나의 docker-compose.yml 파일에 정의할 수 있습니다:

services:
  model-api:
    build: .
...

그러면 단 한 번의 명령으로 전체 스택을 시작할 수 있습니다:

docker compose up

단 한 번의 명령으로 세 개의 서비스가 서로 연결되어 통신합니다. 서비스들이 이름(name)을 통해 서로에게 도달할 수 있기 때문에, 귀하의 API는 호스트 redis:6379를 통해 Redis에 연결되며, 더 이상 IP 주소를 찾아 헤맬 필요가 없습니다. docker compose down으로 모든 것을 종료할 수 있습니다. 이것이 대부분의 사람들이 Docker와 사랑에 빠지는 순간입니다.

전문가와 초보자를 가르는 몇 가지 습관

첫날부터 실천할 가치가 있는 짧은 목록입니다:

  • .dockerignore 파일을 추가하세요. .gitignore와 마찬가지로, 이미지에 불필요한 파일이 포함되지 않도록 합니다. 최소한 __pycache__, .git, venv, *.pt, 그리고 data/는 포함해야 합니다. 이 파일이 없으면 실수로 기가바이트 단위의 캐시와 데이터셋을 빌드(build) 과정에 복사하게 됩니다.
  • -slim 또는 공식 ML 베이스 이미지(base images)를 사용하세요. 전체 이미지 대신 python:3.11-slim을 사용하면 수백 메가바이트를 절약할 수 있습니다. GPU 작업을 위해서는 pytorch/pytorch와 같이 드라이버 스택(driver stack)이 자동으로 처리되는 공식 CUDA 지원 베이스 이미지에서 시작하세요.
  • 컨테이너당 하나의 프로세스(One process per container). API, 데이터베이스, 워커(worker)를 하나의 컨테이너에 억지로 밀어 넣고 싶은 유혹을 뿌리치세요. 그것들을 분리하세요. 그것이 바로 Compose를 사용하는 목적입니다.
  • 절대로 이미지에 비밀 정보(secrets)를 구워 넣지 마세요. API 키와 토큰은 환경 변수(-e MY_KEY=... 또는 .env 파일)에 넣어야 하며, 절대 Dockerfile에 하드코딩해서는 안 됩니다. 이미지를 가진 사람이라면 누구나 내부에 구워진 정보를 읽을 수 있습니다.

보상

귀하의 모델을 실행할 수 없었던 팀원에게 다시 가보세요. Docker를 사용하면 대화는 다음과 같이 바뀝니다:

git clone your-repo
docker compose up

단 두 개의 명령입니다. 노트북에서든, 클라우드 GPU에서든, 운영 서버(production server)에서든 동일한 Python, 동일한 CUDA, 동일한 패키지, 동일한 모든 환경이 유지됩니다. "버전이 무엇인가요?"라거나 "ffmpeg를 설치했나요?" 같은 질문도, 40분 동안의 디버깅 세션도 필요 없습니다.

이를 위해 Kubernetes를 마스터하거나 DevOps 엔지니어가 될 필요는 없습니다. 그저 Dockerfile, 버전이 고정된 requirements.txt, 그리고 아마도 docker-compose.yml만 있으면 됩니다. 위의 PyTorch 예제로 시작하여, 오늘 당장 컨테이너에서 모델 하나를 실행해 보고 거기서부터 발전시켜 나가세요.

다음에 누군가 당신의 프로젝트가 자신의 컴퓨터에서도 작동하는지 묻는다면, 당신은 이미 답을 알고 있을 것입니다.

그것은 모든 컴퓨터에서 작동합니다.

이 내용이 유용했나요? 당신이 겪었던 가장 까다로운 "내 컴퓨터에서는 되는데" (works on my machine) 버그를 댓글로 남겨주세요.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0