-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/16 OCI 서버 배포 및 Docker 기반 CI/CD 파이프라인 구축 #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
5937ed7
afb53a4
dc4b055
fa92e2d
210b44c
d145c3b
e2ceedb
78f7a85
b36b45c
35756fb
6303a24
3c73d7b
f60b4bd
22fdf1e
e8ef2fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # Dependencies | ||
| node_modules | ||
| .pnp | ||
| .pnp.js | ||
|
|
||
| # Testing | ||
| coverage | ||
| .nyc_output | ||
|
|
||
| # Next.js | ||
| .next | ||
| out | ||
| build | ||
| dist | ||
|
|
||
| # Misc | ||
| .DS_Store | ||
| *.pem | ||
| .vscode | ||
| .idea | ||
|
|
||
| # Debug | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
| pnpm-debug.log* | ||
|
|
||
| # Local env files | ||
| .env | ||
| .env.local | ||
| .env.development.local | ||
| .env.test.local | ||
| .env.production.local | ||
|
|
||
| # Vercel | ||
| .vercel | ||
|
|
||
| # Typescript | ||
| *.tsbuildinfo | ||
|
|
||
| # Git | ||
| .git | ||
| .gitignore | ||
|
|
||
| # Docker | ||
| Dockerfile | ||
| .dockerignore | ||
| docker-compose*.yml | ||
|
|
||
| # Documentation | ||
| *.md | ||
| docs |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| name: CI | ||
|
|
||
| # PR이 생성되거나 업데이트될 때 실행 | ||
| on: | ||
| pull_request: | ||
| branches: | ||
| - develop | ||
| - main | ||
| types: [opened, synchronize, reopened] | ||
|
|
||
| jobs: | ||
| # 코드 품질 검사 | ||
| quality-check: | ||
| name: Code Quality Check | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| # 전체 히스토리 (PR 비교용) | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '22' | ||
|
|
||
| - name: Install pnpm | ||
| uses: pnpm/action-setup@v2 | ||
| with: | ||
| version: latest | ||
|
|
||
| # 의존성 캐싱 | ||
| - name: Get pnpm store directory | ||
| shell: bash | ||
| run: | | ||
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | ||
|
|
||
| - uses: actions/cache@v4 | ||
| name: Setup pnpm cache | ||
| with: | ||
| path: ${{ env.STORE_PATH }} | ||
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-pnpm-store- | ||
|
|
||
| # 의존성 설치 | ||
| - name: Install dependencies | ||
| run: pnpm install --frozen-lockfile | ||
|
|
||
| - name: Type check | ||
| run: pnpm tsc --noEmit | ||
|
|
||
| - name: Lint code | ||
| run: pnpm lint | ||
|
|
||
| - name: Build test | ||
| run: pnpm build |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| name: Deploy to OCI | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - develop | ||
| # GitHub Actions 탭에서 수동으로 실행 가능 | ||
| workflow_dispatch: | ||
|
|
||
| # 워크플로우 전체에서 사용할 환경 변수 설정 | ||
| env: | ||
| # 사용할 컨테이너 레지스트리(GitHub Container Registry) | ||
| REGISTRY: ghcr.io | ||
| IMAGE_NAME: ${{ github.repository_owner }}/global-nomad | ||
|
|
||
| jobs: | ||
| # Job 1: Docker 이미지 빌드를 빌드하고 GHCR에 푸시 | ||
| build: | ||
| name: Build and Push Docker Image | ||
| runs-on: ubuntu-latest | ||
| # 해당 Job이 사용할 권한 설정 | ||
| permissions: | ||
| contents: read | ||
| # GHCR에 이미지를 쓰기(푸시) 위한 권한 | ||
| packages: write | ||
|
|
||
| # Job의 결과를 다른 Job에서 사용할 수 있도록 출력 설정 | ||
| outputs: | ||
| # 정확한 이미지 태그 전달 (SHA 기반) - 배포할 정확한 이미지 주소, 버전 확인을 위한 커밋 해시 | ||
| image-url: ${{ steps.image.outputs.image-url }} | ||
| git-sha: ${{ steps.vars.outputs.sha-short }} | ||
|
|
||
| steps: | ||
| # 코드 체크아웃 | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| # Docker Buildx 설정 | ||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| # GHCR 로그인 | ||
| # - secrets.GITHUB_TOKEN: 워크플로우 실행 시 GitHub이 자동으로 생성해주는 임시 토큰 | ||
| - name: Log in to GitHub Container Registry | ||
| uses: docker/login-action@v3 | ||
| with: | ||
| registry: ${{ env.REGISTRY }} | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| # 빌드에 사용할 변수 생성 (짧은 커밋 해시, 빌드 날짜) | ||
| - name: Generate build vars | ||
| id: vars | ||
| run: | | ||
| echo "sha-short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT | ||
| echo "build-date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT | ||
|
|
||
| # 도커 이미지 빌드 및 푸시 (불변 태그 사용) | ||
| - name: Build and push Docker image | ||
| id: build | ||
| uses: docker/build-push-action@v5 | ||
| with: | ||
| # 현재 디렉토리를 빌드 컨텍스트로 사용 | ||
| context: . | ||
| # 빌드 후 레지스트리에 푸시 | ||
| push: true | ||
| # 이미지에 여러 태그를 부여 | ||
| tags: | | ||
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop | ||
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop-${{ steps.vars.outputs.sha-short }} | ||
| # 이미지에 메타 데이터 라벨 추가 | ||
| labels: | | ||
| org.opencontainers.image.revision=${{ github.sha }} | ||
| org.opencontainers.image.created=${{ steps.vars.outputs.build-date }} | ||
| # GitHub Actions 캐시를 사용하여 빌드 속도 향상 | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
| # 빌드할 플랫폼 지정 | ||
| platforms: linux/amd64 | ||
|
|
||
| # Job으로 전달할 정확한 이미지 URL 출력으로 설정 | ||
| - name: Set image output | ||
| id: image | ||
| run: | | ||
| echo "image-url=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop-${{ steps.vars.outputs.sha-short }}" >> $GITHUB_OUTPUT | ||
|
|
||
| # Job 2: OCI 서버에 배포 | ||
| deploy: | ||
| name: Deploy to OCI Server | ||
| runs-on: ubuntu-latest | ||
| # 빌드 Job이 성공해야만 실행 | ||
| needs: build | ||
|
|
||
| steps: | ||
| - name: Setup SSH | ||
| run: | | ||
| mkdir -p ~/.ssh | ||
| echo "${{ secrets.OCI_SSH_KEY }}" > ~/.ssh/id_rsa | ||
| chmod 600 ~/.ssh/id_rsa | ||
| ssh-keyscan -t rsa ${{ secrets.OCI_HOST }} >> ~/.ssh/known_hosts | ||
|
|
||
| # 서버에 배포 스크립트 실행 | ||
| - name: Deploy to server | ||
| env: | ||
| IMAGE_URL: ${{ needs.build.outputs.image-url }} | ||
| GIT_SHA: ${{ needs.build.outputs.git-sha }} | ||
| run: | | ||
| ssh -i ~/.ssh/id_rsa ${{ secrets.OCI_USERNAME }}@${{ secrets.OCI_HOST }} << 'ENDSSH' | ||
| set -e | ||
|
|
||
| echo "📦 Starting deployment..." | ||
| echo "🏷️ Image: ${IMAGE_URL}" | ||
| echo "🔖 Version: ${GIT_SHA}" | ||
|
|
||
| # 프로젝트 디렉토리로 이동 | ||
| cd ~/projects/GlobalNomad | ||
|
|
||
| # 최신 코드 가져오기 | ||
| echo "🔄 Pulling latest code..." | ||
| git pull origin develop | ||
|
|
||
| # GHCR 로그인 (PAT 사용) | ||
| echo "🔐 Logging in to GHCR..." | ||
| echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin | ||
|
|
||
| # 현재 실행 중인 이미지 태그 저장 (롤백용) | ||
| echo "💾 Saving current version..." | ||
| CURRENT_IMAGE=$(docker-compose ps -q nextjs | xargs docker inspect -f '{{.Config.Image}}' 2>/dev/null || echo "none") | ||
| echo "Current: $CURRENT_IMAGE" > ~/deployments/previous-version.txt | ||
|
|
||
| # 기존 컨테이너 중지 | ||
| echo "🛑 Stopping existing containers..." | ||
| docker-compose down || true | ||
|
|
||
| # PM2 정리(환경 테스트를 하기 위해 사용했던 이전 배포 방식 정리 ) | ||
| pm2 stop global-nomad || true | ||
| pm2 delete global-nomad || true | ||
|
|
||
| # 최신 이미지 pull (정확한 버전) | ||
| echo "📥 Pulling image: ${IMAGE_URL}" | ||
| docker pull ${IMAGE_URL} | ||
|
|
||
| # 환경 변수로 이미지 지정하여 실행 | ||
| echo "🚀 Starting containers..." | ||
| export NEXTJS_IMAGE="${IMAGE_URL}" | ||
| docker-compose up -d | ||
|
|
||
| # 헬스 체크 | ||
| echo "❤️ Health check..." | ||
| for i in {1..30}; do | ||
| if curl -f http://localhost:3000 > /dev/null 2>&1; then | ||
| echo "✅ Service is healthy!" | ||
| break | ||
| fi | ||
| echo "⏳ Waiting for service... ($i/30)" | ||
| sleep 2 | ||
| done | ||
|
|
||
| # 최종 확인 | ||
| if ! curl -f http://localhost:3000 > /dev/null 2>&1; then | ||
| echo "❌ Service health check failed after 60 seconds!" | ||
| docker-compose logs --tail 50 nextjs | ||
| exit 1 | ||
| fi | ||
|
|
||
| # 배포 정보 기록 | ||
| echo "📝 Recording deployment..." | ||
| mkdir -p ~/deployments | ||
| echo "${IMAGE_URL}" > ~/deployments/current-version.txt | ||
| echo "$(date -u +'%Y-%m-%d %H:%M:%S UTC') - ${GIT_SHA}" >> ~/deployments/history.log | ||
|
|
||
| # 오래된 이미지 정리 (최근 3개만 유지) | ||
| echo "🧹 Cleaning up old images..." | ||
| docker images | grep global-nomad | tail -n +4 | awk '{print $3}' | xargs -r docker rmi || true | ||
|
|
||
| echo "✅ Deployment completed successfully!" | ||
| echo "📊 Deployed version: ${GIT_SHA}" | ||
| ENDSSH | ||
|
|
||
| # 배포 결과 알림 (Slack, Discord 등 추가 가능) | ||
| - name: Notify deployment status | ||
| if: always() | ||
| run: | | ||
| if [ ${{ job.status }} == 'success' ]; then | ||
| echo "✅ 배포 성공!" | ||
| echo "🏷️ Image: ${{ needs.build.outputs.image-url }}" | ||
| echo "🔖 Version: ${{ needs.build.outputs.git-sha }}" | ||
| else | ||
| echo "❌ 배포 실패!" | ||
| fi | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,67 @@ | ||||||||||||||
| # 1단계: 의존성 설치 | ||||||||||||||
| FROM node:22-alpine AS deps | ||||||||||||||
| RUN apk add --no-cache libc6-compat | ||||||||||||||
|
|
||||||||||||||
| WORKDIR /app | ||||||||||||||
|
|
||||||||||||||
| # pnpm 설치 | ||||||||||||||
| RUN corepack enable | ||||||||||||||
| RUN corepack prepare pnpm@latest --activate | ||||||||||||||
|
|
||||||||||||||
| # 의존성 파일 복사 | ||||||||||||||
| COPY package.json pnpm-lock.yaml ./ | ||||||||||||||
|
|
||||||||||||||
| # 의존성 설치: lock 파일과 정확히 일치하는 버전만 설치 | ||||||||||||||
| RUN pnpm install --frozen-lockfile | ||||||||||||||
|
|
||||||||||||||
| # 2단계: 빌드 | ||||||||||||||
| FROM node:22-alpine AS builder | ||||||||||||||
| RUN apk add --no-cache libc6-compat | ||||||||||||||
|
|
||||||||||||||
| WORKDIR /app | ||||||||||||||
|
|
||||||||||||||
| # pnpm 설치 | ||||||||||||||
| RUN corepack enable | ||||||||||||||
| RUN corepack prepare pnpm@latest --activate | ||||||||||||||
|
|
||||||||||||||
| # 의존성 복사 | ||||||||||||||
| COPY --from=deps /app/node_modules ./node_modules | ||||||||||||||
| COPY . . | ||||||||||||||
|
|
||||||||||||||
| # Next.js 빌드 | ||||||||||||||
| ENV NEXT_TELEMETRY_DISABLED 1 | ||||||||||||||
| RUN pnpm build | ||||||||||||||
|
|
||||||||||||||
| # 3단계: 실행 | ||||||||||||||
| FROM node:22-alpine AS runner | ||||||||||||||
| RUN apk add --no-cache libc6-compat | ||||||||||||||
|
|
||||||||||||||
| WORKDIR /app | ||||||||||||||
|
|
||||||||||||||
| # 프로덕션 환경 설정 | ||||||||||||||
| ENV NODE_ENV production | ||||||||||||||
| ENV NEXT_TELEMETRY_DISABLED 1 | ||||||||||||||
|
|
||||||||||||||
| # 시스템 사용자 생성 | ||||||||||||||
| RUN addgroup --system --gid 1001 nodejs | ||||||||||||||
| RUN adduser --system --uid 1001 nextjs | ||||||||||||||
|
|
||||||||||||||
|
Comment on lines
+45
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 사용자·그룹 ID 불일치 가능성
-RUN addgroup --system --gid 1001 nodejs
-RUN adduser --system --uid 1001 nextjs
+RUN addgroup --system --gid 1001 nodejs \
+ && adduser --system --uid 1001 --ingroup nodejs nextjs📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| # public 폴더 복사 (정적 파일) | ||||||||||||||
| COPY --from=builder /app/public ./public | ||||||||||||||
|
|
||||||||||||||
| # standalone 폴더 복사 | ||||||||||||||
| COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ | ||||||||||||||
| COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static | ||||||||||||||
|
|
||||||||||||||
| # 사용자 전환 | ||||||||||||||
| USER nextjs | ||||||||||||||
|
|
||||||||||||||
| # 포트 설정 | ||||||||||||||
| EXPOSE 3000 | ||||||||||||||
|
|
||||||||||||||
| # 환경 변수 설정 | ||||||||||||||
| ENV PORT 3000 | ||||||||||||||
| ENV HOSTNAME "0.0.0.0" | ||||||||||||||
|
|
||||||||||||||
| # 실행 명령 | ||||||||||||||
| CMD ["node", "server.js"] | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
원격 SSH 스크립트에서
IMAGE_URL,GIT_SHA가 비어 있습니다env:로 설정한 변수는 로컬 셸에만 주입되며,ssh <<'ENDSSH'는 작은따옴표 덕분에 로컬 확장을 막아 원격 측에는 전달되지 않습니다.결과적으로
docker pull ${IMAGE_URL}가 빈 문자열로 실행되어 실패합니다.다음 중 한 가지로 해결하세요.
SendEnv사용가장 간단한 수정 예시:
또는:
📝 Committable suggestion
🧰 Tools
🪛 YAMLlint (1.37.1)
[error] 114-114: trailing spaces
(trailing-spaces)
🤖 Prompt for AI Agents