Skip to content

Commit fb0e1c1

Browse files
authored
Merge pull request #36 from codeit-2team/feature/16
Feature/16 OCI 서버 배포 및 Docker 기반 CI/CD 파이프라인 구축
2 parents a665244 + e8ef2fb commit fb0e1c1

File tree

11 files changed

+667
-2
lines changed

11 files changed

+667
-2
lines changed

.dockerignore

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Dependencies
2+
node_modules
3+
.pnp
4+
.pnp.js
5+
6+
# Testing
7+
coverage
8+
.nyc_output
9+
10+
# Next.js
11+
.next
12+
out
13+
build
14+
dist
15+
16+
# Misc
17+
.DS_Store
18+
*.pem
19+
.vscode
20+
.idea
21+
22+
# Debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
pnpm-debug.log*
27+
28+
# Local env files
29+
.env
30+
.env.local
31+
.env.development.local
32+
.env.test.local
33+
.env.production.local
34+
35+
# Vercel
36+
.vercel
37+
38+
# Typescript
39+
*.tsbuildinfo
40+
41+
# Git
42+
.git
43+
.gitignore
44+
45+
# Docker
46+
Dockerfile
47+
.dockerignore
48+
docker-compose*.yml
49+
50+
# Documentation
51+
*.md
52+
docs

.github/workflows/ci.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: CI
2+
3+
# PR이 생성되거나 업데이트될 때 실행
4+
on:
5+
pull_request:
6+
branches:
7+
- develop
8+
- main
9+
types: [opened, synchronize, reopened]
10+
11+
jobs:
12+
# 코드 품질 검사
13+
quality-check:
14+
name: Code Quality Check
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
with:
21+
# 전체 히스토리 (PR 비교용)
22+
fetch-depth: 0
23+
24+
- name: Setup Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: '22'
28+
29+
- name: Install pnpm
30+
uses: pnpm/action-setup@v2
31+
with:
32+
version: latest
33+
34+
# 의존성 캐싱
35+
- name: Get pnpm store directory
36+
shell: bash
37+
run: |
38+
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
39+
40+
- uses: actions/cache@v4
41+
name: Setup pnpm cache
42+
with:
43+
path: ${{ env.STORE_PATH }}
44+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
45+
restore-keys: |
46+
${{ runner.os }}-pnpm-store-
47+
48+
# 의존성 설치
49+
- name: Install dependencies
50+
run: pnpm install --frozen-lockfile
51+
52+
- name: Type check
53+
run: pnpm tsc --noEmit
54+
55+
56+
- name: Lint code
57+
# 린트오류가 발생해도 CI가 실패하지 않도록 임시 설정 - 메인 배포때 옵션 제거 예정
58+
continue-on-error: true
59+
run: pnpm lint
60+
61+
- name: Build test
62+
run: pnpm build

.github/workflows/deploy.yml

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
name: Deploy to OCI
2+
3+
on:
4+
push:
5+
branches:
6+
- develop
7+
# GitHub Actions 탭에서 수동으로 실행 가능
8+
workflow_dispatch:
9+
10+
# 워크플로우 전체에서 사용할 환경 변수 설정
11+
env:
12+
# 사용할 컨테이너 레지스트리(GitHub Container Registry)
13+
REGISTRY: ghcr.io
14+
IMAGE_NAME: ${{ github.repository_owner }}/global-nomad
15+
16+
jobs:
17+
# Job 1: Docker 이미지 빌드를 빌드하고 GHCR에 푸시
18+
build:
19+
name: Build and Push Docker Image
20+
runs-on: ubuntu-latest
21+
# 해당 Job이 사용할 권한 설정
22+
permissions:
23+
contents: read
24+
# GHCR에 이미지를 쓰기(푸시) 위한 권한
25+
packages: write
26+
27+
# Job의 결과를 다른 Job에서 사용할 수 있도록 출력 설정
28+
outputs:
29+
# 정확한 이미지 태그 전달 (SHA 기반) - 배포할 정확한 이미지 주소, 버전 확인을 위한 커밋 해시
30+
image-url: ${{ steps.image.outputs.image-url }}
31+
git-sha: ${{ steps.vars.outputs.sha-short }}
32+
33+
steps:
34+
# 코드 체크아웃
35+
- name: Checkout code
36+
uses: actions/checkout@v4
37+
38+
# Docker Buildx 설정
39+
- name: Set up Docker Buildx
40+
uses: docker/setup-buildx-action@v3
41+
42+
# GHCR 로그인
43+
# - secrets.GITHUB_TOKEN: 워크플로우 실행 시 GitHub이 자동으로 생성해주는 임시 토큰
44+
- name: Log in to GitHub Container Registry
45+
uses: docker/login-action@v3
46+
with:
47+
registry: ${{ env.REGISTRY }}
48+
username: ${{ github.actor }}
49+
password: ${{ secrets.GITHUB_TOKEN }}
50+
51+
# 빌드에 사용할 변수 생성 (짧은 커밋 해시, 빌드 날짜)
52+
- name: Generate build vars
53+
id: vars
54+
run: |
55+
echo "sha-short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
56+
echo "build-date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
57+
58+
# 도커 이미지 빌드 및 푸시 (불변 태그 사용)
59+
- name: Build and push Docker image
60+
id: build
61+
uses: docker/build-push-action@v5
62+
with:
63+
# 현재 디렉토리를 빌드 컨텍스트로 사용
64+
context: .
65+
# 빌드 후 레지스트리에 푸시
66+
push: true
67+
# 이미지에 여러 태그를 부여
68+
tags: |
69+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop
70+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop-${{ steps.vars.outputs.sha-short }}
71+
# 이미지에 메타 데이터 라벨 추가
72+
labels: |
73+
org.opencontainers.image.revision=${{ github.sha }}
74+
org.opencontainers.image.created=${{ steps.vars.outputs.build-date }}
75+
# GitHub Actions 캐시를 사용하여 빌드 속도 향상
76+
cache-from: type=gha
77+
cache-to: type=gha,mode=max
78+
# 빌드할 플랫폼 지정
79+
platforms: linux/amd64
80+
81+
# Job으로 전달할 정확한 이미지 URL 출력으로 설정
82+
- name: Set image output
83+
id: image
84+
run: |
85+
echo "image-url=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop-${{ steps.vars.outputs.sha-short }}" >> $GITHUB_OUTPUT
86+
87+
# Job 2: OCI 서버에 배포
88+
deploy:
89+
name: Deploy to OCI Server
90+
runs-on: ubuntu-latest
91+
# 빌드 Job이 성공해야만 실행
92+
needs: build
93+
94+
steps:
95+
- name: Setup SSH
96+
run: |
97+
mkdir -p ~/.ssh
98+
echo "${{ secrets.OCI_SSH_KEY }}" > ~/.ssh/id_rsa
99+
chmod 600 ~/.ssh/id_rsa
100+
ssh-keyscan -t rsa ${{ secrets.OCI_HOST }} >> ~/.ssh/known_hosts
101+
102+
# 서버에 배포 스크립트 실행
103+
- name: Deploy to server
104+
env:
105+
IMAGE_URL: ${{ needs.build.outputs.image-url }}
106+
GIT_SHA: ${{ needs.build.outputs.git-sha }}
107+
run: |
108+
ssh -i ~/.ssh/id_rsa ${{ secrets.OCI_USERNAME }}@${{ secrets.OCI_HOST }} << 'ENDSSH'
109+
set -e
110+
111+
echo "📦 Starting deployment..."
112+
echo "🏷️ Image: ${IMAGE_URL}"
113+
echo "🔖 Version: ${GIT_SHA}"
114+
115+
# 프로젝트 디렉토리로 이동
116+
cd ~/projects/GlobalNomad
117+
118+
# 최신 코드 가져오기
119+
echo "🔄 Pulling latest code..."
120+
git pull origin develop
121+
122+
# GHCR 로그인 (PAT 사용)
123+
echo "🔐 Logging in to GHCR..."
124+
echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
125+
126+
# 현재 실행 중인 이미지 태그 저장 (롤백용)
127+
echo "💾 Saving current version..."
128+
CURRENT_IMAGE=$(docker-compose ps -q nextjs | xargs docker inspect -f '{{.Config.Image}}' 2>/dev/null || echo "none")
129+
echo "Current: $CURRENT_IMAGE" > ~/deployments/previous-version.txt
130+
131+
# 기존 컨테이너 중지
132+
echo "🛑 Stopping existing containers..."
133+
docker-compose down || true
134+
135+
# PM2 정리(환경 테스트를 하기 위해 사용했던 이전 배포 방식 정리 )
136+
pm2 stop global-nomad || true
137+
pm2 delete global-nomad || true
138+
139+
# 최신 이미지 pull (정확한 버전)
140+
echo "📥 Pulling image: ${IMAGE_URL}"
141+
docker pull ${IMAGE_URL}
142+
143+
# 환경 변수로 이미지 지정하여 실행
144+
echo "🚀 Starting containers..."
145+
export NEXTJS_IMAGE="${IMAGE_URL}"
146+
docker-compose up -d
147+
148+
# 헬스 체크
149+
echo "❤️ Health check..."
150+
for i in {1..30}; do
151+
if curl -f http://localhost:3000 > /dev/null 2>&1; then
152+
echo "✅ Service is healthy!"
153+
break
154+
fi
155+
echo "⏳ Waiting for service... ($i/30)"
156+
sleep 2
157+
done
158+
159+
# 최종 확인
160+
if ! curl -f http://localhost:3000 > /dev/null 2>&1; then
161+
echo "❌ Service health check failed after 60 seconds!"
162+
docker-compose logs --tail 50 nextjs
163+
exit 1
164+
fi
165+
166+
# 배포 정보 기록
167+
echo "📝 Recording deployment..."
168+
mkdir -p ~/deployments
169+
echo "${IMAGE_URL}" > ~/deployments/current-version.txt
170+
echo "$(date -u +'%Y-%m-%d %H:%M:%S UTC') - ${GIT_SHA}" >> ~/deployments/history.log
171+
172+
# 오래된 이미지 정리 (최근 3개만 유지)
173+
echo "🧹 Cleaning up old images..."
174+
docker images | grep global-nomad | tail -n +4 | awk '{print $3}' | xargs -r docker rmi || true
175+
176+
echo "✅ Deployment completed successfully!"
177+
echo "📊 Deployed version: ${GIT_SHA}"
178+
ENDSSH
179+
180+
# 배포 결과 알림 (Slack, Discord 등 추가 가능)
181+
- name: Notify deployment status
182+
if: always()
183+
run: |
184+
if [ ${{ job.status }} == 'success' ]; then
185+
echo "✅ 배포 성공!"
186+
echo "🏷️ Image: ${{ needs.build.outputs.image-url }}"
187+
echo "🔖 Version: ${{ needs.build.outputs.git-sha }}"
188+
else
189+
echo "❌ 배포 실패!"
190+
fi

Dockerfile

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# 1단계: 의존성 설치
2+
FROM node:22-alpine AS deps
3+
RUN apk add --no-cache libc6-compat
4+
5+
WORKDIR /app
6+
7+
# pnpm 설치
8+
RUN corepack enable
9+
RUN corepack prepare pnpm@latest --activate
10+
11+
# 의존성 파일 복사
12+
COPY package.json pnpm-lock.yaml ./
13+
14+
# 의존성 설치: lock 파일과 정확히 일치하는 버전만 설치
15+
RUN pnpm install --frozen-lockfile
16+
17+
# 2단계: 빌드
18+
FROM node:22-alpine AS builder
19+
RUN apk add --no-cache libc6-compat
20+
21+
WORKDIR /app
22+
23+
# pnpm 설치
24+
RUN corepack enable
25+
RUN corepack prepare pnpm@latest --activate
26+
27+
# 의존성 복사
28+
COPY --from=deps /app/node_modules ./node_modules
29+
COPY . .
30+
31+
# Next.js 빌드
32+
ENV NEXT_TELEMETRY_DISABLED 1
33+
RUN pnpm build
34+
35+
# 3단계: 실행
36+
FROM node:22-alpine AS runner
37+
RUN apk add --no-cache libc6-compat
38+
39+
WORKDIR /app
40+
41+
# 프로덕션 환경 설정
42+
ENV NODE_ENV production
43+
ENV NEXT_TELEMETRY_DISABLED 1
44+
45+
# 시스템 사용자 생성
46+
RUN addgroup --system --gid 1001 nodejs
47+
RUN adduser --system --uid 1001 nextjs
48+
49+
# public 폴더 복사 (정적 파일)
50+
COPY --from=builder /app/public ./public
51+
52+
# standalone 폴더 복사
53+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
54+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
55+
56+
# 사용자 전환
57+
USER nextjs
58+
59+
# 포트 설정
60+
EXPOSE 3000
61+
62+
# 환경 변수 설정
63+
ENV PORT 3000
64+
ENV HOSTNAME "0.0.0.0"
65+
66+
# 실행 명령
67+
CMD ["node", "server.js"]

0 commit comments

Comments
 (0)