반응형
Cursor AI와 Spring Boot 개발환경 설정 가이드 - 5편: 프로덕션 배포 및 CI/CD 파이프라인
프로덕션 환경 준비
개발 환경에서 프로덕션으로 전환하는 과정은 성능 최적화, 보안 강화, 그리고 자동화된 배포 파이프라인 구축이 핵심입니다. Cursor AI의 코드 생성 능력을 활용하면 복잡한 배포 설정도 효율적으로 구성할 수 있습니다.
Docker 멀티 스테이지 빌드
Spring Boot 백엔드를 위한 최적화된 Docker 이미지를 생성합니다:
# backend/Dockerfile
# 멀티 스테이지 빌드로 이미지 크기 최적화
FROM gradle:8.5-jdk17 AS builder
WORKDIR /app
COPY build.gradle settings.gradle ./
COPY src ./src
# Gradle 캐시 활용을 위한 의존성 먼저 다운로드
RUN gradle dependencies --no-daemon
# 애플리케이션 빌드
RUN gradle clean build --no-daemon -x test
# 프로덕션 런타임 이미지
FROM openjdk:17-jre-slim
# 보안을 위한 비루트 사용자 생성
RUN groupadd -r spring && useradd -r -g spring spring
# 필수 패키지만 설치
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 빌드된 JAR 파일 복사
COPY --from=builder /app/build/libs/*.jar app.jar
# 애플리케이션 실행을 위한 설정
RUN chown -R spring:spring /app
USER spring
# JVM 메모리 최적화
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
# 헬스체크 설정
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
Next.js 프로덕션 Dockerfile
# frontend/Dockerfile
FROM node:18-alpine AS base
# 의존성 설치
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci --only=production
# 빌더 스테이지
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js 텔레메트리 비활성화
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
# 프로덕션 런타임
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# 빌드 출력물 복사
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]
GitHub Actions CI/CD 파이프라인
자동화된 빌드, 테스트, 배포를 위한 GitHub Actions 워크플로우를 설정합니다:
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
BACKEND_IMAGE_NAME: ${{ github.repository }}/backend
FRONTEND_IMAGE_NAME: ${{ github.repository }}/frontend
jobs:
# 백엔드 테스트 및 빌드
backend-ci:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./backend
services:
mariadb:
image: mariadb:10.11
env:
MYSQL_ROOT_PASSWORD: testpass
MYSQL_DATABASE: testdb
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle packages
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Run tests
run: ./gradlew test --no-daemon
env:
SPRING_PROFILES_ACTIVE: test
SPRING_DATASOURCE_URL: jdbc:mariadb://localhost:3306/testdb
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: testpass
- name: Build application
run: ./gradlew build --no-daemon
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: backend-test-results
path: backend/build/reports/tests/
# 프론트엔드 테스트 및 빌드
frontend-ci:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run type check
run: npm run type-check
- name: Run linting
run: npm run lint
- name: Run tests
run: npm test -- --watchAll=false
- name: Build application
run: npm run build
env:
NEXT_PUBLIC_API_BASE_URL: ${{ secrets.PRODUCTION_API_URL }}
# Docker 이미지 빌드 및 푸시
build-and-push:
needs: [backend-ci, frontend-ci]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push backend image
uses: docker/build-push-action@v5
with:
context: ./backend
push: true
tags: |
${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:${{ github.sha }}
- name: Build and push frontend image
uses: docker/build-push-action@v5
with:
context: ./frontend
push: true
tags: |
${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}:${{ github.sha }}
# 프로덕션 배포
deploy:
needs: build-and-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.PRODUCTION_HOST }}
username: ${{ secrets.PRODUCTION_USER }}
key: ${{ secrets.PRODUCTION_SSH_KEY }}
script: |
cd /opt/app
docker-compose pull
docker-compose up -d
docker system prune -f
프로덕션 Docker Compose
# docker-compose.prod.yml
version: '3.8'
services:
mariadb:
image: mariadb:10.11
container_name: prod-mariadb
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mariadb_data:/var/lib/mysql
- ./backup:/backup
restart: unless-stopped
networks:
- app-network
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
backend:
image: ghcr.io/${GITHUB_REPOSITORY}/backend:latest
container_name: prod-backend
environment:
SPRING_PROFILES_ACTIVE: prod
SPRING_DATASOURCE_URL: jdbc:mariadb://mariadb:3306/${DB_NAME}
SPRING_DATASOURCE_USERNAME: ${DB_USER}
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
JWT_SECRET: ${JWT_SECRET}
depends_on:
- mariadb
restart: unless-stopped
networks:
- app-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
frontend:
image: ghcr.io/${GITHUB_REPOSITORY}/frontend:latest
container_name: prod-frontend
environment:
NEXT_PUBLIC_API_BASE_URL: https://api.yourdomain.com
depends_on:
- backend
restart: unless-stopped
networks:
- app-network
nginx:
image: nginx:alpine
container_name: prod-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl:/etc/nginx/ssl
depends_on:
- frontend
- backend
restart: unless-stopped
networks:
- app-network
volumes:
mariadb_data:
networks:
app-network:
driver: bridge
Nginx 리버스 프록시 설정
# nginx/nginx.conf
events {
worker_connections 1024;
}
http {
upstream backend {
server backend:8080;
}
upstream frontend {
server frontend:3000;
}
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# API 요청
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# CORS 헤더
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type";
}
# 프론트엔드 요청
location / {
proxy_pass http://frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
모니터링 및 로깅
Prometheus + Grafana 설정
# monitoring/docker-compose.monitoring.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3001:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/dashboards:/etc/grafana/provisioning/dashboards
- ./grafana/datasources:/etc/grafana/provisioning/datasources
volumes:
prometheus_data:
grafana_data:
Cursor AI 활용 배포 스크립트
Cursor AI를 사용하여 자동화된 배포 스크립트를 생성합니다:
#!/bin/bash
# scripts/deploy.sh
set -e
echo "🚀 프로덕션 배포를 시작합니다..."
# 환경 변수 확인
if [ -z "$PRODUCTION_HOST" ] || [ -z "$GITHUB_TOKEN" ]; then
echo "❌ 필요한 환경 변수가 설정되지 않았습니다."
exit 1
fi
# 최신 이미지 가져오기
echo "📦 최신 Docker 이미지를 가져오는 중..."
docker-compose -f docker-compose.prod.yml pull
# 데이터베이스 백업
echo "💾 데이터베이스 백업 중..."
docker exec prod-mariadb mysqldump -u root -p${DB_ROOT_PASSWORD} ${DB_NAME} > backup/backup_$(date +%Y%m%d_%H%M%S).sql
# 무중단 배포
echo "🔄 서비스 업데이트 중..."
docker-compose -f docker-compose.prod.yml up -d --remove-orphans
# 헬스체크
echo "🏥 서비스 상태 확인 중..."
sleep 30
if curl -f http://localhost/api/actuator/health; then
echo "✅ 배포가 성공적으로 완료되었습니다!"
# 슬랙 알림 (선택사항)
if [ -n "$SLACK_WEBHOOK_URL" ]; then
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"🎉 프로덕션 배포가 성공했습니다!"}' \
$SLACK_WEBHOOK_URL
fi
else
echo "❌ 배포에 실패했습니다. 롤백을 진행합니다..."
docker-compose -f docker-compose.prod.yml rollback
exit 1
fi
# 정리 작업
echo "🧹 불필요한 리소스 정리 중..."
docker system prune -f
echo "🎯 배포 프로세스가 완료되었습니다!"
성능 최적화 체크리스트
Spring Boot 프로덕션 설정
# backend/src/main/resources/application-prod.yml
spring:
jpa:
hibernate:
ddl-auto: validate
properties:
hibernate:
jdbc:
batch_size: 50
order_inserts: true
order_updates: true
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 10
connection-timeout: 30000
server:
port: 8080
compression:
enabled: true
http2:
enabled: true
logging:
level:
org.springframework.web: INFO
org.hibernate.SQL: WARN
마무리
이 5편 시리즈를 통해 Cursor AI와 Spring Boot 3.2.x LTS, Next.js 15.0.4, React 18, TypeScript 5, MariaDB를 활용한 완전한 풀스택 개발 환경을 구축하는 방법을 상세히 다뤘습니다. AI 기반 개발 도구의 활용으로 개발 생산성이 크게 향상되었으며, 체계적인 환경 설정을 통해 안정적인 프로덕션 서비스를 구축할 수 있습니다.
반응형
'Development Tools > cursor' 카테고리의 다른 글
CURSOR AI 관련 최신 패치 정보(2025년 6월이전) (4) | 2025.07.06 |
---|---|
커서 AI 툴에 사용할 수 있는 스프링부트 플러그인 (3) | 2025.06.29 |
Cursor AI와 Spring Boot 개발환경 설정 가이드 - 4편: 프론트엔드-백엔드 통합 개발 환경 (8) | 2025.06.27 |
Cursor AI와 Spring Boot 개발환경 설정 가이드 - 3편: Next.js 15.0.4 + React 18 프론트엔드 환경 구축 (0) | 2025.06.26 |
Cursor AI와 Spring Boot 개발환경 설정 가이드 - 2편: Spring Boot 3.2.x LTS 백엔드 환경 구축 (8) | 2025.06.25 |