티스토리 뷰

반응형

안녕하세요!
이번 포스팅에서는 스프링 부트(Kotlin) 애플리케이션의 배포 과정에 대해 정리해보려고 합니다.

사실 이전에도 FastAPI와 스프링 부트(Java) 프로젝트의 배포 과정을 다룬 적이 있었는데요.
이번 프로젝트에서는 제가 서버 배포를 직접 담당하게 되면서, 기존에 사용하던 방식이 조금 잘못되었음을 깨닫게 되었고, 이를 바로잡아 다시 정리하게 되었습니다.

 

기존에 작성한 관련 글들은 아래 링크에서 확인하실 수 있습니다.

 

 

https://pooreumjung.tistory.com/537

 

[FastAPI/Pyhton] - FastAPI Github Actions 자동 배포하기 #1

최근 해커톤에서 AI 기술을 활용해야 하는 상황이 있어, FastAPI 환경을 구축하고 직접 배포를 진행하게 되었습니다.Spring Boot 배포 방식과 다른 점들이 있어, 나중에 헷갈리지 않도록 정리해 두려

pooreumjung.tistory.com

https://pooreumjung.tistory.com/538

 

[FastAPI/Pyhton] - FastAPI Github Actions 자동 배포하기 #2

https://pooreumjung.tistory.com/537 [FastAPI/Pyhton] - FastAPI Github Actions 자동 배포하기 #1최근 해커톤에서 AI 기술을 활용해야 하는 상황이 있어, FastAPI 환경을 구축하고 직접 배포를 진행하게 되었습니다.Sprin

pooreumjung.tistory.com

https://pooreumjung.tistory.com/530

 

[Spring/스프링] - 스프링 부트 Github Actions 자동 배포

최근 프로젝트를 하면서 직접 배포를 하게 되었는데, 생각보다 복잡하고 명령어들이 낯설어서 나중에 헷갈리지 않기 위해 정리하려고 한다.환경은 Spring boot(JAVA) + MySQL + Redis이며, Dockerfile + docker

pooreumjung.tistory.com

 

기본적인 인스턴스 설정이나 ECR설정 보안그룹 SSH 접속 방법 등은 위의 게시물에 접속해서 확인해주시면 될 것 같습니다/.
그러면 차근차근 작성해보도록 하겠습니다.

 

✏️  배포 과정

지금까지 제가 사용해왔던 방식은 다음과 같았습니다:

  1. GitHub Actions의 워크플로우에서 이미지를 빌드
  2. 빌드한 이미지를 AWS ECR에 푸시
  3. EC2 인스턴스에서 해당 이미지를 pull
  4. docker-compose.yml로 컨테이너 실행

처음에는 이 방식이 가장 일반적이고 권장되는 흐름이라고 생각해서, 이에 맞춰 워크플로우와 코드를 구성했습니다.

하지만 실제로는 제가 사용하던 다음 명령어가 문제였습니다.

이 스크립트에서 docker-compose up --build -d는 ECR에서 이미지를 pull해오는 게 아니라, EC2 인스턴스 로컬에서 다시 이미지를 빌드하는 방식이었습니다. 즉, ECR에 푸시한 이미지가 실제 배포에 사용되지 않고 있었던 거죠. 😓

  - name: Deploy to EC2 via SSH
        uses: appleboy/ssh-action@v0.1.10 
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_KEY }}
          script: |
            cd ~/app-backend/app-backend  
            git pull origin main
            docker-compose down
            docker-compose up --build -d

✏️ Workflow

이 워크플로우는 develop 브랜치에 push가 발생했을 때 자동으로 실행되며, 다음과 같은 과정을 거칩니다:

 

1. 이미지 빌드 및 ECR 푸시

  • GitHub Actions에서 코드를 체크아웃한 뒤, AWS OIDC 기반으로 IAM Role을 임시로 Assume합니다. (ARN_ECR_PUSH_ROLE)
  • ECR에 로그인한 후, Docker 빌드에 필요한 application-secret.yml을 생성합니다.
  • 이미지 태그는 GitHub SHA 해시 기반과 latest를 동시에 지정합니다.
  • docker/build-push-action@v6을 이용해 Docker 이미지를 빌드하고, ECR에 푸시합니다.
    이때 Buildx와 GitHub Actions 캐시(GHA)를 활용해 속도도 개선했습니다.

2. EC2로 배포

  • EC2에 SSH로 접속한 후, ECR에서 이미지를 pull 받아 실행하는 과정을 진행합니다.
  • .env 파일에 최신 이미지 URI를 명시하여 docker-compose에서 사용할 수 있게 합니다.
  • ECR 인증 후 docker compose pull로 최신 이미지를 가져오고, up -d로 실행합니다.
  • 마지막으로, 사용하지 않는 이미지 및 컨테이너를 정리합니다.
name: Build and Deploy to EC2 via ECR

on:
  push:
    branches: ["develop"]

concurrency:
  group: ecr-ec2-deploy-${{ github.ref }}
  cancel-in-progress: true

env:
  AWS_REGION: ap-northeast-2
  ECR_REPOSITORY: gat2025/gat_be

permissions:
  id-token: write
  contents: read

jobs:
  build_and_push:
    name: Build & Push Docker Image to ECR
    runs-on: ubuntu-latest
    outputs:
      image_uri: ${{ steps.meta.outputs.image_uri }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      # AWS 자격 증명 구성(OIDC)
      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v3
        with:
          role-to-assume: ${{ secrets.ARN_ECR_PUSH_ROLE }}
          role-session-name: ecrPrivatePushRole
          aws-region: ${{ env.AWS_REGION }}
          audience: sts.amazonaws.com

      # ECR 로그인
      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      # Docker 이미지 빌드 전 application-secret.yml 생성
      - name: Create application-secret.yml before Docker build
        run: |
          mkdir -p src/main/resources
          echo "${{ secrets.GAT_SECRET_YML }}" > src/main/resources/application-secret.yml
          
      # Docker 이미지 태그 준비 (SHA 해시와 latest 태그 포함)
      - name: Prepare image tags
        id: meta
        run: |
          REGISTRY="${{ steps.login-ecr.outputs.registry }}"
          REPO="${{ env.ECR_REPOSITORY }}"
          SHA_TAG="${{ github.sha }}"
          echo "image_uri=${REGISTRY}/${REPO}:${SHA_TAG}" >> $GITHUB_OUTPUT
          echo "tags=${REGISTRY}/${REPO}:${SHA_TAG},${REGISTRY}/${REPO}:latest" >> $GITHUB_OUTPUT

      # Docker Buildx 설치 (멀티 플랫폼 빌드, 캐싱 등)
      - name: Set up Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@v3
        with:
          driver: docker-container
          install: true

      # Docker 이미지 빌드 및 ECR로 푸시
      - name: Build & Push (cache enabled)
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          builder: ${{ steps.buildx.outputs.name }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    name: Deploy on EC2 (pull & up)
    needs: build_and_push
    runs-on: ubuntu-latest

    steps:
      # EC2에서 최신 이미지 pull → 컨테이너 재시작 → 불필요한 이미지/컨테이너 정리
      - name: Pull new image & restart containers (Compose v2)
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            set -e
            cd ~/gat_be
            
            echo "IMAGE=${{ needs.build_and_push.outputs.image_uri }}" > .env
                        
            REGISTRY="$(aws sts get-caller-identity --query 'Account' --output text).dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com"
            aws ecr get-login-password --region ${{ env.AWS_REGION }} \
              | docker login --username AWS --password-stdin "$REGISTRY"
                        
            docker compose pull
            docker compose up -d
                        
            docker image prune -af || true
            docker container prune -f || true

✏️  docker-compose.yml

1. EC2에 GitHub Actions로부터 SSH 접속 

IMAGE=ECR 이미지 URI가 들어간 .env 파일을 생성함
→ ${IMAGE}는 이 값을 참조함

 

2. docker compose pull

.env에 정의된 ${IMAGE} 값을 바탕으로 ECR에서 해당 이미지를 EC2로 다운로드(pull)

 

3.docker compose up -d

pull된 이미지를 기반으로 gat-backend 컨테이너를 실행함

포트는 8080:8080, 컨테이너 이름은 gat_be

 

4. env_file 설정

.env에 정의된 환경변수들을 가져옴(여기서는 ECR 이미지의 경로)

 

5. networks 설정

gat-network라는 사용자 정의 네트워크를 구성
이후 DB, Redis 등의 컨테이너와 연결할 때 이 네트워크를 공유하게 됨

services:
  gat-backend:
    image: ${IMAGE}
    container_name: "gat_be"
    env_file:
      - .env
    ports:
      - 8080:8080
    networks:
      - gat-network

networks:
  gat-network:

 

 

 

✏️ Dockerfile

1. 멀티 스테이지 빌드 구조

  • 하나의 Dockerfile 안에서 build용 이미지 (builder)최종 실행 이미지 (runtime) 를 나눠 사용
  • 빌드 단계에서는 JDK 포함, 런타임 단계에서는 더 가볍게 JRE만 포함 → 이미지 최적화

2. builder 단계 (JDK 기반)

  • bellsoft/liberica-openjdk-alpine:17
    → Alpine 기반의 JDK 17 이미지를 사용해 빌드 수행
  • WORKDIR /app
    → 작업 디렉토리를 /app으로 설정
  • COPY gradlew, COPY gradle, COPY *.kts
    → Gradle 실행 파일과 빌드 설정 파일들을 복사
  • RUN chmod +x ./gradlew
    → gradlew에 실행 권한 부여 (Linux에서 필수)
  • RUN ./gradlew dependencies
    → 의존성 캐싱 → 이후 빌드 시 속도 개선
  • COPY src src
    → 실제 애플리케이션 소스 복사
  • RUN ./gradlew clean bootJar
    → Spring Boot 애플리케이션을 JAR 파일로 빌드

3. runtime 단계 (JRE 기반)

  • bellsoft/liberica-openjre-alpine:17
    → 실행용으로 더 가벼운 JRE 17 이미지 사용
  • COPY --from=builder ...
    → 빌드 단계에서 생성된 app.jar만 추출해서 복사
  • EXPOSE 8080
    → 컨테이너 외부로 노출할 포트 지정 (Spring Boot 기본 포트)
  • ENTRYPOINT ["java", "-Duser.timezone=Asia/Seoul", "-jar", "/app/app.jar"]
    → 해당 JAR 파일을 실행하는 명령어
    시간대는 서울 기준으로 설정
FROM bellsoft/liberica-openjdk-alpine:17 AS builder

WORKDIR /app

# Gradle 및 설정 파일 복사
COPY gradlew .
COPY gradle gradle
COPY build.gradle.kts .
COPY settings.gradle.kts .

# 실행 권한 부여
RUN chmod +x ./gradlew

# 의존성 캐시
RUN ./gradlew dependencies --no-daemon || return 0

# 소스 복사
COPY src src


# 빌드
RUN ./gradlew clean bootJar --no-daemon

# 런타임 이미지
FROM bellsoft/liberica-openjre-alpine:17
WORKDIR /app

COPY --from=builder /app/build/libs/*.jar app.jar


EXPOSE 8080

ENTRYPOINT ["java", "-Duser.timezone=Asia/Seoul", "-jar", "/app/app.jar"]

✏️  기타

이 방식으로 배포하려면 사전 설정이 필요합니다

GitHub Actions → ECR → EC2 방식으로 자동 배포를 하기 위해서는
EC2 인스턴스와 AWS IAM 설정, 그리고 EC2 내부 환경 구성이 먼저 되어 있어야 합니다.

 

Docker 설치

설치 후 재로그인해야 docker 명령어를 sudo 없이 쓸 수 있습니다.

sudo yum update -y
sudo amazon-linux-extras install docker -y
sudo service docker start
sudo usermod -aG docker ec2-user
 

Docker Compose 설치 (v2 기준)

Docker Compose v2는 docker compose 명령어로 통합되어 있으므로 다음처럼 설치합니다.

EC2에 접속한 후 docker compose 명령어가 잘 작동하는지 꼭 테스트해보세요!

mkdir -p ~/.docker/cli-plugins
curl -SL https://github.com/docker/compose/releases/download/v2.27.0/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose
docker compose version

 

 

EC2 인스턴스에 IAM 역할 연결

GitHub Actions가 EC2에 SSH 접속해서 ECR 이미지를 pull하기 위해서는,
EC2 인스턴스에 ECR 접근 권한이 있는 IAM 역할을 연결해줘야 합니다.

2-1. IAM 역할 생성

  1. AWS 콘솔 → IAM → 역할 생성
  2. 신뢰할 수 있는 엔터티 유형: AWS 서비스
  3. 사용 사례: EC2
  4. 권한 정책: 아래 정책을 추가합니다 => AmazonEC2ContainerRegistryReadOnly

이 정책은 ECR에서 이미지를 pull만 할 수 있게 해줍니다.
만약 EC2가 push도 해야 한다면 AmazonEC2ContainerRegistryFullAccess를 사용하세요.

2-2. 생성한 IAM 역할을 EC2 인스턴스에 연결

  1. EC2 콘솔 → 인스턴스 선택
  2. 작업 > 보안 > IAM 역할 수정
  3. 아까 만든 역할을 선택해서 적용

이제 EC2는 ECR에 접근할 수 있는 권한을 가지게 됩니다.

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
글 보관함