[GCP] Google Cloud Run + 도커 Docker 배포 (스프링부트 리액트 노드)
my code archive
article thumbnail
반응형

한 달 정도 팀프로젝트를 진행했고 1차 개발이 끝나서 배포까지 도전!!

현재 프로젝트 구조가 Spring Boot + React + Node.js 로 되어 있기에

추후 CI/CD 까지 고려하여

서버리스 방식으로 관리가 가장 편리한 Google Cloud Run을 선택하게 되었당.

 

GCP Compute Engine VS Cloud Run 비교

  Compute Engine Cloud Run
서비스 종류 가상 머신(VM) 컨테이너 기반 서버리스
내가 직접 관리? OS, 보안 패치 등 직접 GCP가 다 해줌
Docker 필요 여부 없어도 가능 필수 (컨테이너 기반)
배포 방식 수동 (SSH 접속 등) 자동 (gcloud run deploy)

 

현재 프로젝트 구조

서비스 기능 배포 위치
React 프론트, 정적 사이트, Nginx로 서빙 Cloud Run
Spring Boot 백엔드, REST API, DB 연동 가능 Cloud Run
Node.js Puppeteer, 크롤링 등 주기 작업 수행 Cloud Run

 

 

구현 목표

  • React / Spring Boot / Node 각각 Dockerfile 작성
  • Artifact Registry에 컨테이너 저장
  • Cloud Run으로 서비스 배포
  • GitHub + Cloud Build로 CI/CD 자동화 연결 (추후)

 

도커 허브(Docker Hub) 이미지 Repository 생성

 

Dockerfile 생성

각각 프로젝트 최상단 루트에 생성해주면 된다. 확장자는 따로 없고 파일명이 Dockerfile 임!!

 

-Spring Boot

FROM eclipse-temurin:17-jdk-alpine
# 2단계: jar 파일 복사
COPY build/libs/app.jar app.jar

# 3단계: 실행
ENTRYPOINT ["java", "-jar", "/app.jar"]

 

-React

npm run build	--빌드하기
# Step 1: Build the React app
FROM node:18 as build

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Step 2: Serve with nginx
FROM nginx:alpine

COPY --from=build /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
nginx.conf 

server {
    listen 80;
    server_name localhost;

    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri /index.html;
    }
}

 

-Node.js

FROM node:18

WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .

CMD ["node", "crawl.js"]  # 또는 app.js 등 메인 진입점

 

2. Docker 이미지 빌드

cd 프로젝트 경로
./gradlew clean build

 

3. Artifact Registry 생성

gcloud artifacts repositories create 프로젝트이름 \         
  --repository-format=docker \
  --location=asia-northeast3 \
  --description="프로젝트 이름 Docker Repo"

 

배포용 deploy.sh 스크립트 작성

스프링부트, 리액트, node 따로 배포를 하다 보니 각각 명령어가 모두 다르고 번거로움.

각각 배포할 때마다 도커 이미지 푸시, 배포를 따로 해줘야 해서

한방에 자동으로 배포해주는 deploy.sh 파일을 작성하기로 했다.

 

springboot-deploy.sh

#!/bin/bash
# 설정값
PROJECT_ID="gcp 프로젝트 ID"
REGION="asia-northeast3"
REPO_NAME="Artifact Registry 이름"
IMAGE_NAME="도커 이미지파일 이름"
IMAGE_TAG="1.0"
SERVICE_NAME="서비스 이름"
FULL_IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"

echo "Gradle 빌드 시작..."
./gradlew clean bootJar

echo "Docker 멀티 아키텍처 이미지 빌드 및 푸시 중..."
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t "$FULL_IMAGE" \
  --push .

echo "Cloud Run 배포 중 (환경변수 SPRING_PROFILES_ACTIVE=dev 포함)..."
gcloud run deploy "$SERVICE_NAME" \
  --image="$FULL_IMAGE" \
  --platform managed \
  --region "$REGION" \
  --allow-unauthenticated \
  --set-env-vars SPRING_PROFILES_ACTIVE=dev


if [ $? -eq 0 ]; then
  echo "Spring Boot 서비스 배포 성공!"
else
  echo "Spring Boot 배포 실패!"
  exit 1
fi

 

react-deploy.sh

#!/bin/bash

# 설정값
PROJECT_ID="gcp 프로젝트 ID"
REGION="asia-northeast3"
REPO_NAME="Artifact Registry 이름"
IMAGE_NAME="도커 이미지파일 이름"
IMAGE_TAG="1.0"
SERVICE_NAME="서비스 이름"
FULL_IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"

echo "기존 빌드 폴더 제거 중..."
rm -rf build

echo "React 빌드 중..."
npm run build

echo "Docker 이미지 빌드 및 푸시 (빌드 타임 환경변수 포함)..."
docker buildx build \
  --platform=linux/amd64 \
  --build-arg REACT_APP_API_BASE_URL="https://배포된 주소.asia-northeast3.run.app" \
  -t "$FULL_IMAGE" \
  --push .

echo "Cloud Run 배포 중..."
gcloud run deploy "$SERVICE_NAME" \
  --image="$FULL_IMAGE" \
  --platform=managed \
  --region="$REGION" \
  --allow-unauthenticated

if [ $? -eq 0 ]; then
  echo "React 서비스 배포 성공!"
else
  echo "chmod +x deploy.sh
React 배포 실패!"
  exit 1
fi

 

node-deploy.sh

#!/bin/bash

# 설정
PROJECT_ID="gcp 프로젝트 ID"
REGION="asia-northeast3"
REPO_NAME="Artifact Registry 이름"
IMAGE_NAME="도커 이미지파일 이름"
IMAGE_TAG="1.0"
SERVICE_NAME="서비스 이름"
FULL_IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/${IMAGE_NAME}:${IMAGE_TAG}"

# .env → 환경변수 문자열 변환
if [ ! -f .env ]; then
echo ".env 파일이 없습니다. 중단합니다."
exit 1
fi

ENV_VARS=$(cat .env | grep -v '^#' | xargs | sed 's/ /,/g')

echo "이미지 빌드 및 배포 시작..."
echo "이미지: $FULL_IMAGE"

# Docker 이미지 빌드 (M1 대응)
docker buildx build \
--platform linux/amd64 \
-t "$FULL_IMAGE" \
--push .

# Cloud Run 배포
gcloud run deploy "$SERVICE_NAME" \
--image="$FULL_IMAGE" \
--region="$REGION" \
--platform=managed \
--set-env-vars="$ENV_VARS" \
--allow-unauthenticated \
--memory=1Gi

if [ $? -eq 0 ]; then
echo "배포 성공!"
else
echo "배포 실패"
exit 1
fi

 

터미널에 ./deploy.sh 명령어 입력하면 한방에 배포끝!

💣트러블 슈팅

리액트 보안 파일 (.env) 관련

.env 파일로 보안 관련 내용을 관리 중이었는데 배포 시에 개발, 운영 환경에 맞춰 .env 파일을 각각 관리해야 한다고 한다.

 

# .env.development
REACT_APP_API_BASE_URL=http://localhost:8080

# .env.production
REACT_APP_API_BASE_URL=https://배포된 주소.asia-northeast3.run.app

 

리액트 코드에는 이런 식으로

const API_BASE_URL = process.env.REACT_APP_API_BASE_URL;

const response = await axios.get(`${API_BASE_URL}/api/musical/getMonthMusical`, {
    params: {
        startDate: getDateString(),
        endDate: getDateString('lastday'),
    }
});

 

위와 같이 관리하면 빌드, 개발 시 따로 설정할 필요 없이 알맞은 .env 파일을 불러옴.

# 개발
npm start    # → .env.development 자동 사용

# 배포용 정적 파일 빌드
npm run build  # → .env.production 자동 사용

 

이를 위한 React Dockerfile 수정 (최종)

# React 앱 빌드
FROM node:18 as build

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .
# REACT_APP_API_BASE_URL 환경변수로 React 앱 빌드
ARG REACT_APP_API_BASE_URL
ENV REACT_APP_API_BASE_URL=$REACT_APP_API_BASE_URL

RUN echo "REACT_APP_API_BASE_URL is $REACT_APP_API_BASE_URL"

RUN npm run build

# 2. nginx로 서비스
FROM nginx:alpine

# 빌드된 정적파일 복사
COPY --from=build /app/build /usr/share/nginx/html

# nginx 설정 템플릿 복사
COPY nginx.conf.template /etc/nginx/templates/nginx.conf.template

# 컨테이너 포트 노출 (Cloud Run 기본 8080)
EXPOSE 8080

# $PORT 환경변수 치환 후 nginx 실행
CMD ["/bin/sh", "-c", "envsubst '$PORT' < /etc/nginx/templates/nginx.conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"]

 

스프링부트 애플리케이션 실행 가능한 JAR 파일 만들기 위한 build.gradle 설정

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.5.0'
    id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.curty'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    // 생략
}

tasks.withType(Jar).configureEach {
    enabled = false
}

// 핵심!
tasks.named('bootJar') {
    enabled = true
    mainClass.set('com.curty.muggle.MuggleApplication')
    archiveFileName.set('app.jar')
}

 

빌드 시 build/libs/app.jar 파일이 잘 생성됨.

 

GCP 이미지 업로드 스토리지 통일

먼저 다른 팀원이 게시판 이미지 업로드 파일을 저장할 GCP 스토리지를 생성했으나 배포용 GCP에 맞춰

하나로 통일함.

 

+ 이외에도 수많은 삽질이 있었으나 대부분 스프링 시큐리티 관련이었다.

node, React를 따로 배포하다 보니 관련 주소를 SpringSecurityConfig CORS에 추가해주어야했고

기존 로컬 주소 뿐만 아니라 배포 주소도 추가해주어야했다.

 

 

이렇게 Service URL이 뜨면 드디어 정상으로 배포 성공한 것이다!!

 

 

스프링부트, 리액트, 노드 모두 잘 배포됐고

 

로그도 볼 수 있다.

반응형
profile

my code archive

@얼레벌레 개발자👩‍💻

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

반응형