Skip to content

Merge pull request #232 from 2026-snapy/main #68

Merge pull request #232 from 2026-snapy/main

Merge pull request #232 from 2026-snapy/main #68

Workflow file for this run

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