Skip to main content

컨테이너 기초

“내 컴퓨터에서는 되는데요?”라는 말은 컨테이너가 등장한 이유를 한 문장으로 설명합니다. 컨테이너는 애플리케이션과 그 실행에 필요한 모든 의존성을 하나의 패키지로 묶어, 어떤 환경에서든 동일하게 실행되도록 보장합니다. ML 모델을 학습할 때, 서빙할 때, 파이프라인을 구성할 때 — 컨테이너는 재현성의 기본 단위입니다.

학습 목표

  • 컨테이너와 VM의 근본적 아키텍처 차이를 이해하고 적절한 상황에서 선택할 수 있다
  • Dockerfile을 작성하여 재현 가능한 이미지를 빌드하고 최적화할 수 있다
  • docker-compose로 다중 서비스 환경을 정의하고 운영할 수 있다
  • GPU 컨테이너 환경을 구성하여 ML 워크로드를 실행할 수 있다

왜 중요한가

AI/ML 엔지니어링에서 가장 빈번하게 발생하는 문제는 환경 불일치입니다. Python 버전, CUDA 버전, 라이브러리 의존성이 조금만 달라져도 모델 학습 결과가 달라지거나 아예 실행이 불가능합니다. 컨테이너는 이 문제를 근본적으로 해결합니다. docker run 한 줄이면 누구나 동일한 환경에서 동일한 결과를 얻을 수 있습니다. LLMOps 파이프라인에서 컨테이너는 모든 단계의 기반입니다. 데이터 전처리, 모델 학습, 하이퍼파라미터 튜닝, 모델 서빙, A/B 테스트 — 각 단계가 독립적인 컨테이너로 실행되며, Kubernetes가 이를 오케스트레이션합니다.

컨테이너 vs VM 아키텍처 비교

┌─────────────────────────────────┐  ┌─────────────────────────────────┐
│       Virtual Machine           │  │         Container               │
├─────────────────────────────────┤  ├─────────────────────────────────┤
│  App A  │  App B  │  App C      │  │  App A  │  App B  │  App C      │
│  Bins   │  Bins   │  Bins       │  │  Bins   │  Bins   │  Bins       │
│  Guest  │  Guest  │  Guest      │  ├─────────────────────────────────┤
│   OS    │   OS    │   OS        │  │      Container Runtime          │
├─────────────────────────────────┤  │        (Docker Engine)          │
│       Hypervisor (Type 1/2)     │  ├─────────────────────────────────┤
├─────────────────────────────────┤  │         Host OS Kernel          │
│         Host OS / Hardware      │  ├─────────────────────────────────┤
└─────────────────────────────────┘  │           Hardware              │
                                     └─────────────────────────────────┘
비교 항목VMContainer
격리 수준하드웨어 수준 (강력)프로세스 수준 (namespace/cgroup)
시작 시간수십 초 ~ 수 분수 밀리초 ~ 수 초
이미지 크기GB 단위 (Guest OS 포함)MB 단위 (레이어 공유)
리소스 오버헤드높음 (Guest OS 메모리)낮음 (커널 공유)
밀도호스트당 수십 개호스트당 수백 개
보안 격리강력 (하이퍼바이저)상대적 약함 (커널 공유)
사용 사례멀티 OS, 강한 격리 필요마이크로서비스, CI/CD, ML
컨테이너는 VM을 대체하는 것이 아닙니다. VM 위에서 컨테이너를 실행하는 구조가 클라우드 환경의 표준입니다. AWS EC2(VM) 위에서 Docker 컨테이너를 실행하는 것이 대표적인 예입니다.

Docker 아키텍처

Docker는 클라이언트-서버 아키텍처로 동작합니다.
┌──────────┐     REST API     ┌──────────────────────┐
│ Docker   │ ───────────────→ │   Docker Daemon       │
│  CLI     │                  │   (dockerd)           │
└──────────┘                  │                       │
                              │  ┌─── containerd ───┐ │
                              │  │  ┌── runc ──┐    │ │
                              │  │  │Container │    │ │
                              │  │  └──────────┘    │ │
                              │  └──────────────────┘ │
                              └──────────────────────┘
  • Docker CLI: 사용자가 명령어를 입력하는 인터페이스
  • Docker Daemon (dockerd): 이미지 빌드, 컨테이너 관리, 네트워크/볼륨 처리
  • containerd: 컨테이너 라이프사이클 관리 (OCI 표준)
  • runc: 실제 컨테이너 프로세스 생성 (Linux namespace/cgroup)

이미지 레이어 구조

Docker 이미지는 읽기 전용 레이어의 스택입니다. Union File System(OverlayFS)이 이를 하나의 파일시스템으로 합쳐 보여줍니다.
┌─────────────────────────────┐  ← 쓰기 가능 (컨테이너 레이어)
├─────────────────────────────┤
│ Layer 4: COPY app.py /app   │  ← 읽기 전용
│ Layer 3: RUN pip install    │  ← 읽기 전용 (캐시 대상)
│ Layer 2: RUN apt-get update │  ← 읽기 전용
│ Layer 1: FROM python:3.12   │  ← 베이스 이미지
└─────────────────────────────┘
Dockerfile에서 변경이 적은 레이어를 위쪽에, 자주 변경되는 레이어를 아래쪽에 배치하면 빌드 캐시를 최대한 활용할 수 있습니다. requirements.txt COPY와 pip install을 소스코드 COPY보다 먼저 실행하는 것이 대표적인 패턴입니다.

Dockerfile 지시어

지시어설명예시
FROM베이스 이미지 지정FROM python:3.12-slim
RUN빌드 시 명령어 실행RUN pip install torch
COPY호스트 파일을 이미지에 복사COPY . /app
ADDCOPY + URL 다운로드 + tar 자동 해제ADD data.tar.gz /data
WORKDIR작업 디렉터리 설정WORKDIR /app
EXPOSE문서용 포트 선언 (바인딩 아님)EXPOSE 8000
ENV환경 변수 설정 (이미지에 영구 기록)ENV PYTHONUNBUFFERED=1
ARG빌드 시 변수 (런타임 미노출)ARG CUDA_VERSION=12.4
CMD컨테이너 기본 실행 명령 (오버라이드 가능)CMD ["python", "app.py"]
ENTRYPOINT컨테이너 진입점 (CMD가 인자로 전달)ENTRYPOINT ["uvicorn"]
USER실행 사용자 변경USER appuser
HEALTHCHECK컨테이너 상태 점검HEALTHCHECK CMD curl -f http://localhost:8000/health
LABEL이미지 메타데이터LABEL version="1.0"
VOLUME마운트 포인트 선언VOLUME /data
SHELL기본 셸 변경SHELL ["/bin/bash", "-c"]

Dockerfile 예시: ML 서빙 환경

FROM python:3.12-slim AS base

# 시스템 의존성
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 비root 사용자
RUN groupadd -r appuser && useradd -r -g appuser appuser

WORKDIR /app

# 의존성 먼저 (캐시 활용)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 소스코드
COPY . .

USER appuser
EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

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

멀티스테이지 빌드

멀티스테이지 빌드는 빌드에 필요한 도구와 실제 실행에 필요한 파일을 분리하여 최종 이미지 크기를 극적으로 줄입니다.
# ---- 빌드 스테이지 ----
FROM python:3.12 AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --prefix=/install --no-cache-dir -r requirements.txt

# ---- 런타임 스테이지 ----
FROM python:3.12-slim AS runtime
COPY --from=builder /install /usr/local
WORKDIR /app
COPY . .
CMD ["python", "serve.py"]

GPU 이미지 멀티스테이지 빌드

# ---- 빌드 스테이지 ----
FROM nvidia/cuda:12.4.1-devel-ubuntu22.04 AS builder
RUN apt-get update && apt-get install -y python3-pip
COPY requirements.txt .
RUN pip install --prefix=/install --no-cache-dir -r requirements.txt

# ---- 런타임 스테이지 (runtime은 devel보다 ~2GB 작음) ----
FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04 AS runtime
RUN apt-get update && apt-get install -y python3 && rm -rf /var/lib/apt/lists/*
COPY --from=builder /install /usr/local
WORKDIR /app
COPY . .
CMD ["python3", "train.py"]
nvidia/cuda 이미지는 크기가 큽니다. devel 태그(~4GB)는 빌드 시에만 사용하고, 런타임에는 runtime 태그(~2GB)를 사용하세요. 컴파일이 필요 없는 경우 base 태그가 가장 가볍습니다.

이미지 최적화

.dockerignore

.git
__pycache__
*.pyc
.env
data/
models/
*.tar.gz
.vscode
node_modules

최적화 전략

전략효과방법
slim/alpine 베이스이미지 크기 50-80% 감소python:3.12-slim 사용
레이어 캐시 순서빌드 시간 단축변경 빈도 낮은 레이어를 위에 배치
--no-cache-dir불필요한 pip 캐시 제거pip install --no-cache-dir
apt 캐시 제거시스템 패키지 캐시 제거rm -rf /var/lib/apt/lists/*
멀티스테이지빌드 도구 제외빌드/런타임 스테이지 분리
.dockerignore불필요한 파일 제외빌드 컨텍스트 최소화

볼륨과 데이터 관리

유형명령어 예시특징사용 사례
Bind Mount-v /host/path:/container/path호스트 디렉터리 직접 마운트개발 중 소스코드, 데이터셋
Named Volume-v mydata:/dataDocker가 관리하는 볼륨데이터베이스, 모델 체크포인트
tmpfs--tmpfs /tmp메모리 기반 임시 저장소민감 정보, 임시 캐시
# 학습 데이터 바인드 마운트 + 모델 출력용 Named Volume
docker run --gpus all \
    -v /data/datasets:/data/input:ro \
    -v model-output:/data/output \
    training-image python train.py
:ro 플래그는 읽기 전용 마운트를 의미합니다. 학습 데이터는 컨테이너 내부에서 변경될 필요가 없으므로 :ro를 사용하면 실수로 인한 데이터 손상을 방지할 수 있습니다.

docker-compose: 다중 서비스 관리

# docker-compose.yml — ML 서비스 스택 예시
services:
  mlflow:
    image: ghcr.io/mlflow/mlflow:v2.21.3
    ports:
      - "5000:5000"
    volumes:
      - mlflow-data:/mlflow
    environment:
      - MLFLOW_BACKEND_STORE_URI=sqlite:///mlflow/mlflow.db
      - MLFLOW_DEFAULT_ARTIFACT_ROOT=/mlflow/artifacts
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 5s
      retries: 3

  model-server:
    build: ./serving
    ports:
      - "8000:8000"
    depends_on:
      mlflow:
        condition: service_healthy
    environment:
      - MLFLOW_TRACKING_URI=http://mlflow:5000
      - MODEL_NAME=production-model
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

  jupyter:
    image: quay.io/jupyter/pytorch-notebook:latest
    ports:
      - "8888:8888"
    depends_on:
      mlflow:
        condition: service_healthy
    environment:
      - MLFLOW_TRACKING_URI=http://mlflow:5000
    volumes:
      - ./notebooks:/home/jovyan/work

volumes:
  mlflow-data:

networks:
  default:
    name: ml-stack

GPU Docker: NVIDIA Container Toolkit

GPU를 컨테이너에서 사용하려면 NVIDIA Container Toolkit이 필요합니다.
1

NVIDIA Container Toolkit 설치

# Ubuntu/Debian
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
2

Docker 데몬 설정

sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
3

GPU 컨테이너 실행 확인

# 전체 GPU 할당
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi

# 특정 GPU 지정
docker run --rm --gpus '"device=0"' nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi

# GPU 개수 제한
docker run --rm --gpus 2 nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi
4

docker-compose에서 GPU 사용

deploy:
  resources:
    reservations:
      devices:
        - driver: nvidia
          count: 1
          capabilities: [gpu]

실무 명령어 레퍼런스

명령어설명예시
docker build이미지 빌드docker build -t myapp:v1 .
docker run컨테이너 실행docker run -d -p 8000:8000 myapp:v1
docker exec실행 중 컨테이너에 명령docker exec -it <id> bash
docker logs로그 확인docker logs -f --tail 100 <id>
docker ps실행 중 컨테이너 목록docker ps -a
docker stop컨테이너 중지docker stop <id>
docker rm컨테이너 삭제docker rm -f <id>
docker images이미지 목록docker images --format "table {{.Repository}}\t{{.Size}}"
docker rmi이미지 삭제docker rmi myapp:v1
docker pull이미지 다운로드docker pull python:3.12-slim
docker push이미지 업로드docker push registry/myapp:v1
docker tag이미지 태그docker tag myapp:v1 registry/myapp:v1
docker inspect상세 정보 확인docker inspect <id>
docker system prune미사용 리소스 정리docker system prune -af --volumes
docker stats리소스 사용량 모니터링docker stats --no-stream
docker cp파일 복사docker cp <id>:/app/model.pt ./
docker network ls네트워크 목록docker network ls
docker volume ls볼륨 목록docker volume ls
docker compose up스택 시작docker compose up -d
docker compose down스택 중지 및 정리docker compose down -v

컨테이너 보안

영역실천 사항방법
비root 실행컨테이너 내 root 권한 제거USER appuser 지시어
이미지 스캐닝알려진 취약점 탐지trivy image myapp:v1
시크릿 관리환경 변수로 비밀키 주입Docker Secrets, 외부 vault
최소 권한불필요한 capabilities 제거--cap-drop ALL --cap-add NET_BIND_SERVICE
읽기 전용파일시스템 변경 방지--read-only --tmpfs /tmp
네트워크 격리서비스 간 통신 제한별도 Docker 네트워크 구성

AI/ML에서 컨테이너가 중요한 이유

ML 워크플로 단계컨테이너 활용
데이터 전처리재현 가능한 ETL 파이프라인
모델 학습GPU 컨테이너로 환경 고정
실험 추적MLflow 컨테이너 + 볼륨 영속성
모델 서빙FastAPI/vLLM 컨테이너 배포
A/B 테스트버전별 컨테이너 동시 운영
CI/CD이미지 빌드 → 테스트 → 배포 자동화
# vLLM으로 LLM 모델 서빙하는 컨테이너 실행 예시
docker run --gpus all \
    -v /data/models/llama3:/model \
    -p 8000:8000 \
    vllm/vllm-openai:latest \
    --model /model \
    --max-model-len 8192 \
    --tensor-parallel-size 1
Docker Desktop은 macOS/Windows용 GUI 도구로 내부에 Linux VM을 포함합니다. Docker Engine은 Linux 네이티브 데몬으로, 서버 환경에서 직접 실행됩니다. ML 워크로드는 대부분 Linux 서버에서 Docker Engine을 사용합니다. 개발 환경에서는 Docker Desktop이 편리하지만, GPU 패스스루는 Linux Docker Engine에서만 네이티브로 지원됩니다.
latest 태그는 ‘가장 최근’을 의미하지 않습니다. 단지 태그를 지정하지 않았을 때의 기본값일 뿐입니다. 프로덕션에서는 반드시 구체적인 버전 태그(python:3.12.7-slim, nvidia/cuda:12.4.1-runtime-ubuntu22.04)를 사용하세요. Semantic Versioning이나 Git SHA 기반 태그가 재현성을 보장합니다.
  1. NVIDIA 드라이버가 호스트에 설치되어 있는지 확인: nvidia-smi
  2. NVIDIA Container Toolkit이 설치되어 있는지 확인: nvidia-ctk --version
  3. Docker 데몬을 재시작했는지 확인: sudo systemctl restart docker
  4. --gpus all 또는 --gpus '"device=0"' 플래그를 정확히 사용했는지 확인
  5. docker-compose의 경우 deploy.resources.reservations.devices 설정 확인
Apple Silicon(M1/M2/M3)에서 빌드한 이미지는 x86 서버에서 실행되지 않을 수 있습니다. docker buildx를 사용하면 멀티플랫폼 이미지를 생성할 수 있습니다: docker buildx build --platform linux/amd64,linux/arm64 -t myapp:v1 --push . ML 서빙 환경이 x86 Linux라면, 개발 환경이 ARM이라도 반드시 --platform linux/amd64를 지정하세요.
docker-compose(하이픈)는 v1으로 Python 기반이며 별도 설치가 필요했습니다. docker compose(공백)는 v2로 Go 기반이며 Docker CLI 플러그인으로 내장됩니다. v1은 2023년에 EOL되었으므로, 항상 docker compose(v2)를 사용하세요. version 필드도 v2에서는 선택사항입니다.
컨테이너 로그가 디스크를 가득 채우는 것은 흔한 운영 문제입니다. Docker 데몬 설정(/etc/docker/daemon.json)에 로그 크기 제한을 추가하세요:
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
이렇게 하면 컨테이너당 최대 30MB(10MB x 3 파일)로 로그가 제한됩니다.

체크리스트

  • Docker가 설치되어 있고 docker run hello-world가 정상 실행되는가
  • 컨테이너와 VM의 아키텍처 차이를 설명할 수 있는가
  • Dockerfile을 작성하여 이미지를 빌드할 수 있는가
  • 멀티스테이지 빌드로 이미지 크기를 최적화할 수 있는가
  • bind mount와 named volume의 차이를 이해하고 있는가
  • docker-compose로 다중 서비스를 정의하고 실행할 수 있는가
  • --gpus all 플래그로 GPU 컨테이너를 실행할 수 있는가
  • .dockerignore로 빌드 컨텍스트를 최적화하고 있는가
  • 컨테이너를 비root 사용자로 실행하는 방법을 알고 있는가
  • docker logs, docker exec, docker stats 명령어를 활용할 수 있는가

다음 문서