@@ -9,136 +9,69 @@ concurrency:
99 group : deploy-develop
1010 cancel-in-progress : true
1111
12+ permissions :
13+ contents : read
14+ packages : write
15+
1216jobs :
1317 build-and-deploy :
1418 runs-on : ubuntu-latest
1519 env :
16- APP_DIR : /srv/senifit-front
17- RELEASE_NAME : ${{ github.sha }}
18- NODE_ENV : production
19- NEXT_PUBLIC_API_BASE : ${{ secrets.NEXT_PUBLIC_API_BASE }}
20- NEXT_PUBLIC_API_URL : ${{ secrets.NEXT_PUBLIC_API_URL }}
21- API_PREFIX : ${{ secrets.API_PREFIX }}
22- NEXT_PUBLIC_SITE_URL : ${{ secrets.NEXT_PUBLIC_SITE_URL }}
20+ IMAGE_NAME : ghcr.io/team-senifit/web
21+ IMAGE_TAG : ${{ github.sha }}
2322
2423 steps :
2524 - name : Checkout
2625 uses : actions/checkout@v4
2726
28- - name : Setup Node
29- uses : actions/setup-node@v4
27+ - name : Log in to GHCR
28+ uses : docker/login-action@v3
3029 with :
31- node-version : " 22.x"
32- cache : " npm"
33-
34- - name : Install deps
35- run : npm ci
36-
37- - name : Build
38- run : npm run build
39-
40- # next는 dependencies에 있어야 함. devDeps는 제거해 산출물 슬림화
41- - name : Prune devDependencies
42- run : npm prune --omit=dev
43-
44- - name : Create deploy package
45- run : |
46- set -euo pipefail
47- mkdir -p deploy
48- # 실행에 필요한 산출물만 포함 (.next, node_modules, 정적파일, 설정)
49- cp -r .next node_modules package.json package-lock.json public next.config.* deploy/ 2>/dev/null || true
50- tar -C deploy -czf release.tgz .
30+ registry : ghcr.io
31+ username : ${{ github.repository_owner }}
32+ password : ${{ secrets.GITHUB_TOKEN }}
5133
52- - name : Add SSH key
53- uses : webfactory/ssh-agent@v0.9.0
34+ - name : Build and push image
35+ uses : docker/build-push-action@v6
5436 with :
55- ssh-private-key : ${{ secrets.EC2_SSH_KEY }}
56-
57- - name : Known hosts
58- run : |
59- set -euo pipefail
60- mkdir -p ~/.ssh
61- ssh-keyscan -H "${{ secrets.EC2_HOST }}" >> ~/.ssh/known_hosts
62-
63- - name : Remote prep (cleanup & prepare)
64- env :
65- EC2_USER : ${{ secrets.EC2_USER }}
66- EC2_HOST : ${{ secrets.EC2_HOST }}
67- run : |
68- ssh "$EC2_USER@$EC2_HOST" bash -se <<'PREP'
69- set -euo pipefail
70-
71- echo "==== System Health Check ===="
72- df -h /
73- free -m
74-
75- APP_DIR="/srv/senifit-front"
76- echo "==== Cleanup Old Releases ===="
77- # 폴더가 없으면 생성
78- if [ ! -d "$APP_DIR/releases" ]; then
79- sudo mkdir -p "$APP_DIR/releases" "$APP_DIR/shared"
80- sudo chown $USER:$USER "$APP_DIR" "$APP_DIR/releases" "$APP_DIR/shared"
81- fi
82-
83- cd "$APP_DIR/releases"
84-
85- # 최신 3개 제외 나머지 리스트업
86- OLD_RELEASES=$(ls -1t | tail -n +4)
87-
88- if [ -z "$OLD_RELEASES" ]; then
89- echo "No old releases to clean up."
90- else
91- for rel in $OLD_RELEASES; do
92- echo "Removing old release: $rel"
93- # current와 중복 여부 체크 (안전을 위해)
94- CURR="$(readlink -f "$APP_DIR/current" || true)"
95- REL_PATH="$(readlink -f "$rel")"
96- if [ "$REL_PATH" != "$CURR" ]; then
97- rm -rf "$rel" || echo "Warning: Failed to remove $rel"
98- else
99- echo "Skipping current release: $rel"
100- fi
101- done
102- fi
103-
104- # current가 아닌 릴리즈 내부의 캐시 정리
105- echo "==== Optimizing Passive Releases ===="
106- CURR="$(readlink -f "$APP_DIR/current" || true)"
107- for d in "$APP_DIR"/releases/*; do
108- [ -d "$d" ] || continue
109- if [ "$(readlink -f "$d")" != "$CURR" ]; then
110- echo "Cleaning cache/modules in inactive release: $(basename "$d")"
111- rm -rf "$d/.next/cache" "$d/node_modules" || true
112- fi
113- done
114- PREP
37+ context : .
38+ file : Dockerfile
39+ push : true
40+ tags : ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
41+ build-args : |
42+ NEXT_PUBLIC_SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }}
43+ NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }}
44+ SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }}
45+
46+ - name : Configure AWS credentials
47+ uses : aws-actions/configure-aws-credentials@v4
48+ with :
49+ aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }}
50+ aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }}
51+ aws-region : ${{ secrets.AWS_REGION }}
11552
116- - name : Remote deploy (stream extract, swap, restart)
53+ - name : Deploy via SSM
11754 env :
118- EC2_USER : ${{ secrets.EC2_USER }}
119- EC2_HOST : ${{ secrets.EC2_HOST }}
120- RELEASE_NAME : ${{ env.RELEASE_NAME }}
121- SERVICE_USER : ${{ secrets.SERVICE_USER }}
55+ SSM_INSTANCE_ID : ${{ secrets.SSM_INSTANCE_ID }}
56+ IMAGE_TAG : ${{ env.IMAGE_TAG }}
57+ IMAGE_NAME : ${{ env.IMAGE_NAME }}
12258 run : |
12359 set -euo pipefail
124- APP_DIR="/srv/senifit-front"
125- REL="${RELEASE_NAME}"
126- SVC="${SERVICE_USER:-ubuntu}"
127-
128- cat release.tgz | ssh -o StrictHostKeyChecking=yes "$EC2_USER@$EC2_HOST" \
129- "set -euo pipefail; APP_DIR='$APP_DIR'; REL='$REL'; \
130- sudo mkdir -p \"\$APP_DIR/releases/\$REL\"; \
131- sudo chown \$USER:\$USER \"\$APP_DIR/releases/\$REL\"; \
132- tar -xzf - -C \"\$APP_DIR/releases/\$REL\""
133-
134- ssh -o StrictHostKeyChecking=yes "$EC2_USER@$EC2_HOST" \
135- "set -euo pipefail; APP_DIR='$APP_DIR'; REL='$REL'; SVC='$SVC'; \
136- echo '==== Finalizing Release ===='; \
137- ln -sfn \"\$APP_DIR/releases/\$REL\" \"\$APP_DIR/current\"; \
138- sudo systemctl daemon-reload; \
139- sudo systemctl restart senifit-front"
140-
141- - name : Public healthcheck (non-blocking)
142- continue-on-error : true
143- run : |
144- curl -fsS -o /dev/null https://senifit.co.kr || echo "::warning::Public healthcheck failed"
60+ PARAMS=$(jq -nc --arg tag "$IMAGE_TAG" \
61+ '{commands:["cd /srv","export IMAGE_TAG="+$tag,"docker compose down","docker compose pull","docker compose up -d"]}')
62+
63+ COMMAND_ID=$(aws ssm send-command \
64+ --instance-ids "$SSM_INSTANCE_ID" \
65+ --document-name "AWS-RunShellScript" \
66+ --comment "Deploy web image $IMAGE_NAME:$IMAGE_TAG" \
67+ --parameters "$PARAMS" \
68+ --query "Command.CommandId" \
69+ --output text)
70+
71+ aws ssm wait command-executed \
72+ --command-id "$COMMAND_ID" \
73+ --instance-id "$SSM_INSTANCE_ID"
74+
75+ aws ssm get-command-invocation \
76+ --command-id "$COMMAND_ID" \
77+ --instance-id "$SSM_INSTANCE_ID"
0 commit comments