Development environment

[Docker] Docker 기본 개념 및 명령어 정리

hjjummy 2025. 10. 16. 14:44

이번 글은 Streamlit 기반 RAG 앱Docker Compose로 배포과정을 실습 하면서 사용한 Docker 기본개념과 명령어들을 정리해보았다.

 

배포 환경은 다음과 같다

배포 환경
OS Ubuntu Server
앱 프레임워크 Streamlit
코어 구성 (프론트) Streamlit · (백엔드) RAG 서비스(FastAPI/LangGraph), 임베딩/리랭크 서비스
데이터 Milvus(벡터), PostgreSQL(로그/메타)
배포 Docker + Docker Compose
포트 전략 개발 8502 / 운영 8501
운영 포인트 volumes로 코드 핫리로드, restart: unless-stopped, healthcheck로 헬스 모니터링

 

 

처음엔 단순히 streamlit run app.py로만 구동했지만,
운영 환경에서는 여러 서비스와 함께 돌아가야 하다 보니
Docker Compose 기반의 배포 환경으로 전환했다.

“단순히 앱을 띄우는 게 아니라, 운영 가능한 형태로 띄워야 한다.”

 


🐋 Docker에 대해서

Docker로 배포를 한다는 말을 많이 들어봤을텐데, 배포 얘기로 들어가기 전에
Docker의 기본 구조를 짧게 정리해두면 이후 설정이 훨씬 이해가 빠르다.

 

Docker는 애플리케이션과 그 실행 환경을 ‘이미지’로 패키징하고, 그걸 실행 가능한 단위(컨테이너)로 구동하는 시스템이다.

즉, “어디서 실행하든 같은 환경이 보장되도록 만드는 가상화 기술” 이라고 보면 된다.

여기에 레지스트리(Registry)가 배포 저장소 역할을 하고, 네트워크/볼륨이 컨테이너 간 통신과 데이터 영속성을 담당한다.
여러 컨테이너가 얽히면 Compose로 “시스템 단위”로 관리하는 것이 표준 운영 방식이다.


핵심 구성요소와 용어 

 

🔹 이미지(Image) vs 컨테이너(Container)

용어
이미지 실행 환경(라이브러리, 코드, OS 등)을 포함한 ‘설치 패키지’ jeju_rag_app:latest Dockerfile로 만든 산출물.
빌드를 새로 해야 바뀜
컨테이너 이미지를 실제로 실행한 ‘프로세스’ jeju_rag_app-jeju-rag-app-1 이미지에서 “실행”된 실체. 중지/재시작 빠름

🔹 Dockerfile이란?

Dockerfile은 “어떤 환경에서 앱을 실행할지”를 정의하는 스크립트다. 

예시로, Streamlit 앱의 Dockerfile은 보통 이런 구조다:

FROM python:3.11-slim

WORKDIR /app
COPY . /app

RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 8501

CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]

이 한 파일이 곧 “앱 실행 환경의 스냅샷”이 된다.
Docker는 이걸 기반으로 이미지를 만들고, 그 이미지에서 컨테이너를 띄운다.

 


🔹 docker-compose.yml 란?

하나의 컨테이너로 끝나는 서비스는 드물다.
예를 들어 RAG 앱이라면 다음과 같이 여러 컴포넌트가 동시에 필요하다:

  • jeju-rag-app: Streamlit 앱
  • milvus: 벡터DB
  • postgres: 검색 로그 DB
  • reranker: LLM 기반 rerank API

그래서 도입한 것이 Docker Compose이다.  Compose를 쓰면 서비스 전부를 하나의 프로젝트로 정의하고, 한 번에 올리고 한 번에 내릴 수 있다. 

 

이런 멀티 컨테이너 환경을 한꺼번에 정의하고 관리해주는 게 docker-compose.yml 파일이다.

services:
  jeju-rag-app:
    build: .
    container_name: jeju_rag_app-jeju-rag-app-1
    ports:
      - "8501:8501"
    volumes:
      - .:/app
    environment:
      - LLM_PROVIDER=openai
      - ENV=production
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8501"]
      interval: 30s
      timeout: 5s
      retries: 5

이 한 파일로 build, network, env, volume 등 모든 설정을 통합 관리할 수 있다.
결국 Dockerfile이 “하나의 실행환경”을 정의한다면,
Compose는 “여러 실행환경을 한 프로젝트로 엮는 도구”라고 보면 된다.

 

Docker를 이용해 배포하는 과정을 간단하게 그려보면 이런형식이다.

소스코드 → (Dockerfile 빌드) → 이미지 → (run) → 컨테이너
                                   │
                                   └─ 레지스트리 push/pull (공유/배포)

 

 


🔹 Volume Mount 란?

호스트(내 PC/서버)의 폴더를 컨테이너 내부 경로에 연결하는 기능

즉, 컨테이너 안에서 /app에 접근하면 실제로는 내 컴퓨터의 폴더를 읽는 구조이다.

 

이걸 쓰는 이유는 세 가지다:

  1. 코드 변경 실시간 반영 (핫리로드)
     이미 배포한 앱의 코드를 바꾼경우, 이미지 재빌드 없이도 컨테이너 재시작만 으로 반영된다. ( 단, requirements.txt나 Dockerfile를 바꾼 경우엔 재빌드 필요)
    Str
    eamlit이나 FastAPI처럼 개발 중 자주 수정하는 앱에 필수. 
  2. 데이터 영속화 (persist)
    → 컨테이너가 삭제돼도 DB 파일 유지.
  3. 로그·모델 공유
    → 컨테이너 밖에서도 로그/모델 파일 접근 가능.

docker-compose.yml 에서의 Volume Mount

Compose 없이도 docker run 명령에서 -v 옵션으로 바로 쓸 수 있다.

docker run -d \
  -p 8501:8501 \
  -v $(pwd):/app \
  my_streamlit_app


이건 현재 디렉토리($(pwd))를 컨테이너의 /app으로 마운트하는 예시이다.

docker run -v로도 가능하지만, 여러 옵션이 많아지면 관리가 어렵기 때문에 Compose의 volumes:로 선언하는 게 표준적인 운영 방식이다.


🔹 docker run vs docker compose up 

목적 Docker (단일)  Docker compose (멀티)
새 이미지 빌드 docker build -t myapp . docker compose build
컨테이너 실행 docker run -d -p 8501:8501 myapp docker compose up -d
컨테이너 중지 docker stop <id> docker compose stop [svc]
컨테이너 상태 보기 docker ps docker compose ps
컨테이너 로그 보기  docker logs -f <id> docker compose logs -f [svc]
쉘 진입 docker exec -it <id> sh docker compose exec <svc> sh
스케일 수동 run 반복 docker compose up --scale api=3 -d
정리 docker rm -f <id> docker compose down(네트워크 포함)

 

개발 초반에는 docker run으로 충분하지만, 운영 단계에서는 compose로 묶는 게 필수이다.

streamlit, reranker, milvus를 각각 run으로 띄우면, 의존 순서나 포트 충돌, 네트워크 관리가 엉망이 되기 때문이다.

 

📍명령어 헷갈리는 포인트 

 

① up 과 start의 차이

  • docker compose up: 컨테이너가 없으면 새로 생성하고, 있으면 실행
  • docker compose start: 이미 만들어진 컨테이너만 실행 (이미지 변경 반영 안 됨)

👉 즉, 코드 수정 후 새 버전 반영하려면 up을 써야 한다.

 

② stop과 down의 차이

  • stop: 컨테이너를 중지하지만 삭제하지 않음 (다시 up하면 그대로 이어짐)
  • down: 컨테이너 + 네트워크 + 임시 볼륨까지 전부 제거

보통 운영에서는 stop → up 조합을 쓰고,
완전 초기화가 필요할 때만 down을 사용한다.