Docker 기초

컨테이너, 이미지, Dockerfile로 앱을 패키징한다

Docker는 애플리케이션을 컨테이너로 패키징하여 어디서든 동일하게 실행하는 플랫폼이다. 컨테이너와 가상머신의 차이, 이미지 빌드, Dockerfile 작성법, 멀티스테이지 빌드, Docker Compose를 다루어 AI Agent 서비스를 배포하기 위한 Docker 지식을 정리한다.

Engineering
저자

Kwangmin Kim

공개

2026년 05월 05일

1 왜 Docker인가

“내 컴퓨터에서는 되는데 서버에서는 안 된다”는 문제가 있다. Python 버전, 라이브러리 버전, 시스템 패키지, 환경 변수 등 환경 차이로 발생한다.

Docker는 애플리케이션을 실행 환경째 패키징한다. Python, 라이브러리, 설정 파일까지 모두 하나의 이미지로 묶으면 어디서든 동일하게 실행된다.

문제 Docker 이전 Docker 이후
Python 버전 충돌 서버마다 수동 설치 이미지에 Python 3.12 고정
라이브러리 버전 pip install 결과가 서버마다 다름 requirements.txt로 정확히 재현
시스템 패키지 운영팀에 요청 Dockerfile에 명시
환경 변수 서버마다 수동 설정 .env 파일 또는 compose로 관리

2 핵심 개념

2.1 이미지 (Image)

이미지는 컨테이너를 만들기 위한 읽기 전용 템플릿이다. 프로그램 코드, 런타임, 라이브러리, 설정 파일이 모두 포함된다.

클래스(이미지)와 인스턴스(컨테이너)의 관계와 유사하다:

# 비유: 이미지 = 클래스, 컨테이너 = 인스턴스
class AgentImage:  # 이미지
    python = "3.12"
    dependencies = ["fastapi", "langchain", "pydantic"]
    code = "./src"

container1 = AgentImage()  # 컨테이너 1 (포트 8000)
container2 = AgentImage()  # 컨테이너 2 (포트 8001)

2.2 컨테이너 (Container)

컨테이너는 이미지를 실행한 격리된 프로세스이다. 자체 파일 시스템, 네트워크, 프로세스 공간을 가진다.

# 이미지로부터 컨테이너 생성 및 실행
docker run -d -p 8000:8000 --name my-agent agent-image

# 실행 중인 컨테이너 목록
docker ps

# 컨테이너 로그 확인
docker logs my-agent

# 컨테이너 중지 및 삭제
docker stop my-agent
docker rm my-agent

2.3 컨테이너 vs 가상머신

기준 컨테이너 가상머신 (VM)
격리 수준 프로세스 수준 하드웨어 수준
OS 호스트 커널 공유 독자 OS (게스트 OS)
시작 시간 초 단위 분 단위
크기 수십~수백 MB 수 GB
리소스 오버헤드 낮음 높음
사용 사례 앱 패키징, 마이크로서비스 완전한 OS 격리

컨테이너는 호스트 OS의 커널을 공유하므로 가볍고 빠르다. AI Agent처럼 동일한 서비스를 여러 인스턴스로 실행할 때 적합하다.

3 Dockerfile

Dockerfile은 이미지를 빌드하기 위한 명령어 스크립트이다.

3.1 기본 구조

# 베이스 이미지 — 시작점
FROM python:3.12-slim

# 작업 디렉토리 설정
WORKDIR /app

# 의존성 파일 복사 및 설치 (캐시 활용)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 소스 코드 복사
COPY src/ ./src/

# 환경 변수
ENV ENVIRONMENT=production

# 포트 노출 (문서화 목적)
EXPOSE 8000

# 실행 명령
CMD ["uvicorn", "src.services.api.main:app", "--host", "0.0.0.0", "--port", "8000"]

3.2 주요 명령어

명령어 역할 예시
FROM 베이스 이미지 지정 FROM python:3.12-slim
WORKDIR 작업 디렉토리 설정 WORKDIR /app
COPY 파일/디렉토리 복사 COPY src/ ./src/
RUN 빌드 시 명령 실행 RUN pip install -r requirements.txt
ENV 환경 변수 설정 ENV LOG_LEVEL=INFO
EXPOSE 포트 문서화 EXPOSE 8000
CMD 컨테이너 시작 명령 CMD ["uvicorn", "..."]
ENTRYPOINT 고정 시작 명령 ENTRYPOINT ["python"]

3.3 빌드와 실행

# 이미지 빌드 (-t: 태그 지정)
docker build -t agent-api:latest .

# 컨테이너 실행
docker run -d \
  -p 8000:8000 \
  --name agent-api \
  -e AZURE_OPENAI_KEY=xxx \
  agent-api:latest

# 빌드 캐시 없이 재빌드
docker build --no-cache -t agent-api:latest .

3.4 레이어 캐시 최적화

Dockerfile의 각 명령어는 레이어를 생성한다. 변경되지 않은 레이어는 캐시에서 재사용되므로, 자주 변경되는 파일일수록 아래에 배치한다.

# 잘못된 순서 — 코드가 바뀔 때마다 pip install도 다시 실행
COPY . .
RUN pip install -r requirements.txt

# 올바른 순서 — requirements.txt가 변경될 때만 pip install 재실행
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY src/ ./src/  # 코드 변경은 여기만 영향

의존성 설치(pip install)는 시간이 오래 걸리므로, 소스 코드 변경 시에는 캐시를 활용하여 건너뛸 수 있게 배치하는 것이 중요하다.

4 멀티스테이지 빌드

빌드에 필요한 도구(Node.js, 컴파일러 등)를 최종 이미지에 포함하지 않아 이미지 크기를 줄인다.

4.1 React + FastAPI 멀티스테이지

AI Agent 플랫폼처럼 프론트엔드(React)와 백엔드(FastAPI)를 하나의 이미지로 빌드하는 예이다.

# Stage 1: React 빌드
FROM node:20-slim AS frontend
WORKDIR /app/frontend
COPY frontend/package*.json .
RUN npm ci
COPY frontend/ .
RUN npm run build
# 결과: /app/frontend/dist/ 에 정적 파일 생성

# Stage 2: Python 런타임
FROM python:3.12-slim AS runtime
WORKDIR /app

# 시스템 패키지
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Python 의존성
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 소스 코드
COPY src/ ./src/
COPY data/configs/ ./data/configs/

# React 빌드 결과를 정적 파일 디렉토리로 복사
COPY --from=frontend /app/frontend/dist ./static

# 실행
ENV ENVIRONMENT=production
EXPOSE 8000
CMD ["uvicorn", "src.services.api.main:app", "--host", "0.0.0.0", "--port", "8000"]

COPY --from=frontend으로 Stage 1의 빌드 결과만 가져온다. 최종 이미지에는 Node.js가 포함되지 않아 크기가 크게 줄어든다.

방식 이미지 크기
단일 스테이지 (Node + Python) ~1.5 GB
멀티스테이지 ~500 MB

5 Docker Compose

여러 컨테이너를 함께 실행할 때 사용한다.

# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT}
      - AZURE_OPENAI_KEY=${AZURE_OPENAI_KEY}
      - LLM_PROVIDER=azure
      - LOG_LEVEL=INFO
    volumes:
      - ./data/docs:/app/data/docs      # 지식베이스 문서 마운트
      - ./data/experiments:/app/data/experiments
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    restart: unless-stopped
# 모든 서비스 시작
docker compose up -d

# 로그 확인
docker compose logs -f api

# 서비스 중지 및 삭제
docker compose down

# 이미지 재빌드 후 시작
docker compose up -d --build

5.1 환경 변수 관리

# docker-compose.yml
services:
  api:
    env_file:
      - .env              # 로컬 환경 변수 파일
    environment:
      - ENVIRONMENT=production   # 직접 지정 (env_file보다 우선)
# .env
AZURE_OPENAI_ENDPOINT=https://my-resource.openai.azure.com/
AZURE_OPENAI_KEY=sk-xxx
LLM_PROVIDER=azure

.env 파일은 .gitignore에 추가하여 git에 올리지 않는다. 민감한 정보(API 키 등)가 포함되기 때문이다.

6 .dockerignore

빌드 컨텍스트에서 불필요한 파일을 제외한다. .gitignore와 같은 역할이다.

# .dockerignore
.git
.venv
__pycache__
node_modules
*.pyc
.env
.env.local
data/docs/*.pdf
*.log

.dockerignore가 없으면 docker build.venv, node_modules 등 수 GB의 파일이 빌드 컨텍스트에 포함되어 빌드 시간이 크게 늘어난다.

7 자주 사용하는 명령어

# 이미지 관리
docker images                          # 이미지 목록
docker rmi agent-api:latest            # 이미지 삭제
docker image prune                     # 미사용 이미지 정리

# 컨테이너 관리
docker ps                              # 실행 중 컨테이너
docker ps -a                           # 모든 컨테이너 (중지 포함)
docker logs -f my-agent                # 실시간 로그
docker exec -it my-agent /bin/bash     # 컨테이너 내부 접속

# 디버깅
docker inspect my-agent                # 상세 정보 (네트워크, 볼륨 등)
docker stats                           # CPU, 메모리 사용량 실시간

8 관련 주제

선행 지식

후속 주제

다른 카테고리 연결

Subscribe

Enjoy this blog? Get notified of new posts by email: