본문 바로가기
AI 오케스트레이션 캠프/일차별 회고

47일차 회고|Docker·CI/CD·멀티모달 Agent 배포 환경 구축|AI 오케스트레이션 개발자 국비지원

by 랩보다 AI 더 잘해지기 2026. 9. 15.
728x90

 

오늘은 지금까지 개발했던 멀티모달 Agent 프로젝트를 Docker 환경으로 분리하고 GitHub Actions CI까지 연결하는 과정을 실습했다. 이전까지는 개별 기능을 개발하고 실행하는 데 집중했다면, 오늘은 한 단계 더 나아가 다른 환경에서도 같은 프로젝트를 실행할 수 있도록 만드는 과정을 경험했다.

특히 Docker Compose, Docker 이미지, Docker Hub, Release Compose, CI가 각각 따로 존재하는 기술이 아니라 개발한 프로그램을 테스트하고 다른 환경으로 전달하기 위한 하나의 흐름으로 연결되어 있다는 점을 이해하는 것이 오늘의 가장 큰 배움이었다.


1. 오늘 배운 내용

오늘 학습의 전체적인 흐름은 다음과 같았다.

서비스 분리
   ↓
각 서비스별 환경 구성
   ↓
테스트 코드 작성
   ↓
PostgreSQL / RAG 데이터 준비
   ↓
Docker 이미지 생성
   ↓
Docker Compose 실행
   ↓
Release Compose 구성
   ↓
GitHub Actions CI 구성
   ↓
향후 Registry → AWS 배포

여기서 CI(Continuous Integration)는 코드를 GitHub에 올렸을 때 테스트나 빌드 같은 검증 작업을 자동으로 실행하는 것을 의미한다.

반면 CD(Continuous Delivery/Deployment)는 검증된 결과물을 실제 서버나 운영 환경에 전달하고 배포하는 단계다.

오늘은 CI까지 연결하는 기반을 만들었고, 실제 AWS 배포는 다음 단계로 남겨두었다.


2. Docker Compose와 Release 환경 이해하기

어제부터 Docker Compose를 이용해 여러 서비스를 하나의 환경에서 실행하는 방법을 배웠는데, 오늘은 개발용 Compose와 배포용 Compose의 차이를 더 명확하게 이해했다.

개발할 때는 Dockerfile을 이용해서 직접 이미지를 만들기 때문에 다음과 같은 형태가 된다.

build:
  ...

하지만 다른 컴퓨터나 서버에서 이미 만들어진 프로그램을 실행할 때는 다시 소스 코드를 가져와 이미지를 만들 필요가 없다.

Docker Registry에 올라간 이미지를 가져와 실행하면 된다.

그래서 Release Compose에서는 다음처럼 이미지 자체를 지정한다.

image: 계정/프로젝트-backend:버전

이렇게 하면 개발 환경에서는

소스 코드
→ Dockerfile
→ Docker Image
→ Container

로 진행하고,

Release 환경에서는

Docker Registry
→ Docker Image 다운로드
→ Container 실행

으로 진행할 수 있다.

이 차이를 직접 구성하면서 Docker가 단순히 "내 컴퓨터에서 프로그램을 편하게 실행하는 도구"만은 아니라는 것을 알게 됐다.


3. Docker 이미지 버전 관리

Docker Hub에 이미지를 올릴 때 이미지 이름 뒤에 태그를 붙여 버전을 관리할 수 있다는 것도 다시 확인했다.

예를 들어,

backend:0.0.1
backend:0.0.2
backend:1.0.0

처럼 같은 프로그램의 여러 버전을 관리할 수 있다.

회사에서 프로그램을 운영한다면 최신 버전만 필요한 것이 아니라 특정 시점의 버전으로 되돌리거나, 기존 버전과 새로운 버전을 구분해야 하는 상황이 생길 수 있다.

그래서 이미지 이름과 버전을 Release Compose에 정확하게 작성하는 것이 중요했다.

특히 앞선 실습에서 Codex가 생성한 이미지 이름과 실제 Docker Hub에 올라간 이미지 이름이 맞지 않는 문제를 경험하면서, AI가 만들어준 설정 파일이라고 해서 그대로 사용하면 안 된다는 점도 다시 확인했다.

AI가 만들어준 YAML이 문법적으로는 맞더라도 실제 프로젝트의 이미지 이름, 서비스 이름, 폴더 구조와 다르면 실행되지 않는다.

결국 AI가 코드를 작성하는 것과 실제 프로젝트에서 사용할 수 있는 코드를 검증하는 것은 별개의 문제라는 생각이 들었다.


4. 멀티모달 Agent 프로젝트 환경 분리

오후에는 기존에 실습했던 Optional Multi-Model Agent 프로젝트를 다시 가져와 실제 Docker 환경에 맞게 정리했다.

프로젝트는 크게 다음과 같이 구성되어 있었다.

  • Streamlit Frontend
  • FastAPI Backend
  • Agent Worker
  • MCP Server
  • PostgreSQL/pgvector
  • Redis
  • Ollama

기존에는 프로젝트 루트에 하나의 requirements.txt와 가상환경을 두고 여러 서비스가 함께 사용하는 구조였다.

이 구조를 다음과 같이 분리했다.

optional_multimodal_agent/
├─ frontend/
│  ├─ .venv/
│  ├─ .env
│  ├─ .env.example
│  ├─ .env.docker
│  └─ requirements.txt
│
├─ backend/
│  ├─ .venv/
│  ├─ .env
│  ├─ .env.example
│  ├─ .env.docker
│  └─ requirements.txt
│
└─ mcp_server/
   ├─ .venv/
   ├─ .env
   ├─ .env.example
   ├─ .env.docker
   └─ requirements.txt

처음에는 이런 식으로 서비스별로 환경을 나누는 것이 번거롭게 느껴졌지만, 실제로 Docker 이미지를 만들면서 왜 필요한지 이해할 수 있었다.

예를 들어 Frontend가 Backend와 MCP Server에서 사용하는 모든 Python 패키지를 가지고 있을 필요는 없다.

각 서비스가 필요한 패키지만 설치하면

  • 불필요한 의존성 감소
  • 패키지 충돌 가능성 감소
  • Docker 이미지 크기 감소
  • 서비스별 테스트 가능
  • CI에서 문제 발생 위치 파악

등의 장점이 생긴다.

결국 서비스를 분리한다는 것은 단순히 폴더를 나누는 것이 아니라 실행 환경과 책임까지 나누는 것이라는 점을 배웠다.


5. .env, .env.example, .env.docker의 차이

오늘은 환경변수 파일도 목적에 따라 분리했다.

.env

실제 로컬 개발에서 사용하는 설정이다.

BACKEND_URL=http://127.0.0.1:8000

API Key처럼 공개되면 안 되는 정보가 들어갈 수 있기 때문에 Git에 올리지 않는다.

.env.example

프로젝트를 처음 실행하는 사람이 어떤 환경변수를 준비해야 하는지 알 수 있도록 만든 예제 파일이다.

OPENAI_API_KEY=
OPENAI_MODEL=gpt-4.1-mini

실제 비밀값은 넣지 않는다.

.env.docker

Docker 컨테이너에서 사용하는 환경설정이다.

예를 들어 컨테이너끼리 통신할 때는

MCP_URL=http://mcp-server:8020/mcp

처럼 Compose에서 정의한 서비스 이름을 사용한다.

이전에는 localhost를 사용하면 되는 경우가 많았지만 Docker에서는 컨테이너 입장에서 localhost는 자기 자신을 의미한다는 점을 다시 확인했다.

이 부분은 Docker를 처음 배울 때 상당히 헷갈렸던 부분인데, 실제 여러 컨테이너를 연결해보면서 조금 더 명확해졌다.


6. PostgreSQL과 RAG 데이터 다시 구성하기

멀티모달 Agent를 제대로 실행하기 위해서는 단순히 애플리케이션 코드만 실행하면 되는 것이 아니었다.

PostgreSQL/pgvector에 필요한 데이터가 먼저 준비되어 있어야 했다.

기존 Docker 환경의 PostgreSQL 볼륨을 재사용했고, DB 계정의 비밀번호가 환경설정과 맞지 않아 TCP 접속이 되지 않는 문제도 확인했다.

컨테이너 내부에서 계정 비밀번호를 수정한 뒤 연결 설정을 맞췄다.

또한 프로젝트의 데이터를 다시 적재했다.

제품 데이터: 10개
프로그램 데이터: 6개
RAG 문서: 13개
RAG 청크: 44개
Embedding: 768차원

그리고 MM-K100 제품 설명서를 대상으로 실제 벡터 검색을 실행해 관련 문서가 반환되는 것도 확인했다.

여기서 다시 느낀 점은 RAG가 단순히 "문서를 넣어놓고 검색하는 기능"이 아니라 데이터를 적절한 형태로 가공하고 임베딩한 뒤 벡터 데이터베이스에 저장하는 과정까지 포함한다는 것이었다.


7. 테스트를 먼저 만들고 Docker로 넘어가기

Docker를 만들기 전에 각 서비스의 테스트도 작성했다.

frontend/tests/
backend/tests/
mcp_server/tests/

실행 결과는 다음과 같았다.

Backend: 2 passed
Frontend: 2 passed
MCP Server: 3 passed

또한 pip check을 통해 Python 의존성에 문제가 없는지도 확인했다.

특히 테스트에서 실제 OpenAI API를 호출하지 않도록 구성했기 때문에 테스트를 실행할 때 API 비용이 발생하지 않도록 했다.

이 과정을 통해 Docker가 문제를 해결해주는 도구는 아니라는 것도 알게 됐다.

코드 자체가 정상적으로 동작하는지 먼저 확인하고,

로컬 실행
→ 테스트
→ Docker
→ CI

순서로 넘어가는 것이 중요했다.

코드가 제대로 동작하지 않는데 Docker부터 만들면 Docker 설정 문제인지 애플리케이션 문제인지 구분하기 어려워진다.


8. Docker Compose로 네 개의 서비스 실행

서비스별 Dockerfile도 작성했다.

frontend/Dockerfile
backend/Dockerfile
mcp_server/Dockerfile

그리고 Compose에서 다음 네 가지 서비스를 실행했다.

frontend
backend
worker
mcp-server

Backend와 Agent Worker는 같은 Backend 이미지를 사용하지만 실행 명령을 다르게 지정하는 방식으로 구성했다.

또한 Backend와 MCP Server가 생성된 음성이나 이미지 등의 파일을 공유할 수 있도록 Docker Volume도 연결했다.

실제로 Compose를 실행한 뒤

backend       healthy
frontend      running
worker        running
mcp-server    running

상태를 확인했고,

  • Backend
  • Redis
  • Frontend
  • MCP Server
  • RAG 검색

까지 정상적으로 연결되는 것을 확인했다.

여기서 중요한 것은 단순히 docker compose up이 성공한 것이 아니라 실제 서비스 기능까지 확인했다는 점이었다.


9. GitHub Actions CI 구성

마지막으로 GitHub Actions를 이용한 CI 환경을 구성했다.

GitHub Actions는 GitHub 저장소에서 특정 이벤트가 발생했을 때 자동으로 작업을 실행할 수 있는 기능이다.

오늘 만든 CI의 흐름은 다음과 같다.

Checkout
↓
Python 3.12 설치
↓
Backend 테스트
↓
Frontend 테스트
↓
MCP Server 테스트
↓
Compose 문법 검사
↓
Release Compose 문법 검사
↓
Docker 이미지 빌드

이렇게 해두면 내가 코드를 수정해서 GitHub에 Push했을 때 사람이 직접 모든 검사를 다시 하지 않아도 GitHub Runner가 자동으로 기본적인 검증을 수행할 수 있다.

CI의 목적은 "자동으로 배포하기"가 아니라 우선 "코드가 깨지지 않았는지 자동으로 확인하기"라는 점도 명확하게 이해하게 됐다.

오늘은 실제 AWS 배포까지 진행한 것은 아니기 때문에 여기서 범위를 구분해둘 필요가 있다.

CI 구성 및 검증 기반 마련 → 완료
Docker Compose 로컬 실행 → 완료
Release Compose 구성 → 완료
Registry 실제 이미지 배포 → 아직
AWS 배포/CD → 다음 단계

10. 오늘 새롭게 이해한 점

오늘 가장 크게 달라진 부분은 Docker와 CI/CD를 각각의 기술로 보지 않게 된 것이다.

처음에는

Docker는 컨테이너를 실행하는 기술
GitHub Actions는 자동화 도구

정도로 생각했다.

하지만 오늘 전체 과정을 연결해보니 실제 개발에서는 다음과 같은 흐름으로 사용할 수 있다는 것을 알게 됐다.

개발
↓
서비스별 환경 분리
↓
로컬 테스트
↓
Docker 이미지 생성
↓
Compose로 통합 실행
↓
Registry에 이미지 저장
↓
CI에서 테스트/빌드 자동화
↓
CD로 서버에 배포

즉, 개발한 프로그램을 "내 컴퓨터에서만 실행되는 코드"에서 "다른 환경에서도 재현할 수 있는 서비스"로 만드는 과정이 Docker와 CI/CD의 중요한 역할이라는 것을 조금 더 구체적으로 이해하게 됐다.


11. 아직 부족한 부분

오늘 많은 과정을 따라갔지만 Docker와 CI/CD의 명령어나 설정을 아직 전부 외운 상태는 아니다.

특히 다음 부분은 다시 정리할 필요가 있다.

  • Docker Compose 파일 구조
  • Dockerfile 작성 방식
  • Docker Volume
  • Docker 이미지와 태그 관리
  • Docker Hub/Registry 사용 방법
  • Release Compose 작성
  • GitHub Actions YAML 구조
  • CI와 CD의 실제 연결 과정
  • AWS에서 Docker 이미지 배포하는 방법

강사님도 반복해서 "문서와 명령어를 정리해두지 않으면 나중에 다시 할 때 기억하기 어렵다"는 점을 강조했다.

나 역시 지금은 Codex에게 명령어나 설정을 물어볼 수 있지만, 적어도 어떤 파일을 왜 만들고 어떤 순서로 실행하는지는 스스로 설명할 수 있는 수준까지 만들어야겠다고 느꼈다.


12. 오늘의 회고

오늘은 단순히 Docker 명령어 몇 개를 배운 날이라기보다, 지금까지 만든 Agent 프로젝트를 실제 개발 환경에 가까운 형태로 정리해본 날이었다.

특히 서비스마다 가상환경과 requirements.txt, 환경변수를 분리하고 테스트를 작성한 뒤 Docker 이미지로 만드는 과정을 직접 진행하면서, 프로젝트 구조를 어떻게 잡느냐가 이후 배포 과정에도 영향을 준다는 것을 알게 됐다.

또 하나 기억에 남은 것은 AI 코딩 도구를 사용하더라도 개발자가 프로젝트의 구조를 이해하고 결과물을 검증해야 한다는 점이다.

Codex는 Dockerfile이나 Compose, CI YAML을 빠르게 만들어줄 수 있지만, 실제 Docker Hub의 이미지 이름이나 프로젝트의 서비스 구조와 맞지 않는 설정을 만들어낼 수도 있다.

결국 AI를 잘 사용하는 것만큼이나 AI가 만든 결과물이 실제 프로젝트에서 맞는지 확인할 수 있는 기본적인 개발 지식이 중요하다고 느꼈다.

이제 다음 단계는 오늘 만든 CI 기반을 바탕으로 실제 Registry에 이미지를 올리고, 이후 AWS에서 이미지를 받아 서비스를 실행하는 과정이다.

지금까지 배운 Docker, GitHub Actions, MCP, RAG, Redis 등의 기술이 각각 따로 떨어져 있는 것이 아니라 하나의 Agent 서비스를 만들고 검증하고 배포하기 위한 과정으로 연결되고 있다는 점을 조금씩 이해하고 있다.

댓글