이번에 제가 준비한 건,
바로 Leaf의 개발 서버 인프라 구축입니다.
코드는 없지만, 코드를 담을 ‘자리’를 먼저 만들어두는 작업이었죠.
개발 환경에는 보통 이렇게 4가지 서버 단계가 있습니다:
- 로컬 (개발자의 개인 환경)
- 개발 서버 (공동 테스트용, 작업 공유)
- 스테이징 서버 (배포 전 최종 검증)
- 라이브 서버 (실사용자 대상 서비스)
Leaf는 이제 막 프로젝트를 시작한 사이드 팀이고,
각자의 로컬에서 작업을 시작하더라도 합쳐보고 공유할 공간이 필요했습니다.
우리는 그걸 “개발 서버”로 정의하고, 지금 단계에서 가장 먼저 구축한 것입니다.
☁️ EC2 개발 서버 구성
- AWS 관리 콘솔에서 Ubuntu 22.04 LTS AMI 선택 → t2.micro (프리 티어)
- 키 페어는 기존에 생성해둔 leaf_keypair 사용 → PEM 파일을 안전한 곳(~/.ssh/leaf_keypair.pem)에 저장
- 보안 그룹 설정
- SSH(22) 허용: 내 IP → XX.XX.XX.XX/32
- HTTP(80), HTTPS(443) 허용: 0.0.0.0/0 (추후 고정 IP 설정 예정)
- 애플리케이션 포트(8080) 허용: 0.0.0.0/0
- SSH 접속
chmod 400 ~/.ssh/leaf_keypair.pem
ssh -i ~/.ssh/leaf_keypair.pem ubuntu@<Elastic_IP>
- 시행착오:
- 초기엔 퍼블릭 IP가 매번 바뀌어 접속이 번거로웠음 → Elastic IP 할당으로 해결
- SSH config 설정(~/.ssh/config) 시 디렉터리·퍼미션 오류 발생 → chmod 600 ~/.ssh/config로 수정
2. Ubuntu 환경 초기 세팅
- 시스템 업데이트
sudo apt update && sudo apt upgrade -y
- 기본 도구 설치: git, curl, unzip 등
시행착오:
- 24.04 AMI를 실수로 선택 → 원래 목표였던 22.04와 버전 차이 발생
3. Docker & Docker Compose 설치
- 공식 스크립트 대신 APT 저장소 활용
sudo apt install -y ca-certificates curl gnupg
sudo install -m0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker
- 설치 확인
docker --version # Docker version xx.x.x
docker compose version # Docker Compose version v2.x.x
시행착오:
- WSL 터미널에서 도커 권한 오류 → sudo usermod -aG docker $USER 후 재로그인
4. 프로젝트 디렉터리 구조 및 Docker Compose 작성
- EC2 홈 디렉터리에 프로젝트 루트 생성
mkdir ~/leaf && cd ~/leaf
- docker-compose.yml
services:
db:
image: mariadb:10.6
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${pw}
MYSQL_DATABASE: ${dbname}
MYSQL_USER: ${id}
MYSQL_PASSWORD: ${pw}
ports:
- "3306:3306"
backend:
build: ./backend
restart: always
depends_on: [db]
ports: ["8080:8080"]
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/leafdb
SPRING_DATASOURCE_USERNAME: ${id}
SPRING_DATASOURCE_PASSWORD: ${pw}
frontend:
build: ./frontend
restart: always
ports: ["3000:3000"]
nlp:
build: ./nlp
restart: always
ports: ["5000:5000"]
5. 각 서비스별 Dockerfile 예시
- Backend (Spring Boot + Maven)
- Maven 사용 시: mvn package로 target/*.jar 생성
- Gradle 사용 시: ./gradlew bootJar로 build/libs/*.jar 생성 → COPY build/libs/*.jar app.jar로 경로만 변경 안내
- dockerfile
FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY target/*.jar app.jar # mvn package 후 생성되는 JAR
ENTRYPOINT ["java","-jar","/app/app.jar"]
- Frontend (React)
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm ci && npm run build
CMD ["npm","start"]
EXPOSE 3000
- NLP (Python FastAPI 등)
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python","nlp_service.py"]
EXPOSE 5000
시행착오:
- COPY requirements.txt 등 파일 경로 오류 → 각 서비스 폴더에 반드시 파일 배치
6. 컨테이너 빌드 & 실행
- 프로젝트 루트(~/leaf)에서 한 번에 빌드·실행:
docker compose up -d --build
docker compose ps
- 로그 확인
docker compose logs nlp
- 시행착오:
- 의존성 파일(package.json, requirements.txt) 누락 시 빌드 오류 → 각 폴더에 반드시 준비
- 권한 문제: non-root에서 docker compose 실행 시 permission denied → 사용자 재로그인 후 그룹 설정 확인
7. 마무리
- DB 접속 확인
docker compose exec db mysql \ -u ${id} -p'${pw}' leafdb
- Elastic IP 활용
- 인스턴스 중지 후 재시작에도 퍼블릭 IP 유지 → SSH와 애플리케이션 접근 안정성 확보
- 인스턴스 중지/시작
- 사용하지 않을 땐 비용 절감을 위해 중지(Stopped)
- EBS 스토리지(30 GiB)는 중지 상태에서도 요금 발생 → 스토리지 필요 용량 검토
8. CI/CD 파이프라인 완성 🎯
8-1. GitHub Actions 설정
- 레포 루트에 .github/workflows/ci-cd.yml 파일 생성
- push 이벤트(main 브랜치)와 workflow_dispatch 두 트리거 적용
- 민감 정보(EC2 호스트, SSH 키, 컨테이너 레지스트리 자격증명)는 모두 GitHub Secrets에 ****로 등록
name: CI/CD Pipeline
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Login to Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ secrets.REG_USER }}
password: ${{ secrets.REG_PASS }}
- name: Build & Push Backend Image
uses: docker/build-push-action@v4
with:
context: ./backend
file: ./backend/Dockerfile
push: true
tags: ghcr.io/yourorg/leaf-backend:latest
- name: Build & Push Frontend Image
uses: docker/build-push-action@v4
with:
context: ./frontend
file: ./frontend/Dockerfile
push: true
tags: ghcr.io/yourorg/leaf-frontend:latest
- name: Build & Push NLP Image
uses: docker/build-push-action@v4
with:
context: ./nlp
file: ./nlp/Dockerfile
push: true
tags: ghcr.io/yourorg/leaf-nlp:latest
- name: Deploy to EC2
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.EC2_HOST }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd ~/leaf
docker compose pull
docker compose up -d --build
배운 점 & 시행착오
- Secrets 관리: 키·토큰을 깃에 절대 노출하지 않기 위해 **** 마스킹
- 워크플로우 디버깅: 초기 단계에서 이미지 푸시 권한 오류 → Registry 권한 설정 보완
돌아보며
현재 작업한 내용들은 CI/CD 뼈대를 구축하기 위해 임시로 작업해 둔 내용입니다.
실제 개발이 진행되면서 점차 보완해 나갈 생각입니다.
제로 베이스에서 여기까지 진행한 스스로에게 따봉 드립니다ㅎ
아직 인프라에 대해서 산적해있는 문제들이 많습니다.
네트워크 설정이랑 도메인도 연결해야하고..
여기도 아는게 없는 제로 베이스라 계속 공부해 가면서 작업해 가야겠죠..ㅠ
이전글 보러가기
2025.06.11 - [프로젝트 관리] - Leaf 성장일지 #1 – ‘가볍게 쓰는 가계부’를 설계한다는 건
'프로젝트_리프' 카테고리의 다른 글
| Leaf 성장일지 #3 – 가비아 도메인을 AWS EC2에 연결하는 가장 깔끔한 방법 (0) | 2025.07.12 |
|---|