요는 github 최적화되어 사용이 간편하고, 마켓플레이스에서 프리셋을 사용할 수 있다. 서버를 직접 호스팅하지 않아도 되고, 자원도 넉넉하여 비용부담이 거의 없다.
키워드 순서대로 설명하겠다.
# StayConnect-Action.yml
name: GITHUB ACTION CI/CD WITH AWS

말 그대로 이름이다. github의 action탭에서 왼쪽 사이드바에서 확인할 수 있다.
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
on 키워드는 '언제' 실행될지 설정하는 조건을 위한 키워드이다. 해당 설정은 main, develop 브랜치에 push 또는 pull request를 할 때 실행되도록 설정했다.
jobs:
build: ...
deploy: ...
clear: ...
dependency-submission: ...
jobs 키워드는 on 키워드의 조건이 충족되었을 때 실행될 작업들을 정의하는 곳이다. 하위의 build, deploy, clear는 직접 정의한 jobs의 이름이다. 본인이 원하는 이름으로 작성하면 된다. dependency-submission은 dependency graph를 생성하는 공식 프리셋 기본 이름이다.
jobs:
build:
runs-on: ubuntu-latest # hosting server os 명시
steps: # 내 repository를 hostign server로 가져온다.
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up JDK 21 # hosting server에 JDK 설치
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Create environment variables file # 환경변수 파일 생성. 보안을 위해 github secret 사용
run: |
echo "BASE_URL=${{ secrets.BASE_URL }}" >> .env
echo "SERVER_PORT=${{ secrets.SERVER_PORT }}" >> .env
echo "JSON_WEB_TOKEN_SECRET_KEY=${{ secrets.JSON_WEB_TOKEN_SECRET_KEY }}" >> .env
echo "JSON_WEB_TOKEN_EXPIRATION_TIME=${{ secrets.JSON_WEB_TOKEN_EXPIRATION_TIME }}" >> .env
echo "JSON_WEB_TOKEN_ISSUER=${{ secrets.JSON_WEB_TOKEN_ISSUER }}" >> .env
echo "MYSQL_SERVER_URL=${{ secrets.MYSQL_SERVER_URL }}" >> .env
echo "MYSQL_SERVER_USERNAME=${{ secrets.MYSQL_SERVER_USERNAME }}" >> .env
echo "MYSQL_SERVER_PASSWORD=${{ secrets.MYSQL_SERVER_PASSWORD }}" >> .env
echo "GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}" >> .env
echo "GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}" >> .env
echo "KAKAO_CLIENT_ID=${{ secrets.KAKAO_CLIENT_ID }}" >> .env
echo "KAKAO_CLIENT_SECRET=${{ secrets.KAKAO_CLIENT_SECRET }}" >> .env
echo "PORTONE_API_URL=${{ secrets.PORTONE_API_URL }}" >> .env
echo "PORTONE_API_KEY=${{ secrets.PORTONE_API_KEY }}" >> .env
echo "PORTONE_SECRET_KEY=${{ secrets.PORTONE_SECRET_KEY }}" >> .env
echo "PRODUCTION_MYSQL_URL=${{ secrets.PRODUCTION_MYSQL_URL }}" >> .env
echo "PRODUCTION_MYSQL_ROOT_PASSWORD=${{ secrets.PRODUCTION_MYSQL_ROOT_PASSWORD }}" >> .env
echo "PRODUCTION_MYSQL_DATABASE=${{ secrets.PRODUCTION_MYSQL_DATABASE }}" >> .env
echo "PRODUCTION_MYSQL_USERNAME=${{ secrets.PRODUCTION_MYSQL_USERNAME }}" >> .env
echo "PRODUCTION_MYSQL_PASSWORD=${{ secrets.PRODUCTION_MYSQL_PASSWORD }}" >> .env
- name: Cache Gradle packages # gradle caching
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
- name: Grant execute permission for gradlew # gradlew 실행 권한 부여
run: chmod +x gradlew
- name: Build with Gradle # 배포를 위해 prod profile을 사용해서 jar build
run: ./gradlew clean build -Dspring.profiles.active=prod -x test
- name: Upload build artifacts # build한 jar 파일을 artifact에 업로드
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: build/libs/StayConnect-0.0.2.jar
retention-days: 1 # 저장공간을 확보하기 위해 유지기간 1일로 설정
- name: Upload build artifacts # deploy jobs로 넘겨주기 위해 env artifact upload
uses: actions/upload-artifact@v4
with:
name: env-artifacts
path: .env
retention-days: 1
deploy:
if: github.ref == 'refs/heads/main' # main branch에 변화가 있을 때 동작하도록 조건 설정
runs-on: ubuntu-latest
needs: build # build jobs가 선행되어야 함.
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download build artifacts # build한 jar 다운로드
uses: actions/download-artifact@v4
with:
name: build-artifacts
path: build/libs
- name: Download env artifacts # build한 env 다운로드
uses: actions/download-artifact@v4
with:
name: env-artifacts
path: .
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Set lower-case repository owner # 소문자만 돼서 owner 이름 소문자로 변경
id: lowercase
run: echo "::set-output name=repository_owner::$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')"
- name: Set up QEMU # 다중 플랫폼 빌드를 지원하기 위해 QEMU 에뮬레이터를 설정
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx # 다중 아키텍처 이미지를 빌드할 수 있도록 Docker Buildx를 설정
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry # GitHub Container Registry(GHCR)에 로그인
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ steps.lowercase.outputs.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Nginx image # Nginx 이미지를 빌드하고 GHCR로 푸시
uses: docker/build-push-action@v4
with:
context: .
file: ./docker/dockerfile.nginx
push: true
tags: ghcr.io/${{ steps.lowercase.outputs.repository_owner }}/stay_connect_nginx:latest
- name: Build and push Spring Boot image # Spring Boot 이미지를 빌드하고 GHCR로 푸시
uses: docker/build-push-action@v4
with:
context: .
file: ./docker/dockerfile.springboot
push: true
tags: ghcr.io/${{ steps.lowercase.outputs.repository_owner }}/stay_connect_app:latest
- name: Build and push certbot image # Certbot 이미지를 빌드하고 GHCR로 푸시
uses: docker/build-push-action@v4
with:
context: .
file: ./docker/dockerfile.certbot
push: true
tags: ghcr.io/${{ steps.lowercase.outputs.repository_owner }}/stay_connect_certbot:latest
- name: Check Directory # 오류 발생시 확인 용
run: ls -al
- name: Transfer docker-compose-prod.yml to EC2 using SCP # SCP 사용하여 EC2 인스턴스로 파일 전송
uses: appleboy/scp-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_PORT }}
source: ./docker/docker-compose-prod.yml
target: /home/${{ secrets.EC2_USER }}
- name: Transfer .env to EC2 using SCP
uses: appleboy/scp-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_PORT }}
source: .env
target: /home/${{ secrets.EC2_USER }}
- name: Transfer init-letsencrypt.sh to EC2 using SCP
uses: appleboy/scp-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_PORT }}
source: init-letsencrypt.sh
target: /home/${{ secrets.EC2_USER }}
- name: SSH and deploy to EC2 # SSH로 EC2 접속하여 배포 진행
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
port: ${{ secrets.EC2_PORT }}
script: |
if [ -f /home/${{ secrets.EC2_USER }}/docker/docker-compose-prod.yml ]; then
echo "File exists"
chmod 644 /home/${{ secrets.EC2_USER }}/docker/docker-compose-prod.yml
chmod +x init-letsencrypt.sh
else
echo "File does not exist"
exit 1
fi
docker-compose --env-file /home/${{ secrets.EC2_USER }}/.env -f /home/${{ secrets.EC2_USER }}/docker/docker-compose-prod.yml down
docker stop $(docker ps -aq)
docker rm $(docker ps -aq)
docker rmi -f $(docker images -q)
docker-compose --env-file /home/${{ secrets.EC2_USER }}/.env -f /home/${{ secrets.EC2_USER }}/docker/docker-compose-prod.yml pull
docker-compose --env-file /home/${{ secrets.EC2_USER }}/.env -f /home/${{ secrets.EC2_USER }}/docker/docker-compose-prod.yml up -d
clear: # 만들어진 artifact들을 성공여부에 상관없이 삭제하기 위한 jobs
runs-on: ubuntu-latest
needs: [build, deploy] # 모든 jobs가 실행되어야 함.
if: always() # 실패 여부와 상관없이 항상 실행되도록 설정.
steps:
- name: Delete Artifacts # Artifact 삭제 (env-artifact는 공개되면 절대 안되기 때문에 작업이 완료되면 바로 삭제해주기 위함이다.)
uses: geekyeggo/delete-artifact@v5
with:
name: |
*
간단하긴 하지만 주석 설명만 봐도 워크플로우를 이해하는데 어렵지 않을것이다.

잘 동작한다면 위 이미지를 확인할 수 있다. deploy의 성공여부나 스킵과 관계없이 clear jobs가 실행완료한 모습을 확인할 수 있다.
글이 너무 길어져서 AWS관련 설명은 다음글에서 이어서 하겠다.