Merge pull request #232 from 2026-snapy/main #68
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: SNAPY Deploy | |
| on: | |
| push: | |
| branches: [ prod ] | |
| concurrency: | |
| group: deploy-prod | |
| cancel-in-progress: true | |
| env: | |
| REMOTE_APP_DIR: /home/ec2-user/snapy-app | |
| jobs: | |
| build-and-push: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| image_tag: ${{ steps.meta.outputs.image_tag }} | |
| image_name: ${{ steps.meta.outputs.image_name }} | |
| steps: | |
| - name: 코드 체크아웃 | |
| uses: actions/checkout@v4 | |
| - name: Docker Buildx 설정 | |
| uses: docker/setup-buildx-action@v3 | |
| - name: GHCR 로그인 | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: 이미지 이름/태그 준비 | |
| id: meta | |
| run: | | |
| IMAGE_NAME=$(echo "ghcr.io/${{ github.repository }}" | tr '[:upper:]' '[:lower:]') | |
| SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) | |
| echo "image_name=${IMAGE_NAME}" >> "$GITHUB_OUTPUT" | |
| echo "image_tag=${SHORT_SHA}" >> "$GITHUB_OUTPUT" | |
| - name: Docker 이미지 빌드 & 푸시 | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| push: true | |
| tags: | | |
| ${{ steps.meta.outputs.image_name }}:latest | |
| ${{ steps.meta.outputs.image_name }}:${{ steps.meta.outputs.image_tag }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| deploy: | |
| needs: build-and-push | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: read | |
| steps: | |
| - name: 코드 체크아웃 | |
| uses: actions/checkout@v4 | |
| - name: docker-compose.yml 전송 | |
| uses: appleboy/[email protected] | |
| with: | |
| host: ${{ secrets.EC2_HOST }} | |
| username: ${{ secrets.EC2_USERNAME }} | |
| key: ${{ secrets.EC2_SSH_KEY }} | |
| source: "docker-compose.yml" | |
| target: ${{ env.REMOTE_APP_DIR }} | |
| - name: .env 생성 & 컨테이너 교체 | |
| uses: appleboy/[email protected] | |
| env: | |
| GHCR_USER: ${{ github.actor }} | |
| GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| DB_USERNAME: ${{ secrets.DB_USERNAME }} | |
| DB_PASSWORD: ${{ secrets.DB_PASSWORD }} | |
| JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }} | |
| S3_BUCKET: ${{ secrets.S3_BUCKET }} | |
| CLOUDFRONT_URL: ${{ secrets.CLOUDFRONT_URL }} | |
| IMAGE_NAME: ${{ needs.build-and-push.outputs.image_name }} | |
| IMAGE_TAG: ${{ needs.build-and-push.outputs.image_tag }} | |
| GRPC_SERVER_HOST: ${{ secrets.GRPC_SERVER_HOST }} | |
| GRPC_SERVER_PORT: ${{ secrets.GRPC_SERVER_PORT }} | |
| GOOGLE_OAUTH_WEB_CLIENT_ID: ${{ secrets.GOOGLE_OAUTH_WEB_CLIENT_ID }} | |
| GOOGLE_OAUTH_WEB_CLIENT_SECRET: ${{ secrets.GOOGLE_OAUTH_WEB_CLIENT_SECRET }} | |
| GOOGLE_OAUTH_WEB_REDIRECT_URI: ${{ secrets.GOOGLE_OAUTH_WEB_REDIRECT_URI }} | |
| GOOGLE_OAUTH_IOS_CLIENT_ID: ${{ secrets.GOOGLE_OAUTH_IOS_CLIENT_ID }} | |
| APPLE_OAUTH_TEAM_ID: ${{ secrets.APPLE_OAUTH_TEAM_ID }} | |
| APPLE_OAUTH_KEY_ID: ${{ secrets.APPLE_OAUTH_KEY_ID }} | |
| APPLE_OAUTH_PRIVATE_KEY: ${{ secrets.APPLE_OAUTH_PRIVATE_KEY }} | |
| APPLE_OAUTH_BUNDLE_ID: ${{ secrets.APPLE_OAUTH_BUNDLE_ID }} | |
| APPLE_OAUTH_SERVICE_ID: ${{ secrets.APPLE_OAUTH_SERVICE_ID }} | |
| APPLE_OAUTH_REDIRECT_URI: ${{ secrets.APPLE_OAUTH_REDIRECT_URI }} | |
| APNS_ENABLED: ${{ secrets.APNS_ENABLED }} | |
| APNS_PRODUCTION: ${{ secrets.APNS_PRODUCTION }} | |
| APNS_BUNDLE_ID: ${{ secrets.APNS_BUNDLE_ID }} | |
| FRONTEND_URL: ${{ secrets.FRONTEND_URL }} | |
| ALIGO_KEY: ${{ secrets.ALIGO_KEY }} | |
| ALIGO_USER_ID: ${{ secrets.ALIGO_USER_ID }} | |
| ALIGO_SENDER: ${{ secrets.ALIGO_SENDER }} | |
| ALIGO_TEST_MODE: ${{ secrets.ALIGO_TEST_MODE }} | |
| with: | |
| host: ${{ secrets.EC2_HOST }} | |
| username: ${{ secrets.EC2_USERNAME }} | |
| key: ${{ secrets.EC2_SSH_KEY }} | |
| envs: GHCR_USER,GHCR_TOKEN,DB_USERNAME,DB_PASSWORD,JWT_SECRET_KEY,S3_BUCKET,CLOUDFRONT_URL,IMAGE_NAME,IMAGE_TAG,GRPC_SERVER_HOST,GRPC_SERVER_PORT,GOOGLE_OAUTH_WEB_CLIENT_ID,GOOGLE_OAUTH_WEB_CLIENT_SECRET,GOOGLE_OAUTH_WEB_REDIRECT_URI,GOOGLE_OAUTH_IOS_CLIENT_ID,APPLE_OAUTH_TEAM_ID,APPLE_OAUTH_KEY_ID,APPLE_OAUTH_PRIVATE_KEY,APPLE_OAUTH_BUNDLE_ID,APPLE_OAUTH_SERVICE_ID,APPLE_OAUTH_REDIRECT_URI,APNS_ENABLED,APNS_PRODUCTION,APNS_BUNDLE_ID,FRONTEND_URL,ALIGO_KEY,ALIGO_USER_ID,ALIGO_SENDER,ALIGO_TEST_MODE | |
| script: | | |
| set -e | |
| cd ${{ env.REMOTE_APP_DIR }} | |
| # .env 파일 생성 (권한 600) | |
| umask 077 | |
| cat > .env <<EOF | |
| APP_IMAGE=${IMAGE_NAME}:${IMAGE_TAG} | |
| SPRING_DATASOURCE_USERNAME=${DB_USERNAME} | |
| SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD} | |
| JWT_SECRET_KEY=${JWT_SECRET_KEY} | |
| SNAPY_S3_BUCKET=${S3_BUCKET} | |
| SNAPY_CLOUDFRONT_URL=${CLOUDFRONT_URL} | |
| GRPC_SERVER_HOST=${GRPC_SERVER_HOST} | |
| GRPC_SERVER_PORT=${GRPC_SERVER_PORT} | |
| GOOGLE_OAUTH_WEB_CLIENT_ID=${GOOGLE_OAUTH_WEB_CLIENT_ID} | |
| GOOGLE_OAUTH_WEB_CLIENT_SECRET=${GOOGLE_OAUTH_WEB_CLIENT_SECRET} | |
| GOOGLE_OAUTH_WEB_REDIRECT_URI=${GOOGLE_OAUTH_WEB_REDIRECT_URI} | |
| GOOGLE_OAUTH_IOS_CLIENT_ID=${GOOGLE_OAUTH_IOS_CLIENT_ID} | |
| APPLE_OAUTH_TEAM_ID=${APPLE_OAUTH_TEAM_ID} | |
| APPLE_OAUTH_KEY_ID=${APPLE_OAUTH_KEY_ID} | |
| APPLE_OAUTH_PRIVATE_KEY=${APPLE_OAUTH_PRIVATE_KEY} | |
| APPLE_OAUTH_BUNDLE_ID=${APPLE_OAUTH_BUNDLE_ID} | |
| APPLE_OAUTH_SERVICE_ID=${APPLE_OAUTH_SERVICE_ID} | |
| APPLE_OAUTH_REDIRECT_URI=${APPLE_OAUTH_REDIRECT_URI} | |
| APNS_ENABLED=${APNS_ENABLED} | |
| APNS_PRODUCTION=${APNS_PRODUCTION} | |
| APNS_BUNDLE_ID=${APNS_BUNDLE_ID} | |
| APNS_CERTIFICATE_PATH=/app/certs/apns.pem | |
| APNS_CERTIFICATE_PASSWORD= | |
| FRONTEND_URL=${FRONTEND_URL} | |
| ALIGO_KEY=${ALIGO_KEY} | |
| ALIGO_USER_ID=${ALIGO_USER_ID} | |
| ALIGO_SENDER=${ALIGO_SENDER} | |
| ALIGO_TEST_MODE=${ALIGO_TEST_MODE} | |
| EOF | |
| # GHCR 로그인 | |
| echo "${GHCR_TOKEN}" | docker login ghcr.io -u "${GHCR_USER}" --password-stdin | |
| # 이미지 pull & 컨테이너 교체 | |
| docker compose pull | |
| docker compose up -d --remove-orphans | |
| # 사용하지 않는 이미지 정리 | |
| docker image prune -f | |
| # GHCR 로그아웃 | |
| docker logout ghcr.io | |
| - name: Health check | |
| uses: appleboy/[email protected] | |
| with: | |
| host: ${{ secrets.EC2_HOST }} | |
| username: ${{ secrets.EC2_USERNAME }} | |
| key: ${{ secrets.EC2_SSH_KEY }} | |
| script: | | |
| echo "Health check 대기 중..." | |
| for i in $(seq 1 30); do | |
| STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/health || echo "000") | |
| if [ "$STATUS" = "200" ]; then | |
| echo "배포 성공 (health check 200 OK, ${i}초)" | |
| exit 0 | |
| fi | |
| sleep 1 | |
| done | |
| echo "Health check 실패 — 최근 컨테이너 로그:" | |
| docker logs --tail 100 snapy-app || true | |
| exit 1 |