Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .dockerignore
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
59 changes: 59 additions & 0 deletions .github/workflows/ci.yml
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
190 changes: 190 additions & 0 deletions .github/workflows/deploy.yml
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}"

# 프로젝트 디렉토리로 이동
Comment on lines +104 to +115
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

원격 SSH 스크립트에서 IMAGE_URL, GIT_SHA 가 비어 있습니다

env: 로 설정한 변수는 로컬 셸에만 주입되며,
ssh <<'ENDSSH' 는 작은따옴표 덕분에 로컬 확장을 막아 원격 측에는 전달되지 않습니다.
결과적으로 docker pull ${IMAGE_URL} 가 빈 문자열로 실행되어 실패합니다.

다음 중 한 가지로 해결하세요.

  1. 여기-문을 큰따옴표로 변경하고 변수를 로컬에서 치환
  2. SSH SendEnv 사용
  3. echo/EXPORT 방식으로 값 삽입

가장 간단한 수정 예시:

-          ssh -i ~/.ssh/id_rsa ${{ secrets.OCI_USERNAME }}@${{ secrets.OCI_HOST }} << 'ENDSSH'
+          ssh -i ~/.ssh/id_rsa ${{ secrets.OCI_USERNAME }}@${{ secrets.OCI_HOST }} << ENDSSH

또는:

-          ssh -i ~/.ssh/id_rsa ...
+          ssh -i ~/.ssh/id_rsa -o SendEnv=IMAGE_URL -o SendEnv=GIT_SHA ...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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}"
# 프로젝트 디렉토리로 이동
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'
+ 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}"
# 프로젝트 디렉토리로 이동
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 114-114: trailing spaces

(trailing-spaces)

🤖 Prompt for AI Agents
In .github/workflows/deploy.yml around lines 104 to 115, the environment
variables IMAGE_URL and GIT_SHA set under env are not passed into the remote SSH
session because the here-document uses single quotes, preventing variable
expansion. To fix this, change the here-document delimiter from 'ENDSSH' to
ENDSSH without quotes so that the variables are expanded locally before being
sent to the remote shell. This ensures IMAGE_URL and GIT_SHA have their intended
values during the remote commands.

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
67 changes: 67 additions & 0 deletions Dockerfile
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

사용자·그룹 ID 불일치 가능성

addgroup --system --gid 1001 nodejs 로 그룹을 만들었지만 adduser 시 그룹을 지정하지 않아
nextjs 사용자가 기본 그룹(동일 이름)으로 생성됩니다. 아래처럼 명시적으로 연결해 주세요.

-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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 시스템 사용자 생성
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
🤖 Prompt for AI Agents
In the Dockerfile at lines 45 to 48, the group 'nodejs' is created with GID
1001, but the user 'nextjs' is added without specifying this group, causing a
mismatch. Fix this by explicitly assigning the 'nodejs' group to the 'nextjs'
user during creation using the appropriate adduser option to link the user to
the 'nodejs' group.

# 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"]
Loading
Loading