728x90
반응형
사이드 프로젝트의 성패는 '얼마나 개발 이외의 인프라 공수를 줄일 수 있는가'에 달려있습니다. 코드 한 줄 작성할 시간도 부족한데, 리눅스 서버에 접속해 배포 스크립트를 고치고 Nginx 리버스 프록시를 튜닝하는 것은 굉장한 피로를 유발합니다. 이를 해결하기 위해 프론트엔드는 Vercel, 백엔드는 Render를 활용하여 완전 자동화된 배포 파이프라인을 구축했습니다.

1. 프론트엔드: Vercel이 가져다준 개발 경험(DX)의 혁신
과거에는 AWS S3 버킷에 빌드된 리액트 파일을 올리고 CloudFront 캐시를 갱신(Invalidation)하는 GitHub Actions 스크립트를 직접 짜야 했습니다. Vercel은 이 과정을 생략합니다.
Preview Deployment (미리보기 배포)
가장 만족스러웠던 기능은 PR(Pull Request) 단계에서 제공되는 Preview Deployment입니다.
새로운 기능을 브랜치에서 작업하고 PR을 올리면, Vercel이 해당 브랜치만을 위한 독립적인 URL을 즉시 생성해 줍니다.
- 로컬에서 브랜치를 체크아웃받고
npm run dev를 실행할 필요 없이, 핸드폰 브라우저로 링크만 열어서 모바일 UI를 바로 테스트할 수 있었습니다. - 1인 개발 사이드 프로젝트에서도 '셀프 코드 리뷰'와 '모바일 실기기 테스트'의 질을 엄청나게 높여주었습니다.
Vercel 프로젝트 설정
실제 프로젝트의 vercel.json 설정입니다. 단 13줄로 빌드 명령, API 프록시, 프레임워크 지정까지 모두 끝납니다.
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"installCommand": "npm install",
"framework": "vite",
"rewrites": [
{
"source": "/api/:path*",
"destination": "https://<your-app>.onrender.com/api/:path*"
}
]
}
rewrites 설정 한 방으로 프론트엔드의 /api/* 요청이 Render 백엔드로 자동 프록시 되어, CORS 설정의 복잡함을 제거했습니다.
2. 백엔드: Render의 Docker 배포와 Cold Start 방어
백엔드는 Spring Boot(Java)를 사용했습니다. Render는 IaaS가 아닌 PaaS이므로, 서버 인스턴스를 관리할 필요가 없습니다.
환경 파편화 방지: Multi-stage Dockerfile 배포
로컬과 운영 서버의 자바(Java) 버전 파편화를 막기 위해 멀티 스테이지 빌드 Dockerfile을 작성하여 Render에 배포했습니다.
# Stage 1: Build — Gradle로 bootJar 생성
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /app
COPY gradlew .
COPY gradle gradle
COPY build.gradle settings.gradle ./
RUN chmod +x gradlew
RUN ./gradlew dependencies --no-daemon || true
COPY src src
RUN ./gradlew bootJar --no-daemon -x test
# Stage 2: Run — 경량 JRE 이미지에서 실행
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-jar", "app.jar"]
핵심 포인트: jdk-alpine으로 빌드하고 jre-alpine으로 실행하는 멀티 스테이지 전략으로, 최종 이미지 사이즈를 400MB → 약 150MB로 줄였습니다. Render의 무료 빌드 시간(Free Tier)과 메모리를 아끼는 데 크게 기여했습니다.
Render 대시보드에 GitHub 레포를 연결하고 환경을 Docker로 지정하면, 코드가 푸시될 때마다 Render가 위 파일을 읽어 자동으로 컨테이너를 빌드하고 무중단 배포를 수행합니다.
치명적 단점, Cold Start 방어전
Render의 무료 플랜(Free Tier)은 15분 동안 외부 요청이 없으면 인스턴스를 수면(Sleep) 상태로 전환합니다. 이후 유저가 접속하면 서버가 깨어나는 데(Cold Start) 30초~1분 이상이 소요되어 끔찍한 UX를 초래합니다. 이를 막기 위해 /health 엔드포인트를 직접 구현했습니다.
// HealthController.java — UptimeRobot이 핑하는 대상
@RestController
public class HealthController {
@GetMapping("/health")
public ResponseEntity<Map<String, String>> health() {
return ResponseEntity.ok(Map.of("status", "ok"));
}
}
그리고 UptimeRobot이라는 무료 외부 모니터링 서비스에 가입하여, 위 /health 엔드포인트로 10분마다 핑(HTTP GET)을 날리도록 설정했습니다. 이 꼼수(?) 덕분에 백엔드 서버는 24시간 잠들지 않고 즉각적인 API 응답을 보장하게 되었습니다.
Spring Boot Actuator 대신 직접 구현한 이유: Actuator를 의존성에 추가하면 /actuator/health 외에도 다수의 관리 엔드포인트가 자동 노출됩니다. 보안 설정에 신경 쓸 여유가 없었기에, 딱 필요한 한 줄짜리 컨트롤러를 직접 작성했습니다.
3. 마무리
Vercel과 Render의 조합은 1인 개발자에게 축복과도 같습니다. CI/CD 구축과 인프라 관리에 들어갈 수십 시간을 아껴 핵심 비즈니스 로직 개발에 투자할 수 있었습니다. 다음 편에서는 데이터베이스로 활용한 Supabase와, 그곳에서 마주친 끔찍한 '커넥션 풀 고갈' 에러 해결 과정에 대해 다루겠습니다.
728x90
반응형