본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 19. 13:16

"내 컴퓨터에서는 잘 되는데요"라고 말하는 것을 멈추세요: AI 엔지니어를 위한 Docker

요약

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

핵심 포인트

  • AI 프로젝트는 Python, 시스템 라이브러리, CUDA 등 다층적 의존성을 가짐
  • requirements.txt만으로는 해결할 수 없는 환경 문제를 Docker로 해결 가능
  • Docker 이미지는 OS, 패키지, 설정을 포함한 완전한 스냅샷 역할을 수행
  • 컨테이너는 VM보다 가볍고 빠르며 호스트 커널을 공유하여 효율적임

모델을 학습시켰습니다. 노트북(Notebook)은 잘 돌아갑니다. 데모도 작동합니다. 팀원에게 전달했는데, 40분 뒤 모든 엔지니어가 두려워하는 메시지를 받게 됩니다:

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

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

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

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

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

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

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

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

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

Docker란 실제로 무엇인가?

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

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

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

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

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

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

# 공식 Python 이미지에서 시작합니다. "-slim" 변형(variant)은 크기가 더 작습니다.
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을 찾으라고 지시합니다. 이것으로 끝입니다. 이제 당신은 이식 가능하고 재현 가능한 모델을 갖게 되었습니다.

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

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

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

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

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

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

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

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

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

이제 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

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

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

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

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

보상

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

git clone your-repo
docker compose up

단 두 개의 명령입니다. 그들의 노트북에서도, 클라우드 GPU에서도, 운영 서버에서도 동일한 Python, 동일한 CUDA, 동일한 패키지, 동일한 모든 것이 작동합니다. "버전이 무엇인가요?"라는 질문도, "ffmpeg를 설치했나요?"라는 질문도, 40분 동안 이어지는 디버깅 세션도 없습니다.

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

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

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

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

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0