"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > NextJ와 NestJ를 단일 애플리케이션으로 배포

NextJ와 NestJ를 단일 애플리케이션으로 배포

2024-11-08에 게시됨
검색:617

안녕하세요! 단일 호스트에서 원활하게 작동하도록 NestJS를 구성하는 방법을 공유하게 되어 기쁩니다. 하지만 먼저 오랫동안 프런트엔드와 백엔드를 모두 관리하기 위해 이 설정이 제가 최고의 선택이었던 이유를 설명하겠습니다.

Next.js는 새로운 프로젝트를 시작할 때 강력한 힘을 발휘합니다. 기본 제공 라우팅, 서버 측 렌더링(SSR), 캐싱과 같은 기능이 포함되어 있어 원활한 실행에 도움이 됩니다. 또한 Next.js에는 자체 내부 API 기능이 있어 프레임워크 내에서 바로 캐싱 및 데이터 준비와 같은 작업을 관리할 수 있습니다. 즉, 인프라 설정에 시간을 덜 쓰고 앱 구축에 더 집중할 수 있습니다.

하지만 때로는 서버에 더 강력한 기능이 필요할 때가 있습니다. 이것이 바로 Nest.js가 개입하는 곳입니다. 이 프레임워크는 매우 강력하여 백엔드와 프런트엔드 사이의 미들웨어 업무를 처리할 수 있을 뿐만 아니라 그 자체로 강력한 백엔드 솔루션 역할을 할 수도 있습니다. 따라서 이 경우 NestJS는 Next.js에 추가되어 프런트엔드와 백엔드에 단일 프로그래밍 언어를 사용할 수 있게 해줍니다.

왜 단일 호스트인가요?

간단히 말하면 엄청나게 편리합니다. git pull과 docker-compose up -d만 사용하면 준비가 완료됩니다. CORS나 포트 저글링에 대해 걱정할 필요가 없습니다. 또한 배송 프로세스를 간소화하여 모든 것이 더욱 원활하고 효율적으로 실행되도록 합니다. 단점으로는 부하가 많이 걸리는 대규모 프로젝트에는 적합하지 않다는 점을 지적할 수 있습니다.

1. 먼저 저장소의 폴더 구조를 정의해 보겠습니다.

Deploy NextJs and NestJs as a single application

2. 서버용 도커 파일을 선언해 보겠습니다.

파일: ./docker-compose.yml

services:
    nginx:
        image: nginx:alpine
        ports:
            - "80:80"
        volumes:
            - "./docker/nginx/conf.d:/etc/nginx/conf.d"
        depends_on:
            - frontend
            - backend
        networks:
            - internal-network
            - external-network

    frontend:
        image: ${FRONTEND_IMAGE}
        restart: always
        networks:
            - internal-network

    backend:
        image: ${BACKEND_IMAGE}
        environment:
            NODE_ENV: ${NODE_ENV}
            POSTGRES_HOST: ${POSTGRES_HOST}
            POSTGRES_USER: ${POSTGRES_USER}
            POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
            POSTGRES_DB: ${POSTGRES_DB}
        depends_on:
            - postgres
        restart: always
        networks:
            - internal-network

    postgres:
        image: postgres:12.1-alpine
        container_name: postgres
        volumes:
            - "./docker/postgres:/var/lib/postgresql/data"
        environment:
            POSTGRES_USER: ${POSTGRES_USER}
            POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
            POSTGRES_DB: ${POSTGRES_DB}
        ports:
            - "5432:5432"

networks:
    internal-network:
        driver: bridge

    external-network:
        driver: bridge

간단히 말하면 엄청나게 편리합니다. git pull과 docker-compose up -d만 사용하면 준비가 완료됩니다. CORS나 포트 저글링에 대해 걱정할 필요가 없습니다. 또한 배송 프로세스를 간소화하여 모든 것이 더욱 원활하고 효율적으로 실행되도록 합니다. 단점으로는 부하가 많이 걸리는 대규모 프로젝트에는 적합하지 않다는 점을 지적할 수 있습니다.

3. 개발 모드를 위한 또 다른 도커 파일

개발 모드의 경우 백엔드와 프런트엔드를 위한 컨테이너 서비스가 필요하지 않습니다. 왜냐하면 로컬에서 실행하기 때문입니다.

파일: ./docker-compose.dev.yml

version: '3'

services:
    nginx:
        image: nginx:alpine
        ports:
            - "80:80"
        volumes:
            - "./docker/nginx/conf.d:/etc/nginx/conf.d"

    postgres:
        image: postgres:12.1-alpine
        container_name: postgres
        volumes:
            - "./docker/postgres:/var/lib/postgresql/data"
        environment:
            POSTGRES_USER: postgres
            POSTGRES_PASSWORD: postgres
            POSTGRES_DB: postgres
        ports:
            - "5432:5432"

4. 백엔드용 Docker 파일

파일: ./backend/Dockerfile

FROM node:18-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json package-lock.json ./
RUN  npm install

FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED 1

RUN npm run build

FROM node:18-alpine AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

RUN mkdir -p /app/backups && chown -R nextjs:nodejs /app/backups && chmod -R 777 /app/backups

USER nextjs

EXPOSE 3010

ENV PORT 3010

CMD ["node", "dist/src/main"]

## 5. Docker file for frontend
File: ./frontend/Dockerfile

FROM node:18-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json package-lock.json ./
RUN  npm install

FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED 1

RUN npm run build

FROM node:18-alpine AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["npm", "start"]

6. Ngnix 구성

이 단계에서는 Next.js 프런트엔드와 Nest.js 백엔드에 대한 역방향 프록시 역할을 하도록 Nginx를 구성합니다. Nginx 구성을 사용하면 프런트엔드와 백엔드 간에 요청을 원활하게 라우팅하는 동시에 동일한 호스트에서 요청을 처리할 수 있습니다.

파일: /docker/nginx/conf.d/default.conf

server {
    listen 80;

    location / {
        proxy_pass http://host.docker.internal:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /api {
        proxy_pass http://host.docker.internal:3010;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

이 구성은 포트 80에서 수신 대기하고 일반 트래픽을 포트 3000의 Next.js 프런트엔드로 라우팅하는 반면, /api에 대한 모든 요청은 포트 3010의 Nest.js 백엔드로 전달됩니다.

7. NestJ의 글로벌 프리픽스

동일한 호스트를 사용하므로 /apipath에서 NestJ를 사용할 수 있어야 합니다. 이를 위해서는 setGlobalPrefix — API가 필요합니다.

파일: ./backend/src/main.js

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, { cors: true  });
  app.setGlobalPrefix('api');
  await app.listen(3010);
}
bootstrap();

8. 프론트엔드

프런트엔드에는 구성이 필요하지 않지만 모든 서버 요청은 /api 경로를 기준으로 호출되어야 한다는 점만 고려하면 됩니다.

9. 로컬에서 실행

CD 프론트엔드
npm 실행 개발
CD ../백엔드
npm 실행 시작:dev
CD ../
docker-compose -f docker-compose.dev.yml up -d

이제 브라우저에서 localhost를 열어 웹사이트를 확인할 수 있습니다. 이 예에서는 서버에 1개의 요청이 있고 클라이언트에 또 다른 요청이 있습니다. 이 두 요청은 모두 Next.J에서 호출되고 Nest.J에서 처리됩니다.

Deploy NextJs and NestJs as a single application

10. GitHub를 통해 서버에 배포 및 실행

이 문서에서는 Docker 레지스트리 및 GitHub Actions를 사용하여 서버에 프로젝트를 배포하는 방법을 살펴봅니다. 프로세스는 Docker 레지스트리에서 백엔드와 프런트엔드 모두에 대한 Docker 이미지를 생성하는 것으로 시작됩니다. 그런 다음 GitHub 저장소를 설정하고 원활한 배포를 위해 필요한 비밀을 구성해야 합니다.

DOCKERHUB_USERNAME
DOCKERHUB_TOKEN
DOCKER_FRONTEND_IMAGE
DOCKER_BACKEND_IMAGE
REMOTE_SERVER_HOST
REMOTE_SERVER_USERNAME
REMOTE_SERVER_SSH_KEY
REMOTE_SERVER_SSH_PORT

백엔드와 프런트엔드에 하나의 저장소를 사용하는 것의 단점은 무언가를 푸시할 때마다 두 이미지가 모두 다시 빌드된다는 것입니다. 이를 최적화하기 위해 다음 조건을 사용할 수 있습니다.

if: contains(github.event_name, ‘push’) && !startsWith(github.event.head_commit.message, ‘frontend’)
if: contains(github.event_name, ‘push’) && !startsWith(github.event.head_commit.message, ‘backend’)

커밋 메시지를 지정하여 원하는 이미지만 다시 빌드하는 것이 가능합니다.

파일: ./github/workflows/deploy.yml

name: deploy nextjs and nestjs to GITHUB

on:
  push:
    branches: [ "main" ]

jobs:
  build-and-push-frontend:
    runs-on: ubuntu-latest

    if: contains(github.event_name, 'push') && !startsWith(github.event.head_commit.message, 'backend')

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push frontend to Docker Hub
        uses: docker/build-push-action@v2
        with:
          context: frontend
          file: frontend/Dockerfile
          push: true
          tags: ${{ secrets.DOCKER_FRONTEND_IMAGE }}:latest

      - name: SSH into the remote server and deploy frontend
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.REMOTE_SERVER_HOST }}
          username: ${{ secrets.REMOTE_SERVER_USERNAME }}
          password: ${{ secrets.REMOTE_SERVER_SSH_KEY }}
          port: ${{ secrets.REMOTE_SERVER_SSH_PORT }}
          script: |
            cd website/
            docker rmi -f ${{ secrets.DOCKER_FRONTEND_IMAGE }}:latest
            docker-compose down
            docker-compose up -d

  build-and-push-backend:
    runs-on: ubuntu-latest

    if: contains(github.event_name, 'push') && !startsWith(github.event.head_commit.message, 'frontend')

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push backend to Docker Hub
        uses: docker/build-push-action@v2
        with:
          context: backend
          file: backend/Dockerfile
          push: true
          tags: ${{ secrets.DOCKER_BACKEND_IMAGE }}:latest

      - name: SSH into the remote server and deploy backend
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.REMOTE_SERVER_HOST }}
          username: ${{ secrets.REMOTE_SERVER_USERNAME }}
          password: ${{ secrets.REMOTE_SERVER_SSH_KEY }}
          port: ${{ secrets.REMOTE_SERVER_SSH_PORT }}
          script: |
            cd website/
            docker rmi -f ${{ secrets.DOCKER_BACKEND_IMAGE }}:latest
            docker-compose down
            docker-compose up -d=
직업: 빌드 및 푸시 프런트엔드: 실행: 우분투 최신 if: 포함(github.event_name, 'push') && !startsWith(github.event.head_commit.message, 'backend') 단계: - 이름 : 체크아웃 용도: actions/checkout@v3 - 이름: Docker Hub에 로그인합니다. 용도: docker/login-action@v1 와 함께: 사용자 이름: ${{ secrets.DOCKERHUB_USERNAME }} 비밀번호: ${{ secrets.DOCKERHUB_TOKEN }} - 이름: 프런트엔드를 빌드하고 Docker Hub에 푸시합니다. 용도: docker/build-push-action@v2 와 함께: 컨텍스트: 프론트엔드 파일: 프론트엔드/Dockerfile 푸시: 사실 태그: ${{ secrets.DOCKER_FRONTEND_IMAGE }}:최신 - 이름: SSH를 통해 원격 서버에 접속하고 프런트엔드 배포 용도: appleboy/ssh-action@master 와 함께: 호스트: ${{ secrets.REMOTE_SERVER_HOST }} 사용자 이름: ${{ secrets.REMOTE_SERVER_USERNAME }} 비밀번호: ${{ secrets.REMOTE_SERVER_SSH_KEY }} 포트: ${{ secrets.REMOTE_SERVER_SSH_PORT }} 스크립트: | CD 웹사이트/ docker rmi -f ${{ secrets.DOCKER_FRONTEND_IMAGE }}:최신 도커 작성 도커-작성 -d 빌드 및 푸시 백엔드: 실행: 우분투 최신 if: 포함(github.event_name, 'push') && !startsWith(github.event.head_commit.message, 'frontend') 단계: - 이름 : 체크아웃 용도: actions/checkout@v3 - 이름: Docker Hub에 로그인합니다. 용도: docker/login-action@v1 와 함께: 사용자 이름: ${{ secrets.DOCKERHUB_USERNAME }} 비밀번호: ${{ secrets.DOCKERHUB_TOKEN }} - 이름: 백엔드를 빌드하고 Docker Hub에 푸시합니다. 용도: docker/build-push-action@v2 와 함께: 컨텍스트: 백엔드 파일: 백엔드/Dockerfile 푸시: 사실 태그: ${{ secrets.DOCKER_BACKEND_IMAGE }}:최신 - 이름: SSH를 통해 원격 서버에 접속하고 백엔드 배포 용도: appleboy/ssh-action@master 와 함께: 호스트: ${{ secrets.REMOTE_SERVER_HOST }} 사용자 이름: ${{ secrets.REMOTE_SERVER_USERNAME }} 비밀번호: ${{ secrets.REMOTE_SERVER_SSH_KEY }} 포트: ${{ secrets.REMOTE_SERVER_SSH_PORT }} 스크립트: | CD 웹사이트/ docker rmi -f ${{ secrets.DOCKER_BACKEND_IMAGE }}:최신 도커 작성 도커-작성 -d=

저장소: https://github.com/xvandevx/blog-examples/tree/main/nextjs-nestjs-deploy

요약

이 문서는 Next.js와 Nest.js를 단일 서버에 함께 배포하여 간소화된 설정을 원하는 개발자를 위한 솔루션으로 만드는 실습 가이드입니다. 프런트엔드용 Next.js와 백엔드용 Nest.js의 장점을 결합하여 Docker 및 GitHub Actions를 사용하여 애플리케이션의 두 부분을 효율적으로 관리하는 방법을 보여주었습니다. 배포 프로세스를 단순화하여 여러 구성을 저글링하는 대신 앱 구축에 집중할 수 있습니다. 번거로움을 최소화하면서 풀 스택 프로젝트를 빠르게 시작하고 실행하려는 사람들에게 적합합니다.

릴리스 선언문 이 기사는 https://dev.to/xvandev/deploy-nextjs-and-nestjs-as-a-single-application-15mj?1에서 복제됩니다.1 침해 내용이 있는 경우, [email protected]으로 연락하여 삭제하시기 바랍니다. 그것
최신 튜토리얼 더>

부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

Copyright© 2022 湘ICP备2022001581号-3