이번 글은 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에 접근하면 실제로는 내 컴퓨터의 폴더를 읽는 구조이다.
이걸 쓰는 이유는 세 가지다:
- 코드 변경 실시간 반영 (핫리로드)
→ 이미 배포한 앱의 코드를 바꾼경우, 이미지 재빌드 없이도 컨테이너 재시작만 으로 반영된다. ( 단, requirements.txt나 Dockerfile를 바꾼 경우엔 재빌드 필요)
Streamlit이나 FastAPI처럼 개발 중 자주 수정하는 앱에 필수. - 데이터 영속화 (persist)
→ 컨테이너가 삭제돼도 DB 파일 유지. - 로그·모델 공유
→ 컨테이너 밖에서도 로그/모델 파일 접근 가능.

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을 사용한다.
'Development environment' 카테고리의 다른 글
| [Git] 이미 Git에 올라간 파일 .gitignore 추가하기 (0) | 2025.11.24 |
|---|---|
| [Docker] streamlit+ milvus로 구성한 RAG 서비스 docker로 배포하기 (0) | 2025.10.16 |
| [Git] Git 정리: 개념, 필수 명령어, 협업 흐름, 트러블슈팅 총정리 (1) | 2025.09.19 |
| [Poetry] poetry 개발 환경 셋팅하기 : 설치부터 가상환경 구축까지 (1) | 2025.08.22 |
| [FastAPI] FastAPI 제대로 이해하기 : 코드와 개념으로 (0) | 2025.08.20 |