diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..68e0c06bf --- /dev/null +++ b/.dockerignore @@ -0,0 +1,37 @@ +node_modules +# Keep environment variables out of version control +.env +.env.test + +/generated/prisma +.DS_Store + +# 기타 파일 +erd/ +archive/ +http/ +infra/ +scripts/ +test/ +DockerImage.txt + +# Git +.git +.gitignore +.github + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Dependency +.npm +.yarn-cache + +# Build outputs +/dist +/build + +# Test coverage +coverage/ \ No newline at end of file diff --git a/.env.sample b/.env.sample index 704c432e7..b8c11b5d2 100644 --- a/.env.sample +++ b/.env.sample @@ -1,4 +1,7 @@ -DATABASE_URL="postgresql://아이디:비번@localhost:5432/db이름?schema=public" +# 도커용 +DATABASE_URL=postgresql://devuser:devpassword@localhost:5432/devdatabase?schema=public +# 실제 db용 +DATABASE_URL=postgresql://아이디:비번@localhost:5432/db이름?schema=public PORT=3000 CORS_ORIGIN=http://localhost:3000 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..d0186b6f5 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,94 @@ +name: Deploy to AWS EC2 + +on: + push: + branches: + - main + +env: + AWS_REGION: ap-northeast-2 # 서울 리전 + ECR_REPOSITORY: uju-api-sever # ECR 리포지토리 이름 + DOCKER_CONTAINER_NAME: 4-sprint-mission-server # EC2에서 실행할 컨테이너 이름 + +jobs: + build-and-deploy: + name: Build and Deploy to EC2 + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + IMAGE_URI=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker build --build-arg NODE_VERSION=22.17.1 -t $IMAGE_URI . + docker push $IMAGE_URI + echo "IMAGE_URI=$IMAGE_URI" >> $GITHUB_ENV + + - name: Get GitHub Actions Runner IP + id: get_ip + run: echo "ip=$(curl -s http://checkip.amazonaws.com)" >> $GITHUB_OUTPUT + + - name: Add Runner IP to EC2 Security Group + run: | + aws ec2 authorize-security-group-ingress \ + --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} \ + --protocol tcp \ + --port 22 \ + --cidr ${{ steps.get_ip.outputs.ip }}/32 + + - name: Get ECR Login Password + id: ecr_password + run: echo "ECR_PASSWORD=$(aws ecr get-login-password --region ${{ env.AWS_REGION }})" >> $GITHUB_ENV + + - name: Deploy to EC2 instance + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USERNAME }} + key: ${{ secrets.EC2_SSH_KEY }} + script: | + echo ${{ env.ECR_PASSWORD }} | docker login --username AWS --password-stdin ${{ steps.login-ecr.outputs.registry }} + + cd /home/ec2-user/4-sprint-mission + + docker stop ${{ env.DOCKER_CONTAINER_NAME }} || true + docker rm ${{ env.DOCKER_CONTAINER_NAME }} || true + + docker image prune -a -f + + docker pull ${{ env.IMAGE_URI }} + + docker run \ + -d \ + --name ${{ env.DOCKER_CONTAINER_NAME }} \ + --restart always \ + -p 3000:3000 \ + --env-file ./.env \ + -e DATABASE_URL='${{ secrets.DATABASE_URL }}' \ + ${{ env.IMAGE_URI }} + + - name: Remove Runner IP from EC2 Security Group + if: always() + run: | + aws ec2 revoke-security-group-ingress \ + --group-id ${{ secrets.EC2_SECURITY_GROUP_ID }} \ + --protocol tcp \ + --port 22 \ + --cidr ${{ steps.get_ip.outputs.ip }}/32 diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 000000000..647315271 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,68 @@ +name: PR test Check (DB 포함) + +on: pull_request + +jobs: + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:16.5 + env: + POSTGRES_USER: ci_user + POSTGRES_PASSWORD: ci_password + + POSTGRES_DB: test_db + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 5s + --health-timeout 5s + --health-retries 5 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install dependencies + run: npm ci + + - name: Run Integration Tests + run: | + echo "Waiting for PostgreSQL service to be fully ready..." + sleep 5 + + echo "Running Prisma migrate deploy..." + npx prisma migrate deploy + + echo "Running Prisma generate..." + npx prisma generate + + echo "Running npm tests..." + npm test && echo "✅ Test Passed" || (echo "❌ Test Failed" && exit 1) + env: + DATABASE_URL: postgres://ci_user:ci_password@localhost:5432/test_db?schema=public + DB_HOST: localhost + DB_PORT: 5432 + DB_USER: ci_user + DB_PASSWORD: ci_password + DB_NAME: test_db + + JWT_ACCESS_TOKEN_SECRET: 'ci_test_access_token_secret_123' + JWT_REFRESH_TOKEN_SECRET: 'ci_test_refresh_token_secret_456' + + CLOUDINARY_CLOUD_NAME: 'ci-test-cloud' + CLOUDINARY_API_KEY: '123456789012345' + CLOUDINARY_API_SECRET: 'ci_test_cloudinary_secret_key' + + AWS_S3_BUCKET_NAME: 'ci-test-bucket' + AWS_S3_REGION: 'us-east-1' + AWS_ACCESS_KEY_ID: 'ASIA_CI_TEST_KEY' + AWS_SECRET_ACCESS_KEY: 'ci_test_aws_secret_key_abcdef' diff --git a/.gitignore b/.gitignore index 8b0ca50d6..69046bb0a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,9 +12,6 @@ yarn-debug.log* yarn-error.log* # Dependency -/node_modules -/frontend/node_modules -/backend/node_modules .npm .yarn-cache diff --git a/DockerImage.txt b/DockerImage.txt new file mode 100644 index 000000000..3d7ef01a3 --- /dev/null +++ b/DockerImage.txt @@ -0,0 +1,27 @@ +도커 빌드 +docker image build \ + -t parkdaseul/uju-api:1.0.0-slim-express \ + --build-arg NODE_VERSION=22.17.1 \ + -f ./Dockerfile \ + --pull \ +. + +도커 허브 push +docker push parkdaseul/uju-api:1.0.0-express + +도커 컨테이너 마이그레이션 1회성 생성 +docker run \ + --rm \ + --name uju-api-migration \ + --env-file ./.env \ + parkdaseul/uju-api:1.0.0-slim-express \ + npx prisma migrate deploy + +도커 컨테이터 생성 +docker run \ + -d \ + --rm \ + --name uju-api-container \ + -p 3000:3000 \ + --env-file ./.env \ + parkdaseul/uju-api:1.0.0-slim-express \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..6b24368fd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# ---------------------------------------------------------------------- +# 1단계: 빌드 스테이지 (Builder Stage) +# ---------------------------------------------------------------------- +ARG NODE_VERSION= +FROM node:${NODE_VERSION}-slim AS builder + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +RUN apt-get update && \ + apt-get install -y openssl libssl-dev && \ + rm -rf /var/lib/apt/lists/* + +COPY . . +RUN npx prisma generate --schema=./prisma/schema.prisma +RUN npm run build + +# ---------------------------------------------------------------------- +# 2단계: 실행 스테이지 (Runtime Stage) +# ---------------------------------------------------------------------- +FROM node:${NODE_VERSION}-slim AS runner + +WORKDIR /app + +RUN apt-get update && \ + apt-get install -y openssl libssl-dev && \ + rm -rf /var/lib/apt/lists/* + +COPY package.json package-lock.json ./ +RUN npm ci --only=production + +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules/.prisma/client ./node_modules/.prisma/client +COPY --from=builder /app/prisma ./prisma + +ENV NODE_ENV=production +CMD ["node", "dist/server.js"] \ No newline at end of file diff --git a/README.md b/README.md index 8a0de210a..4f2790f2c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🚀 UJU API 서버 -10번째 스프린트 미션을 위해 제작된 API 서버입니다. 이 서버는 상품 및 게시글 관리를 위한 백엔드 서비스를 제공하며, 사용자 인증, 실시간 알림 등 다양한 기능을 포함하고 있습니다. +11번째 스프린트 미션을 위해 제작된 API 서버입니다. 이 서버는 상품 및 게시글 관리를 위한 백엔드 서비스를 제공하며, 사용자 인증, 실시간 알림 등 다양한 기능을 포함하고 있습니다. ## ✨ 주요 기능 @@ -24,6 +24,7 @@ - **실시간 통신:** Socket.io - **환경 변수 관리:** Dotenv - **코드 포맷팅:** Prettier +- **컨테이너:** Docker, Docker Compose ## 🚀 배포 @@ -81,6 +82,7 @@ - Node.js (v18 이상 권장) - npm - PostgreSQL 데이터베이스 +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) ### 설치 및 설정 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..ad4fc32db --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,37 @@ +version: '3.8' + +services: + db: + image: postgres:16.5 + container_name: my-local-postgres + restart: always + ports: + - '5432:5432' + environment: + POSTGRES_USER: devuser + POSTGRES_PASSWORD: devpassword + POSTGRES_DB: devdatabase + volumes: + - postgres-data:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB'] + interval: 5s + timeout: 5s + retries: 5 + + app: + build: . + container_name: my-app-container + ports: + - '3000:3000' + env_file: + - ./.env + volumes: + - .:/app + - /app/node_modules + depends_on: + db: + condition: service_healthy + +volumes: + postgres-data: